diff --git a/README.md b/README.md index d80d3651..17d09db8 100644 --- a/README.md +++ b/README.md @@ -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): - -- +This is a fork of MeshCore-EastMesh firmware with MeshCoreTel broker support instead of EastMesh. MeshCoreTel: - -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. +- -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 ` - `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: - -- - 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) diff --git a/docs/custom-cli.md b/docs/custom-cli.md index bc56f2ef..f44d2c93 100644 --- a/docs/custom-cli.md +++ b/docs/custom-cli.md @@ -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` diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b1379030..64043d83 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -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 diff --git a/src/helpers/mqtt/MQTTCaCerts.h b/src/helpers/mqtt/MQTTCaCerts.h index 336d6996..24d65473 100644 --- a/src/helpers/mqtt/MQTTCaCerts.h +++ b/src/helpers/mqtt/MQTTCaCerts.h @@ -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 diff --git a/src/helpers/mqtt/MQTTPrefs.cpp b/src/helpers/mqtt/MQTTPrefs.cpp index 8f43f0bc..a9dde6b0 100644 --- a/src/helpers/mqtt/MQTTPrefs.cpp +++ b/src/helpers/mqtt/MQTTPrefs.cpp @@ -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; } diff --git a/src/helpers/mqtt/MQTTUplink.cpp b/src/helpers/mqtt/MQTTUplink.cpp index 21cf3ba4..eba4b168 100644 --- a/src/helpers/mqtt/MQTTUplink.cpp +++ b/src/helpers/mqtt/MQTTUplink.cpp @@ -17,15 +17,15 @@ #include #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) { diff --git a/src/helpers/mqtt/MQTTUplink.h b/src/helpers/mqtt/MQTTUplink.h index 1792103a..ccb23f5e 100644 --- a/src/helpers/mqtt/MQTTUplink.h +++ b/src/helpers/mqtt/MQTTUplink.h @@ -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; diff --git a/src/helpers/web/WebPanelServer.cpp b/src/helpers/web/WebPanelServer.cpp index 2d84f240..94324a08 100644 --- a/src/helpers/web/WebPanelServer.cpp +++ b/src/helpers/web/WebPanelServer.cpp @@ -854,71 +854,6 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -942,15 +877,15 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
-
+
-
Meshcoretel
+
MeshCoreTel
-
Meshcoretel RU
+
MeshCoreTel RU
Off
@@ -966,29 +901,6 @@ const char kWebPanelAppHtml[] PROGMEM = R"HTML(
-
-
EastMesh
-
-
-
-
-
-
EastMesh AU
-
Off
-
-
-
- - -
- -
-
-
-
LetsMesh
@@ -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 `${shortId}`; + return shortId; }; const rows = neighbours.map((neighbour) => ` ${renderNeighbourId(neighbour)} @@ -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();