Removed EastMesh MQTT broker support.

It's intended for Eastern Australia repeaters only.  Using original EastMesh
firmware is prefered in this case.
Этот коммит содержится в:
Valentin V. Bartenev
2026-04-28 00:52:26 +03:00
родитель 64cebbc750
Коммит 46b0df7a4b
8 изменённых файлов: 64 добавлений и 413 удалений
+9 -81
Просмотреть файл
@@ -1,64 +1,25 @@
# MeshCore-EastMesh-MeshCoreTel # MeshCoreTel Firmware
This is a fork of MeshCore-EastMesh firmware with MeshCoreTel broker support. This is a fork of MeshCore-EastMesh firmware with MeshCoreTel broker support instead of EastMesh.
Original firmware (credits to Jared Dohrman, Scott Powell / Ripple Radios and the MeshCore contributors):
- <https://github.com/xJARiD/MeshCore-EastMesh>
MeshCoreTel: MeshCoreTel:
- <https://meshcoretel.ru/> - <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: ## Key MeshCoreTel Features
- 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
### MQTT Repeater Additions ### MQTT Repeater Additions
- hardcoded broker support for: - hardcoded broker support for:
- `meshcoretel` - `meshcoretel`
- `eastmesh-au`
- `letsmesh-eu` - `letsmesh-eu`
- `letsmesh-us` - `letsmesh-us`
- WSS transport at `/mqtt` - WSS transport at `/mqtt`
@@ -105,46 +66,13 @@ These rescue commands are only available after entering `CLI Rescue`:
- `set wifi.pwd <password>` - `set wifi.pwd <password>`
- `set wifi.powersaving none|min|max` - `set wifi.powersaving none|min|max`
## Active GitHub Workflows ## Status
- `.github/workflows/build-companion-wifi-firmwares.yml` Tested with my Heltec V4 OLED.
- `.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.
## Documentation ## Documentation
Published docs site:
- <https://xjarid.github.io/MeshCore-EastMesh/>
Current docs pages: Current docs pages:
- [Home](./docs/index.md) - [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) - [Custom CLI Commands](./docs/custom-cli.md)
+3 -7
Просмотреть файл
@@ -1,6 +1,6 @@
# Custom CLI Commands # 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. 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 ### MQTT Endpoints
- `set mqtt.meshcoretel on|off`
- `get mqtt.meshcoretel` - `get mqtt.meshcoretel`
- `set mqtt.letsmesh-us on|off` - `set mqtt.meshcoretel on|off`
- `get mqtt.eastmesh-au`
- `set mqtt.eastmesh-au on|off`
- `get mqtt.letsmesh-eu` - `get mqtt.letsmesh-eu`
- `set mqtt.letsmesh-eu on|off` - `set mqtt.letsmesh-eu on|off`
- `get mqtt.letsmesh-us` - `get mqtt.letsmesh-us`
@@ -52,11 +49,10 @@ Notes:
- new repeater MQTT installs default `mqtt.iata` to `UNSET` - new repeater MQTT installs default `mqtt.iata` to `UNSET`
- `letsmesh-eu` and `letsmesh-us` remain off by default unless already configured in saved prefs - `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: Legacy dotted aliases are also accepted:
- `mqtt.eastmesh.au`
- `mqtt.letsmesh.eu` - `mqtt.letsmesh.eu`
- `mqtt.letsmesh.us` - `mqtt.letsmesh.us`
+2 -10
Просмотреть файл
@@ -2302,14 +2302,12 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
sprintf(reply, "> %s", mqtt.isRawEnabled() ? "on" : "off"); sprintf(reply, "> %s", mqtt.isRawEnabled() ? "on" : "off");
} else if (memcmp(command, "get mqtt.tx", 11) == 0) { } else if (memcmp(command, "get mqtt.tx", 11) == 0) {
sprintf(reply, "> %s", mqtt.isTxEnabled() ? "on" : "off"); 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"); 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) { } 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"); 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) { } 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"); 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) { } else if (memcmp(command, "set mqtt.tx ", 12) == 0) {
mqtt.setTxEnabled(memcmp(&command[12], "on", 2) == 0); mqtt.setTxEnabled(memcmp(&command[12], "on", 2) == 0);
strcpy(reply, "OK"); 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) { } else if (memcmp(command, "set mqtt.status ", 16) == 0) {
mqtt.setStatusEnabled(memcmp(&command[16], "on", 2) == 0); mqtt.setStatusEnabled(memcmp(&command[16], "on", 2) == 0);
strcpy(reply, "OK"); 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)) { if (mqtt.setEndpointEnabled(0x01, memcmp(&command[21], "on", 2) == 0)) {
strcpy(reply, "OK"); strcpy(reply, "OK");
} else { } else {
@@ -2358,12 +2356,6 @@ 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.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 #endif
} else{ } else{
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
-86
Просмотреть файл
@@ -2,41 +2,6 @@
namespace mqtt_ca_certs { 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. // LetsMesh currently presents a certificate issued by Google Trust Services WE1.
static const char kLetsmeshWe1Pem[] = static const char kLetsmeshWe1Pem[] =
R"PEM(-----BEGIN CERTIFICATE----- R"PEM(-----BEGIN CERTIFICATE-----
@@ -58,55 +23,4 @@ sQIwJonMaAFi54mrfhfoFNZEfuNMSQ6/bIBiNLiyoX46FohQvKeIoJ99cx7sUkFN
-----END CERTIFICATE----- -----END CERTIFICATE-----
)PEM"; )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 } // namespace mqtt_ca_certs
+1 -1
Просмотреть файл
@@ -67,7 +67,7 @@ bool MQTTPrefsStore::load(FILESYSTEM* fs, MQTTPrefs& prefs) {
StrHelper::strncpy(prefs.iata, MQTT_DEFAULT_IATA, sizeof(prefs.iata)); StrHelper::strncpy(prefs.iata, MQTT_DEFAULT_IATA, sizeof(prefs.iata));
} }
prefs.status_interval_ms = kFixedStatusIntervalMs; prefs.status_interval_ms = kFixedStatusIntervalMs;
prefs.enabled_mask &= 0x0F; prefs.enabled_mask &= 0x07;
return true; return true;
} }
+14 -16
Просмотреть файл
@@ -17,15 +17,15 @@
#include <time.h> #include <time.h>
#ifndef FIRMWARE_VERSION #ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.15.0 (VBart EastMesh)" #define FIRMWARE_VERSION "v1.15.0 (VBart MeshCoreTel)"
#endif #endif
#ifndef FIRMWARE_BUILD_DATE #ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "20 Mar 2026" #define FIRMWARE_BUILD_DATE "27 Apr 2026"
#endif #endif
#ifndef CLIENT_VERSION #ifndef CLIENT_VERSION
#define CLIENT_VERSION "eastmesh-repeater-mqtt" #define CLIENT_VERSION "meshcoretel-repeater-mqtt"
#endif #endif
#ifndef MQTT_DEBUG #ifndef MQTT_DEBUG
@@ -95,15 +95,13 @@ void logMqttMemorySnapshot(const char*, const char* = nullptr) {
} }
const MQTTUplink::BrokerSpec MQTTUplink::kBrokerSpecs[4] = { const MQTTUplink::BrokerSpec MQTTUplink::kBrokerSpecs[3] = {
{"eastmesh-au", "eastmesh-au", "mqtt2.eastmesh.au", "wss://mqtt2.eastmesh.au:443/mqtt", {"meshcoretel", "meshcoretel", "meshcoretel.ru", "mqtt://meshcoretel.ru:1883",
kEastmeshBit, false, nullptr, nullptr}, kMeshcoretelBit, true, "meshcore", "meshcore"},
{"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, nullptr, nullptr}, kLetsmeshEuBit, false, nullptr, nullptr},
{"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, nullptr, nullptr}, kLetsmeshUsBit, false, nullptr, nullptr},
{"meshcoretel", "meshcoretel", "meshcoretel.ru", "mqtt://meshcoretel.ru:1883",
kMeshcoretelBit, true, "meshcore", "meshcore"},
}; };
MQTTUplink::MQTTUplink(mesh::RTCClock& rtc, mesh::LocalIdentity& identity) 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)); memset(_device_id, 0, sizeof(_device_id));
MQTTPrefsStore::setDefaults(_prefs); 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])); memset(&_brokers[i], 0, sizeof(_brokers[i]));
_brokers[i].spec = &kBrokerSpecs[i]; _brokers[i].spec = &kBrokerSpecs[i];
} }
@@ -128,7 +126,7 @@ bool MQTTUplink::savePrefs() {
} }
bool MQTTUplink::hasEnabledBroker() const { bool MQTTUplink::hasEnabledBroker() const {
return (_prefs.enabled_mask & 0x0F) != 0; return (_prefs.enabled_mask & 0x07) != 0;
} }
bool MQTTUplink::isUnsetIataValue(const char* iata) { 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.port = 443;
cfg.broker.address.transport = MQTT_TRANSPORT_OVER_WSS; cfg.broker.address.transport = MQTT_TRANSPORT_OVER_WSS;
cfg.broker.address.path = "/mqtt"; 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.authentication.password = broker.token;
} }
cfg.credentials.username = broker.username; cfg.credentials.username = broker.username;
@@ -700,7 +698,7 @@ void MQTTUplink::ensureBroker(BrokerState& broker, bool allow_new_connect) {
cfg.port = 443; cfg.port = 443;
cfg.transport = MQTT_TRANSPORT_OVER_WSS; cfg.transport = MQTT_TRANSPORT_OVER_WSS;
cfg.path = "/mqtt"; cfg.path = "/mqtt";
cfg.cert_pem = mqtt_ca_certs::kCombinedPem; cfg.cert_pem = mqtt_ca_certs::kLetsmeshWe1Pem;
cfg.password = broker.token; cfg.password = broker.token;
} }
cfg.username = broker.username; cfg.username = broker.username;
@@ -741,7 +739,7 @@ void MQTTUplink::ensureBroker(BrokerState& broker, bool allow_new_connect) {
void MQTTUplink::begin(FILESYSTEM* fs) { void MQTTUplink::begin(FILESYSTEM* fs) {
_fs = fs; _fs = fs;
MQTTPrefsStore::load(_fs, _prefs); 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) { if (normalized_mask != _prefs.enabled_mask) {
_prefs.enabled_mask = normalized_mask; _prefs.enabled_mask = normalized_mask;
savePrefs(); savePrefs();
@@ -885,16 +883,16 @@ void MQTTUplink::formatStatusReply(char* reply, size_t reply_size) const {
return "retry"; 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->isWifiConnected()) ? "up" : "down",
(_network != nullptr && _network->hasTimeSync()) ? "up" : "wait", (_network != nullptr && _network->hasTimeSync()) ? "up" : "wait",
_prefs.iata, _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"); _prefs.status_enabled ? "on" : "off", _prefs.tx_enabled ? "on" : "off");
} }
bool MQTTUplink::setEndpointEnabled(uint8_t bit, bool enabled) { 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) { if (enabled) {
next_mask = normalizeEnabledMask(next_mask | bit); next_mask = normalizeEnabledMask(next_mask | bit);
if ((next_mask & bit) == 0) { if ((next_mask & bit) == 0) {
+3 -4
Просмотреть файл
@@ -110,15 +110,14 @@ private:
NetworkStateProvider* _network; NetworkStateProvider* _network;
#if defined(ESP_PLATFORM) #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 kLetsmeshEuBit = 0x02;
static constexpr uint8_t kLetsmeshUsBit = 0x04; static constexpr uint8_t kLetsmeshUsBit = 0x04;
static constexpr uint8_t kMeshcoretelBit = 0x08;
static constexpr uint8_t kMaxEnabledBrokers = 2; static constexpr uint8_t kMaxEnabledBrokers = 2;
static const BrokerSpec kBrokerSpecs[4]; static const BrokerSpec kBrokerSpecs[3];
static bool isUnsetIataValue(const char* iata); 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); static void handleMqttEvent(void* handler_args, esp_event_base_t base, int32_t event_id, void* event_data);
bool hasEnabledBroker() const; bool hasEnabledBroker() const;
+32 -208
Просмотреть файл
@@ -854,71 +854,6 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
<option value="VSG">VSG - Lugansk</option> <option value="VSG">VSG - Lugansk</option>
<option value="VVO">VVO - Vladivostok</option> <option value="VVO">VVO - Vladivostok</option>
</optgroup> </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> </select>
<button class="iconbtn" data-load-cmd="get mqtt.iata" data-load-input="mqttIata" title="Refresh MQTT IATA">&#8635;</button> <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> <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>
<div class="field-card"> <div class="field-card">
<label class="label">MQTT Servers</label> <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">
<div class="broker-group-title">Meshcoretel</div> <div class="broker-group-title">MeshCoreTel</div>
<div class="broker-grid single"> <div class="broker-grid single">
<div class="broker-card"> <div class="broker-card">
<div class="broker-mode"> <div class="broker-mode">
<div class="broker-row"> <div class="broker-row">
<div class="broker-copy"> <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 class="broker-state" id="mqttMeshcoretelState">Off</div>
</div> </div>
</div> </div>
@@ -966,29 +901,6 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
</div> </div>
</div> </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">
<div class="broker-group-title">LetsMesh</div> <div class="broker-group-title">LetsMesh</div>
<div class="broker-grid single"> <div class="broker-grid single">
@@ -1731,12 +1643,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
} }
const renderNeighbourId = (neighbour) => { const renderNeighbourId = (neighbour) => {
const shortId = escapeHtml(neighbour.id || "--"); const shortId = escapeHtml(neighbour.id || "--");
const fullId = typeof neighbour.full_id === "string" ? neighbour.full_id.trim() : ""; return shortId;
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>`;
}; };
const rows = neighbours.map((neighbour) => `<tr> const rows = neighbours.map((neighbour) => `<tr>
<td>${renderNeighbourId(neighbour)}</td> <td>${renderNeighbourId(neighbour)}</td>
@@ -2126,7 +2033,7 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
} }
if (inlineWarning) { if (inlineWarning) {
inlineWarning.textContent = showWarning 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"; if (usOn) return "us";
return "off"; return "off";
} }
function refreshEastmeshModeUi() { function refreshMeshcoretelModeUi() {
const input = document.getElementById("mqttEastmeshAu"); const input = document.getElementById("mqttMeshcoretel");
const enabled = !!(input && input.checked); const enabled = !!(input && input.checked);
const slider = document.getElementById("mqttEastmeshMode"); const slider = document.getElementById("mqttMeshcoretelMode");
if (slider) { if (slider) {
slider.value = enabled ? "1" : "0"; slider.value = enabled ? "1" : "0";
} }
document.querySelectorAll("[data-eastmesh-label]").forEach((label) => { document.querySelectorAll("[data-meshcoretel-label]").forEach((label) => {
label.classList.toggle("active", label.dataset.eastmeshLabel === (enabled ? "on" : "off")); label.classList.toggle("active", label.dataset.meshcoretelLabel === (enabled ? "on" : "off"));
}); });
} }
function getLetsmeshModeIndex(mode) { function getLetsmeshModeIndex(mode) {
@@ -2273,27 +2180,15 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
return Object.prototype.hasOwnProperty.call(order, mode) ? order[mode] : 0; return Object.prototype.hasOwnProperty.call(order, mode) ? order[mode] : 0;
} }
function clampLetsmeshModeIndex(index) { function clampLetsmeshModeIndex(index) {
const eastmesh = document.getElementById("mqttEastmeshAu");
const eastmeshEnabled = !!(eastmesh && eastmesh.checked);
const meshcoretel = document.getElementById("mqttMeshcoretel"); const meshcoretel = document.getElementById("mqttMeshcoretel");
const meshcoretelEnabled = !!(meshcoretel && meshcoretel.checked); const meshcoretelEnabled = !!(meshcoretel && meshcoretel.checked);
const bounded = Math.max(0, Math.min(3, index)); const bounded = Math.max(0, Math.min(3, index));
if ((eastmeshEnabled || meshcoretelEnabled) && bounded === 3) { return meshcoretelEnabled && bounded === 3 ? 1 : bounded;
return 1;
}
if (eastmeshEnabled && meshcoretelEnabled && bounded > 0) {
return 0;
}
return bounded;
} }
function refreshLetsmeshModeUi() { function refreshLetsmeshModeUi() {
const mode = getLetsmeshMode(); const mode = getLetsmeshMode();
const eastmesh = document.getElementById("mqttEastmeshAu");
const eastmeshEnabled = !!(eastmesh && eastmesh.checked);
const meshcoretel = document.getElementById("mqttMeshcoretel"); const meshcoretel = document.getElementById("mqttMeshcoretel");
const meshcoretelEnabled = !!(meshcoretel && meshcoretel.checked); const meshcoretelEnabled = !!(meshcoretel && meshcoretel.checked);
const bothBlocked = eastmeshEnabled || meshcoretelEnabled;
const allBlocked = eastmeshEnabled && meshcoretelEnabled;
const state = document.getElementById("mqttLetsmeshModeState"); const state = document.getElementById("mqttLetsmeshModeState");
const labels = { off:"Off", eu:"EU", us:"US", both:"Both" }; const labels = { off:"Off", eu:"EU", us:"US", both:"Both" };
if (state) { if (state) {
@@ -2304,15 +2199,11 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
if (slider) { if (slider) {
slider.max = "3"; slider.max = "3";
slider.value = String(clampLetsmeshModeIndex(getLetsmeshModeIndex(mode))); slider.value = String(clampLetsmeshModeIndex(getLetsmeshModeIndex(mode)));
slider.disabled = allBlocked;
} }
document.querySelectorAll("[data-letsmesh-label]").forEach((label) => { document.querySelectorAll("[data-letsmesh-label]").forEach((label) => {
const labelMode = label.dataset.letsmeshLabel; const labelMode = label.dataset.letsmeshLabel;
label.classList.toggle("active", labelMode === mode); label.classList.toggle("active", labelMode === mode);
label.classList.toggle("disabled", label.classList.toggle("disabled", meshcoretelEnabled && labelMode === "both");
(bothBlocked && labelMode === "both") ||
(allBlocked && labelMode !== "off")
);
}); });
} }
function setBrokerToggle(inputId, state) { function setBrokerToggle(inputId, state) {
@@ -2327,11 +2218,9 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
} }
if (inputId === "mqttLetsmeshEu" || inputId === "mqttLetsmeshUs") { if (inputId === "mqttLetsmeshEu" || inputId === "mqttLetsmeshUs") {
refreshLetsmeshModeUi(); refreshLetsmeshModeUi();
} else if (inputId === "mqttEastmeshAu") {
refreshEastmeshModeUi();
refreshLetsmeshModeUi();
} else if (inputId === "mqttMeshcoretel") { } else if (inputId === "mqttMeshcoretel") {
refreshMeshcoretelModeUi(); refreshMeshcoretelModeUi();
refreshLetsmeshModeUi();
} }
} }
async function loadBrokerState(cmd, inputId, options = {}) { async function loadBrokerState(cmd, inputId, options = {}) {
@@ -2449,54 +2338,34 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
if (mqttIataSelect) { if (mqttIataSelect) {
mqttIataSelect.addEventListener("change", refreshMqttIataWarning); mqttIataSelect.addEventListener("change", refreshMqttIataWarning);
} }
async function setEastmeshMode(enabled) { async function setMeshcoretelMode(enabled) {
if (enabled) { if (enabled && getLetsmeshMode() === "both") {
if (getLetsmeshMode() === "both") { await setLetsmeshMode("eu");
await setLetsmeshMode("eu");
}
const meshcoretel = document.getElementById("mqttMeshcoretel");
if (meshcoretel && meshcoretel.checked && getLetsmeshMode() !== "off") {
await setLetsmeshMode("off");
}
} }
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) { if (!result.ok) {
refreshEastmeshModeUi(); refreshMeshcoretelModeUi();
refreshLetsmeshModeUi(); refreshLetsmeshModeUi();
return; return;
} }
setBrokerToggle("mqttEastmeshAu", enabled ? "on" : "off"); setBrokerToggle("mqttMeshcoretel", enabled ? "on" : "off");
refreshEastmeshModeUi();
refreshLetsmeshModeUi();
refreshMeshcoretelModeUi(); refreshMeshcoretelModeUi();
refreshLetsmeshModeUi();
} }
const eastmeshModeSlider = document.getElementById("mqttEastmeshMode"); const meshcoretelModeSlider = document.getElementById("mqttMeshcoretelMode");
if (eastmeshModeSlider) { if (meshcoretelModeSlider) {
eastmeshModeSlider.addEventListener("input", () => { meshcoretelModeSlider.addEventListener("input", () => {
eastmeshModeSlider.value = (Number.parseInt(eastmeshModeSlider.value, 10) || 0) >= 1 ? "1" : "0"; meshcoretelModeSlider.value = (Number.parseInt(meshcoretelModeSlider.value, 10) || 0) >= 1 ? "1" : "0";
}); });
eastmeshModeSlider.addEventListener("change", () => { meshcoretelModeSlider.addEventListener("change", () => {
setEastmeshMode((Number.parseInt(eastmeshModeSlider.value, 10) || 0) >= 1); setMeshcoretelMode((Number.parseInt(meshcoretelModeSlider.value, 10) || 0) >= 1);
}); });
} }
async function setLetsmeshMode(mode) { async function setLetsmeshMode(mode) {
const eastmesh = document.getElementById("mqttEastmeshAu");
const eastmeshEnabled = !!(eastmesh && eastmesh.checked);
const meshcoretel = document.getElementById("mqttMeshcoretel"); const meshcoretel = document.getElementById("mqttMeshcoretel");
const meshcoretelEnabled = !!(meshcoretel && meshcoretel.checked); if (mode === "both" && meshcoretel && meshcoretel.checked) {
if (mode === "both") { refreshLetsmeshModeUi();
if (eastmeshEnabled) { return;
refreshLetsmeshModeUi();
return;
}
if (meshcoretelEnabled) {
refreshLetsmeshModeUi();
return;
}
}
if (mode !== "off" && meshcoretelEnabled && eastmeshEnabled) {
refreshLetsmeshModeUi();
return;
} }
const desired = { const desired = {
eu: mode === "eu" || mode === "both", eu: mode === "eu" || mode === "both",
@@ -2530,49 +2399,6 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
setLetsmeshMode(modes[index] || "off"); 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 = () => { document.getElementById("saveOwnerInfo").onclick = () => {
const value = document.getElementById("ownerInfo").value.replace(/\n/g, "|"); const value = document.getElementById("ownerInfo").value.replace(/\n/g, "|");
runCommand("set owner.info " + value); 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.iata", "mqttIata", null, quiet),
() => loadField("get mqtt.owner", "mqttOwner", null, quiet), () => loadField("get mqtt.owner", "mqttOwner", null, quiet),
() => loadField("get mqtt.email", "mqttEmail", 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-eu", "mqttLetsmeshEu", quiet),
() => loadBrokerState("get mqtt.letsmesh-us", "mqttLetsmeshUs", quiet), () => loadBrokerState("get mqtt.letsmesh-us", "mqttLetsmeshUs", quiet)
() => loadBrokerState("get mqtt.meshcoretel", "mqttMeshcoretel", quiet)
]); ]);
statusEl.textContent = "Ready"; statusEl.textContent = "Ready";
} catch (error) { } catch (error) {
@@ -2713,9 +2538,8 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
} }
loadRadioPresets(); loadRadioPresets();
} }
refreshEastmeshModeUi();
refreshLetsmeshModeUi();
refreshMeshcoretelModeUi(); refreshMeshcoretelModeUi();
refreshLetsmeshModeUi();
initApp(); initApp();
</script> </script>
</body> </body>