Enable Meshtext on all supported Full Companion boards

This commit is contained in:
mikecarper committed 2026-10-23 17:41:12 -07:00
1 parent 881d8e58a5
commit 127d1bec24
3 files changed
+85 -73

No files matched your search

+12 -5
View File
@@ -556,11 +556,18 @@ download can discard it.
### Meshtext channel chat
`MESH_ENABLE_MESHTEXT_CHAT` enables the text-only Meshtext backend in the
Companion. It defaults on for ESP32-S3 **Full Companion** builds using
SX1262 LoRa and for the RAK4631 **Full Companion** starting with 1.17.1.10
preview3. An ESP-NOW primary and other builds default off. RAK4631 1.17.1.10
preview2 and earlier builds did not include this backend. This is not
a replacement for stock peer firmware or a cross-protocol bridge.
Companion. It defaults on for **Full Companion** builds on ESP32 and nRF52,
including classic ESP32, C3, C6, S3, T1000-E, XIAO nRF52, RAK3401, Nano G2,
T114 and the other Full families. The installed radio driver must support
Meshtext and the selected frequency/modulation. An ESP-NOW primary, non-Full
builds, and other platforms default off; an explicit build override is retained.
This default replaces the earlier ESP32-S3/SX1262 and RAK4631-only scope.
Older images can therefore lack the backend even when their channel editor
accepts an `mt-` name; a factory reset cannot add code omitted from an image.
The common default and modem support do not establish physical qualification
for every board. Each release image must still pass its flash and runtime RAM
budgets, and hardware interoperability must be checked separately.
In an enabled build, an `mt-` name always requests Meshtext, even if radio2 is
off or setup is blocked. It never falls back to sending MeshCore frames. In a
+7 -17
View File
@@ -4,24 +4,14 @@
#include "EthernetTunnelConfig.h"
#include "ota/OtaDirectConfig.h"
// Resolve the chip in every translation unit, including Dispatcher.cpp which
// includes this before Arduino.h. Otherwise different class layouts could be
// selected for the same build depending on include order.
#if !defined(MESH_ENABLE_MESHTEXT_CHAT) && defined(ESP32_PLATFORM) && defined(ARDUINO)
#include <sdkconfig.h>
#endif
// Channel chat is an optional Companion transport, never a bridge. Qualified
// families are ESP32-S3 Full/SX1262 and RAK4631 Full; other builds retain their
// features and do not acquire the codec or its bounded raw-frame queue. A new
// family must qualify its modem implementation, flash and runtime RAM first.
// Full Companion includes channel chat on both supported platforms. Keep this
// independent of board/chip macros, which some Arduino variants reveal only
// after shared sources include this header. Every translation unit must select
// the same layouts. The concrete driver still gates modem/profile support;
// release builds must also pass their unchanged flash and runtime RAM budgets.
#ifndef MESH_ENABLE_MESHTEXT_CHAT
#if defined(ESP32_PLATFORM) && defined(CONFIG_IDF_TARGET_ESP32S3) \
&& defined(COMPANION_RADIO_FULL) && defined(USE_SX1262) \
&& !(defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW)
#define MESH_ENABLE_MESHTEXT_CHAT 1
#elif defined(NRF52_PLATFORM) && defined(RAK_4631) \
&& defined(COMPANION_RADIO_FULL) && defined(USE_SX1262) \
#if (defined(ESP32_PLATFORM) || defined(NRF52_PLATFORM)) \
&& defined(COMPANION_RADIO_FULL) && COMPANION_RADIO_FULL \
&& !(defined(MESH_PRIMARY_ESPNOW) && MESH_PRIMARY_ESPNOW)
#define MESH_ENABLE_MESHTEXT_CHAT 1
#else
+66 -51
View File
@@ -38,7 +38,7 @@ class MeshtextConfigTest(unittest.TestCase):
if arduino_first:
includes.reverse()
source = "".join("#include <" + name + ">\n" for name in includes)
source += feature + "\n"
source += "#if " + feature + "\n1\n#else\n0\n#endif\n"
result = subprocess.run(
[self.compiler, "-E", "-P", "-x", "c++", "-",
"-I", str(work), "-I", str(ROOT / "src"),
@@ -54,37 +54,47 @@ class MeshtextConfigTest(unittest.TestCase):
self.assertEqual(self.effective(defines, arduino_first=arduino_first, **kwargs),
str(expected))
def test_rak4631_full_companion_is_enabled_in_every_translation_unit(self):
self.assert_orders(["NRF52_PLATFORM", "RAK_4631", "COMPANION_RADIO_FULL", "USE_SX1262"], 1)
def test_nrf52_full_companion_families_are_enabled_in_every_translation_unit(self):
for board, modem in (
("RAK_4631", "USE_SX1262"),
("RAK_3401", "USE_SX1262"),
("T1000_E", "USE_LR1110"),
("XIAO_NRF52", "USE_SX1262"),
("NANO_G2_ULTRA", "USE_SX1262"),
("HELTEC_T114", "USE_SX1262"),
("MUZI_BASE", "USE_LR1121"),
):
self.assert_orders(["NRF52_PLATFORM", "COMPANION_RADIO_FULL=1", board, modem], 1)
# Shared sources may not know the board or radio chip yet.
self.assert_orders(["NRF52_PLATFORM", "COMPANION_RADIO_FULL=1"], 1)
def test_nrf52_default_stays_scoped_to_qualified_hardware_and_role(self):
enabled = ["NRF52_PLATFORM", "RAK_4631", "COMPANION_RADIO_FULL", "USE_SX1262"]
for missing in enabled:
self.assert_orders([item for item in enabled if item != missing], 0)
self.assert_orders(["NRF52_PLATFORM", "RAK_3401", "COMPANION_RADIO_FULL", "USE_SX1262"], 0)
self.assert_orders(enabled + ["MESH_PRIMARY_ESPNOW=1"], 0)
self.assert_orders(enabled + ["MESH_PRIMARY_ESPNOW=0"], 1)
def test_esp32_full_companion_does_not_depend_on_sdk_target_or_modem_macros(self):
for target in (None, "CONFIG_IDF_TARGET_ESP32", "CONFIG_IDF_TARGET_ESP32S2",
"CONFIG_IDF_TARGET_ESP32S3", "CONFIG_IDF_TARGET_ESP32C3",
"CONFIG_IDF_TARGET_ESP32C6"):
for modem in (None, "USE_SX1262", "USE_SX1276", "USE_SX1268",
"USE_LR1110", "USE_LR1121", "USE_LR2021", "USE_LLCC68"):
flags = ["ESP32_PLATFORM", "ARDUINO=10819", "COMPANION_RADIO_FULL=1"]
self.assert_orders(flags + ([modem] if modem else []), 1, sdk_target=target)
def test_esp32_s3_full_keeps_effective_sdk_target_enablement(self):
enabled = ["ESP32_PLATFORM", "ARDUINO=10819", "COMPANION_RADIO_FULL", "USE_SX1262"]
self.assert_orders(enabled, 1, sdk_target="CONFIG_IDF_TARGET_ESP32S3")
self.assert_orders(enabled + ["MESH_PRIMARY_ESPNOW=0"], 1,
sdk_target="CONFIG_IDF_TARGET_ESP32S3")
self.assert_orders(enabled + ["MESH_PRIMARY_ESPNOW=1"], 0,
sdk_target="CONFIG_IDF_TARGET_ESP32S3")
for target in (None, "CONFIG_IDF_TARGET_ESP32", "CONFIG_IDF_TARGET_ESP32S2"):
self.assert_orders(enabled, 0, sdk_target=target)
for missing in ("COMPANION_RADIO_FULL", "USE_SX1262"):
self.assert_orders([item for item in enabled if item != missing], 0,
sdk_target="CONFIG_IDF_TARGET_ESP32S3")
def test_defaults_keep_minimal_other_platforms_and_espnow_primary_disabled(self):
for platform in ("ESP32_PLATFORM", "NRF52_PLATFORM"):
for full in ([], ["COMPANION_RADIO_FULL=0"]):
self.assert_orders([platform, "USE_SX1262", *full], 0)
enabled = [platform, "COMPANION_RADIO_FULL=1"]
self.assert_orders(enabled + ["MESH_PRIMARY_ESPNOW=1"], 0)
self.assert_orders(enabled + ["MESH_PRIMARY_ESPNOW=0"], 1)
for platform in ([], ["RP2040_PLATFORM"], ["STM32_PLATFORM"]):
self.assert_orders([*platform, "COMPANION_RADIO_FULL=1", "USE_SX1262"], 0)
def test_explicit_enable_and_disable_override_defaults(self):
self.assert_orders(["MESH_ENABLE_MESHTEXT_CHAT=1"], 1)
self.assert_orders(["NRF52_PLATFORM", "RAK_4631", "COMPANION_RADIO_FULL",
"USE_SX1262", "MESH_ENABLE_MESHTEXT_CHAT=0"], 0)
self.assert_orders(["ESP32_PLATFORM", "ARDUINO=10819", "COMPANION_RADIO_FULL",
"USE_SX1262", "MESH_ENABLE_MESHTEXT_CHAT=0"], 0,
sdk_target="CONFIG_IDF_TARGET_ESP32S3")
for platform in ("NRF52_PLATFORM", "ESP32_PLATFORM"):
self.assert_orders([platform, "COMPANION_RADIO_FULL=1",
"MESH_ENABLE_MESHTEXT_CHAT=0"], 0)
for overrides in (["COMPANION_RADIO_FULL=0"],
["COMPANION_RADIO_FULL=1", "MESH_PRIMARY_ESPNOW=1"]):
self.assert_orders([platform, *overrides, "MESH_ENABLE_MESHTEXT_CHAT=1"], 1)
def test_tx_beacon_wire_support_does_not_enable_companion_chat(self):
for platform in ("ESP32_PLATFORM", "NRF52_PLATFORM", "RP2040_PLATFORM"):
@@ -102,23 +112,30 @@ class MeshtextConfigTest(unittest.TestCase):
for flags in (["STM32_PLATFORM", "USE_SX1262"], []):
self.assert_orders(flags, 0, feature="MESH_ENABLE_MESHTEXT_WIRE")
def test_late_variant_chip_discovery_does_not_change_wire_layout(self):
def test_late_variant_chip_discovery_does_not_change_chat_or_wire_layout(self):
with tempfile.TemporaryDirectory(prefix="meshcore-mt-late-variant-") as folder:
work = Path(folder)
# Nano G2 Ultra declares USE_SX1262 in its Arduino variant header,
# not in global flags. The codec translation unit omits Arduino.
(work / "Arduino.h").write_text(
"#pragma once\n#define USE_SX1262 1\n", encoding="ascii")
for arduino_first in (False, True):
source = "#include <Arduino.h>\n" if arduino_first else ""
source += "#include <helpers/MeshtextConfig.h>\n"
source += "static_assert(MESH_ENABLE_MESHTEXT_CHAT == 0, \"chat stays disabled\");\n"
source += "static_assert(MESH_ENABLE_MESHTEXT_WIRE == 1, \"consistent wire layout\");\n"
result = subprocess.run(
[self.compiler, "-std=c++17", "-fsyntax-only", "-x", "c++", "-",
"-DNRF52_PLATFORM", "-I", str(work), "-I", str(ROOT / "src")],
input=source, capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
# Some variants expose their chip only through Arduino.h, while
# Dispatcher and codec translation units include this header first.
for platform in ("NRF52_PLATFORM", "ESP32_PLATFORM"):
for modem in ("USE_SX1262", "USE_LR1110", "USE_LR1121", "USE_SX1276"):
(work / "Arduino.h").write_text(
"#pragma once\n#define " + modem + " 1\n", encoding="ascii")
for full in (0, 1):
for includes in (("Arduino.h", "helpers/MeshtextConfig.h"),
("helpers/MeshtextConfig.h", "Arduino.h"),
("helpers/MeshtextConfig.h",)):
with self.subTest(platform=platform, modem=modem,
full=full, includes=includes):
source = "".join("#include <" + name + ">\n" for name in includes)
source += f'static_assert(MESH_ENABLE_MESHTEXT_CHAT == {full}, "consistent chat layout");\n'
source += 'static_assert(MESH_ENABLE_MESHTEXT_WIRE == 1, "consistent wire layout");\n'
result = subprocess.run(
[self.compiler, "-std=c++17", "-fsyntax-only", "-x", "c++", "-",
"-D" + platform, f"-DCOMPANION_RADIO_FULL={full}",
"-I", str(work), "-I", str(ROOT / "src")],
input=source, capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_actual_driver_capability_and_mc_only_no_spi(self):
source = (ROOT / "src/helpers/radiolib/CustomSX1262Wrapper.h").read_text()
@@ -184,15 +201,13 @@ int main() {
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_chat_keeps_wire_support_with_beacons_disabled(self):
flags = ["NRF52_PLATFORM", "RAK_4631", "COMPANION_RADIO_FULL", "USE_SX1262",
"MESH_ENABLE_TX_BEACONS=0"]
self.assert_orders(flags, 1)
self.assert_orders(flags, 1, feature="MESH_ENABLE_MESHTEXT_WIRE")
flags = ["ESP32_PLATFORM", "ARDUINO=10819", "COMPANION_RADIO_FULL", "USE_SX1262",
"MESH_ENABLE_TX_BEACONS=0"]
self.assert_orders(flags, 1, sdk_target="CONFIG_IDF_TARGET_ESP32S3")
self.assert_orders(flags, 1, feature="MESH_ENABLE_MESHTEXT_WIRE",
sdk_target="CONFIG_IDF_TARGET_ESP32S3")
for platform in ("NRF52_PLATFORM", "ESP32_PLATFORM"):
flags = [platform, "COMPANION_RADIO_FULL=1", "MESH_ENABLE_TX_BEACONS=0"]
self.assert_orders(flags, 1)
self.assert_orders(flags, 1, feature="MESH_ENABLE_MESHTEXT_WIRE")
self.assert_orders(flags, 1, feature="MESH_ENABLE_FOREIGN_RADIO")
self.assert_orders([platform, "COMPANION_RADIO_FULL=0", "MESH_ENABLE_TX_BEACONS=0"],
0, feature="MESH_ENABLE_MESHTEXT_WIRE")
if __name__ == "__main__":