Эх сурвалжийг харах

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 3 сар өмнө
parent
commit
46b0df7a4b

+ 9 - 81
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):
-
-- <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.
-
-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.
+Original firmware (credits to Jared Dohrman, Scott Powell / Ripple Radios and the MeshCore contributors):
 
-## What This Repo Adds
+- <https://github.com/xJARiD/MeshCore-EastMesh>
 
-- `*_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
+MeshCoreTel firmware builds for MeshCore, with MQTT repeaters, WiFi companions, and simple release downloads for supported boards.
 
-## Status
+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.
 
-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
-
-- `.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`
+## Status
 
-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
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`
 

+ 2 - 10
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

+ 0 - 86
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

+ 1 - 1
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;
 }
 

+ 14 - 16
src/helpers/mqtt/MQTTUplink.cpp

@@ -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
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;

+ 32 - 208
src/helpers/web/WebPanelServer.cpp

@@ -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>