Merge remote-tracking branch 'origin/keymindCascade' into keymindCascade

This commit is contained in:
mikecarper committed 2026-10-23 10:05:38 -07:00
commit 86ef1e890c
17 files changed
+324 -21

No files matched your search

+1
View File
@@ -1115,6 +1115,7 @@ jobs:
python3 -B test/test_ssd1306_power.py -v
python3 -B test/test_oled_display_probe.py -v
python3 -B test/test_oled_controller_settings.py -v
python3 -B test/test_upstream_auto_display_merge.py -v
python3 -B test/test_companion_inbox.py
python3 -B test/test_heltec_r8_external_power.py
python3 -B test/test_bluetooth_mac_contract.py
+3 -3
View File
@@ -30,7 +30,7 @@
#include "esp32/WiFiRadioPolicy.h"
#endif
#if defined(ESP32_PLATFORM) && defined(ENABLE_OTA) && \
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID))
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID) || defined(ENABLE_WIFI_INTERFACE))
#include "esp32/WiFiOtaSeeder.h"
#endif
#ifdef WITH_MQTT_BRIDGE
@@ -52,7 +52,7 @@ static uint32_t _atoi(const char* sp) {
#ifdef ESP_PLATFORM
static bool observerBluetoothWiFiCoexistenceRequired() {
#if defined(BLE_PIN_CODE) && defined(WIFI_SSID)
#if defined(BLE_PIN_CODE) && (defined(WIFI_SSID) || defined(ENABLE_WIFI_INTERFACE))
return true;
#else
return false;
@@ -1129,7 +1129,7 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf
#endif
}
#if defined(ESP32_PLATFORM) && defined(ENABLE_OTA) && \
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID))
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID) || defined(ENABLE_WIFI_INTERFACE))
mesh::ota::WiFiOtaSeeder::appendStatus(reply, 160);
#endif
} else if (memcmp(config, "wifi.powersave", 14) == 0) {
+1 -1
View File
@@ -1,6 +1,6 @@
#include "WiFiSetupPortal.h"
#if defined(ESP32_PLATFORM) && defined(WIFI_SSID)
#if defined(ESP32_PLATFORM) && (defined(WIFI_SSID) || defined(ENABLE_WIFI_INTERFACE))
#include <WiFi.h>
#include <helpers/esp32/StaticHtml.h>
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#if defined(ESP32_PLATFORM) && defined(WIFI_SSID)
#if defined(ESP32_PLATFORM) && (defined(WIFI_SSID) || defined(ENABLE_WIFI_INTERFACE))
#include <Arduino.h>
#include <IPAddress.h>
+3 -3
View File
@@ -21,7 +21,7 @@ static_assert(sizeof(WEBCONFIG_AP_PREFIX) <= 28,
#include <esp_heap_caps.h>
#if defined(ESP32_PLATFORM) && defined(ENABLE_OTA) && \
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID))
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID) || defined(ENABLE_WIFI_INTERFACE))
#include <helpers/esp32/WiFiOtaSeeder.h>
#endif
@@ -177,7 +177,7 @@ static bool bluetoothWiFiCoexistenceRequired() {
// This server is never constructed on the exclusive profile's BLE boot.
// Any running WebConfig instance therefore has no active Bluetooth stack.
return false;
#elif defined(BLE_PIN_CODE) && defined(WIFI_SSID)
#elif defined(BLE_PIN_CODE) && (defined(WIFI_SSID) || defined(ENABLE_WIFI_INTERFACE))
return mesh::wifi::activeCompanionMode()
!= mesh::wifi::CompanionMode::WiFiOnly;
#else
@@ -307,7 +307,7 @@ static bool fixedTimeEquals(const char* a, const char* b, size_t max_len) {
static void appendOtaSeederStatus(char* reply, size_t reply_len) {
#if defined(ESP32_PLATFORM) && defined(ENABLE_OTA) && \
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID))
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID) || defined(ENABLE_WIFI_INTERFACE))
mesh::ota::WiFiOtaSeeder::appendStatus(reply, reply_len);
#else
(void)reply;
+1 -1
View File
@@ -1,7 +1,7 @@
#include "WiFiOtaSeeder.h"
#if defined(ESP32_PLATFORM) && defined(ENABLE_OTA) && \
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID))
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID) || defined(ENABLE_WIFI_INTERFACE))
#include <Arduino.h>
#include <WiFi.h>
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#if defined(ESP32_PLATFORM) && defined(ENABLE_OTA) && \
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID))
(defined(WIFI_OTA_SEEDER) || defined(WIFI_SSID) || defined(ENABLE_WIFI_INTERFACE))
#include <stddef.h>
#include <stdint.h>
+1
View File
@@ -0,0 +1 @@
#include "AutoDetectDisplay.h"
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include "SSD1306Display.h"
// Compatibility with upstream's driver name. OLED_AUTO_DETECT selects the
// shared backend, including saved controller overrides, rotation and one
// framebuffer. RAK profiles retain SSD1306Display for existing size accounting.
using AutoDetectDisplay = SSD1306Display;
+9 -9
View File
@@ -21,15 +21,15 @@ bool SH1106Display::i2c_probe(TwoWire &wire, uint8_t addr)
}
// Color scheme
ColorVal UIColor::window_bkg = SH110X_BLACK;
ColorVal UIColor::title_bkg = SH110X_BLACK;
ColorVal UIColor::title_txt = SH110X_WHITE;
ColorVal UIColor::primary_txt = SH110X_WHITE;
ColorVal UIColor::secondary_txt = SH110X_WHITE;
ColorVal UIColor::warning_txt = SH110X_WHITE;
ColorVal UIColor::popup_bkg = SH110X_BLACK;
ColorVal UIColor::popup_txt = SH110X_WHITE;
ColorVal UIColor::corp_blue = SH110X_WHITE;
__attribute__((weak)) ColorVal UIColor::window_bkg = SH110X_BLACK;
__attribute__((weak)) ColorVal UIColor::title_bkg = SH110X_BLACK;
__attribute__((weak)) ColorVal UIColor::title_txt = SH110X_WHITE;
__attribute__((weak)) ColorVal UIColor::primary_txt = SH110X_WHITE;
__attribute__((weak)) ColorVal UIColor::secondary_txt = SH110X_WHITE;
__attribute__((weak)) ColorVal UIColor::warning_txt = SH110X_WHITE;
__attribute__((weak)) ColorVal UIColor::popup_bkg = SH110X_BLACK;
__attribute__((weak)) ColorVal UIColor::popup_txt = SH110X_WHITE;
__attribute__((weak)) ColorVal UIColor::corp_blue = SH110X_WHITE;
bool SH1106Display::begin()
{
+7
View File
@@ -82,6 +82,13 @@ struct Canvas {
void reset() { std::fill(pixels.begin(),pixels.end(),0); }
};
Canvas* Canvas::active=nullptr;
// This fixture exercises direct panel geometry; the full buffered T-Deck
// renderer and its PSRAM canvas run in test_tdeck_display_cooperation.py.
class TDeckCanvas : public Canvas {
public:
int16_t getCursorX() const { return x; }
int16_t getCursorY() const { return y; }
};
struct ST7789Spi : Canvas { template<class... T> ST7789Spi(T...) {} };
struct Adafruit_ST7789 : Canvas { template<class... T> Adafruit_ST7789(T...) {} };
struct GxEPD2_150_BN { static const int WIDTH=200,HEIGHT=200; template<class... T> GxEPD2_150_BN(T...) {} };
+9 -1
View File
@@ -50,12 +50,17 @@ class NativeDisplaysTest(unittest.TestCase):
source += driver[a:b]
source += method(driver, cls, "drawChar")
else:
source += f"bool {cls}::begin() {{ return true; }}\n"
awake = "_isOn=true; " if cls == "ST7789LCDDisplay" else ""
source += f"bool {cls}::begin() {{ {awake}return true; }}\n"
for name in ("turnOn", "turnOff", "clear", "endFrame"):
source += f"void {cls}::{name}() {{}}\n"
source += f"void {cls}::startFrame(ColorVal) {{}}\n"
if cls == "ST7789LCDDisplay":
source += f"void {cls}::setFlipped(bool) {{}}\n"
if "LILYGO_TDECK" in defines:
source += f"{cls}::~{cls}() {{}}\n"
source += method(driver, cls, "isBufferedRendering")
source += f"void {cls}::finishCanvasPrimitive(int,int,int,int) {{ assert(false); }}\n"
names = ["setTextSize", "setColor", "setCursor", "print", "fillRect", "drawRect", "drawXbm", "getTextWidth"]
if cls == "ST7789LCDDisplay":
names += ["setRotationDegrees"]
@@ -66,6 +71,9 @@ class NativeDisplaysTest(unittest.TestCase):
source += r'''
int main() {
Driver d; d.begin(); d.setColor(1); d.setTextSize(1);
#ifdef LILYGO_TDECK
assert(!d.isBufferedRendering());
#endif
auto& c=*Canvas::active;
assert(d.width()==EXPECTED_WIDTH && d.height()==EXPECTED_HEIGHT);
assert(!d.useSmallMessageFont());
+2 -1
View File
@@ -440,7 +440,8 @@ class Nrf52VariantContractsTest(unittest.TestCase):
self.assertIn("bool isBridgeRunning() const override", header)
self.assertIn("if (isRs232BridgeRunning()) reply_data[8] |= 0x01", implementation)
self.assertIn("if (isBridgeRunning()) reply_data[8] |= 0x03", implementation)
self.assertIn("if (isEspNowBridgeRunning()) reply_data[8] |= 0x03", implementation)
self.assertNotIn("if (isBridgeRunning()) reply_data[8] |= 0x03", implementation)
boot_failure = implementation[
implementation.index("if (!bridge || !beginRS232Bridge())"):
implementation.index("#else", implementation.index(
+131
View File
@@ -5,12 +5,143 @@ import subprocess
import tempfile
import unittest
from test_t114_autodisplay_recipes import defines
from test_upstream_observer_recipes import Recipes
from test_replay_reset_integration import extract_braced
ROOT = Path(__file__).resolve().parents[1]
COMPANION = ROOT / "examples/companion_radio"
class DynamicWiFiBootContractTest(unittest.TestCase):
def test_new_ble_wifi_recipes_keep_dynamic_setup_and_queue_capacity(self):
recipes = Recipes()
for name in (
"heltec_v4_tft_companion_radio_ble_femon",
"heltec_v4_3_tft_companion_radio_ble_femoff",
"heltec_v4_expansionkit_tft_companion_radio_ble_femon",
"heltec_v4_3_expansionkit_tft_companion_radio_ble_femoff",
"T_Beam_S3_Supreme_SX1262_companion_radio_ble",
"T_Beam_S3_Supreme_SX1262_companion_radio_ble_ps",
):
with self.subTest(target=name):
section = "env:" + name
flags = defines(recipes.value(section, "build_flags"))
self.assertEqual(flags["ENABLE_WIFI_INTERFACE"], "1")
self.assertEqual(flags["BLE_PIN_CODE"], "123456")
self.assertEqual(flags["OFFLINE_QUEUE_SIZE"], "512")
self.assertNotIn("WIFI_SSID", flags)
self.assertNotIn("WIFI_PWD", flags)
for source in ("helpers/wifi/SerialWifiInterface.cpp",
"helpers/WiFiSetupPortal.cpp",
"helpers/esp32/WiFiOtaSeeder.cpp"):
self.assertTrue(recipes.includes(section, source), source)
def test_dynamic_wifi_helpers_link_without_companion_header_normalization(self):
portal = (ROOT / "src/helpers/WiFiSetupPortal.cpp").read_text()
seeder = (ROOT / "src/helpers/esp32/WiFiOtaSeeder.cpp").read_text()
def guard(source):
lines = iter(source.splitlines())
line = next(line for line in lines if line.startswith("#if "))
result = line
while line.endswith("\\"):
line = next(lines)
result += "\n" + line
return result
helper_source = '''
#include <helpers/WiFiSetupPortal.h>
#include <helpers/esp32/WiFiOtaSeeder.h>
#include <helpers/CLICommandUtils.h>
#include <Preferences.h>
#include <strings.h>
@PORTAL_GUARD@
@PORTAL_METHODS@
#endif
@SEEDER_GUARD@
namespace mesh { namespace ota {
static bool listener_active = true, tcp_folder_attached = false;
@SEEDER_METHODS@
} }
#endif
'''.replace("@PORTAL_GUARD@", guard(portal)).replace("@SEEDER_GUARD@", guard(seeder))
helper_source = helper_source.replace("@PORTAL_METHODS@", "\n".join(
extract_braced(portal, signature) for signature in (
"bool WiFiSetupPortal::loadStoredCredentials(",
"bool WiFiSetupPortal::isPlaceholderSSID(")))
helper_source = helper_source.replace("@SEEDER_METHODS@", "\n".join(
extract_braced(seeder, signature) for signature in (
"bool WiFiOtaSeeder::isListening(", "bool WiFiOtaSeeder::isAttached(")))
caller = '''
#include <helpers/WiFiSetupPortal.h>
#include <helpers/esp32/WiFiOtaSeeder.h>
#include <Preferences.h>
#include <cassert>
#if @PORTAL@
static void portalTest() {
assert(WiFiSetupPortal::isPlaceholderSSID(""));
assert(!WiFiSetupPortal::isPlaceholderSSID("mesh"));
char ssid[32] = {}, password[65] = {};
assert(!WiFiSetupPortal::loadStoredCredentials(ssid, sizeof(ssid), password, sizeof(password)));
nvs_values["ssid"] = {PT_STR, "mesh", 0};
nvs_values["password"] = {PT_STR, "password", 0};
assert(WiFiSetupPortal::loadStoredCredentials(ssid, sizeof(ssid), password, sizeof(password)));
}
#else
class WiFiSetupPortal {}; // disabled declarations must stay absent
#endif
#if @SEEDER@
static void seederTest() {
assert(mesh::ota::WiFiOtaSeeder::isListening());
assert(!mesh::ota::WiFiOtaSeeder::isAttached());
}
#else
namespace mesh { namespace ota { class WiFiOtaSeeder {}; } }
#endif
int main() {
#if @PORTAL@
portalTest();
#endif
#if @SEEDER@
seederTest();
#endif
}
'''
cases = (
(("ESP32_PLATFORM", "ENABLE_WIFI_INTERFACE", "ENABLE_OTA=1"), 1, 1),
(("ESP32_PLATFORM", 'WIFI_SSID="legacy"', "ENABLE_OTA=1"), 1, 1),
(("ESP32_PLATFORM", "ENABLE_OTA=1"), 0, 0),
(("ESP32_PLATFORM", "ENABLE_WIFI_INTERFACE"), 1, 0),
(("ESP32_PLATFORM", "WIFI_OTA_SEEDER", "ENABLE_OTA=1"), 0, 1),
(("NRF52_PLATFORM", "ENABLE_WIFI_INTERFACE", "ENABLE_OTA=1"), 0, 0),
)
with tempfile.TemporaryDirectory(prefix="dynamic-wifi-helpers-") as temporary:
folder = Path(temporary)
(folder / "Arduino.h").write_text("#pragma once\n#include <stddef.h>\n#include <stdint.h>\n")
(folder / "IPAddress.h").write_text(
"#pragma once\nstruct IPAddress { IPAddress(int,int,int,int) {} };\n")
(folder / "Preferences.h").write_text(
(ROOT / "test/fixtures/wifi_credentials_preferences.h").read_text())
helper = folder / "helpers.cpp"
helper.write_text(helper_source)
for index, (flags, has_portal, has_seeder) in enumerate(cases):
with self.subTest(defines=flags):
main = folder / "main.cpp"
main.write_text(caller.replace("@PORTAL@", str(has_portal))
.replace("@SEEDER@", str(has_seeder)))
binary = folder / str(index)
built = subprocess.run([
"c++", "-std=c++17", "-Wall", "-Wextra", "-Werror",
"-I", str(folder), "-I", str(ROOT / "src"),
*["-D" + flag for flag in flags], str(helper), str(main),
"-o", str(binary),
], text=True, capture_output=True, timeout=30)
self.assertEqual(built.returncode, 0, built.stderr)
ran = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
self.assertEqual(ran.returncode, 0, ran.stderr)
def test_real_include_order_normalizes_before_feature_selection(self):
mesh = (COMPANION / "MyMesh.h").read_text()
abstract = (COMPANION / "AbstractUITask.h").read_text()
+142
View File
@@ -0,0 +1,142 @@
"""Keep upstream's display name on the existing single-buffer OLED backend."""
from pathlib import Path
import tempfile
import unittest
import test_oled_display_probe as probe
from test_upstream_power_policy_merge import Recipes
DRAWING = r'''
uint8_t rotation = 0;
void setRotation(uint8_t value) { rotation = last_rotation = value; }
int16_t width() const { return rotation & 1 ? HEIGHT : WIDTH; }
int16_t height() const { return rotation & 1 ? WIDTH : HEIGHT; }
void setTextColor(uint16_t) {}
void setTextSize(int) {}
void setTextWrap(bool) {}
void cp437(bool) {}
void setCursor(int, int) {}
void print(const char*) {}
void fillRect(int, int, int, int, uint16_t) {}
void drawRect(int, int, int, int, uint16_t) {}
void drawBitmap(int, int, const uint8_t*, int, int, uint16_t) {}
void getTextBounds(const char* text, int, int, int16_t* x, int16_t* y,
uint16_t* width, uint16_t* height) {
*x = *y = 0; *width = std::strlen(text) * 6; *height = 8;
}
'''
HARNESS = r'''
#include <cassert>
#include <cstdlib>
#include <stdlib.h>
#include <type_traits>
#include <Adafruit_SSD1306.h>
static unsigned allocations = 0, active = 0;
void* test_malloc(size_t size) {
assert(size == 1024); ++allocations; ++active; return std::malloc(size);
}
void test_free(void* p) { if (p) { assert(active == 1); --active; std::free(p); } }
#define malloc test_malloc
#include <helpers/ui/AutoDetectDisplay.h>
#include <helpers/ui/SSD1306Display.cpp>
#undef malloc
static_assert(std::is_same<AutoDetectDisplay, SSD1306Display>::value,
"compatibility must preserve the single driver and framebuffer");
namespace mesh { namespace ui {
static OLEDControllerMode saved = OLEDControllerMode::Auto;
OLEDControllerMode oledControllerMode() { return saved; }
}}
using mesh::ui::OLEDControllerMode;
int main() {
for (auto mode : {OLEDControllerMode::Auto, OLEDControllerMode::SSD1306,
OLEDControllerMode::SH1106}) {
for (int status : {0, 8}) {
Wire = TwoWire{}; Wire.status[0x3C] = {status, status};
mesh::ui::saved = mode;
const unsigned before = allocations;
{
AutoDetectDisplay panel;
DisplayDriver* ui = &panel;
assert(ui->supportsRotation() && panel.begin() && ui->isOn());
assert(allocations == before + 1 && active == 1);
const bool sh = mode == OLEDControllerMode::SH1106 ||
(mode == OLEDControllerMode::Auto && status == 8);
// SSD1306 init has no payload in this vendor double. SH1106 performs
// its real init and eight page writes through the actual backend.
bool saw_page = false;
for (const auto& tx : Wire.transactions)
if (tx.data.size() == 4 && tx.data[1] == 0xB0) saw_page = true;
assert(saw_page == sh);
for (unsigned degrees : {0, 90, 180, 270}) {
assert(ui->setRotationDegrees(degrees));
assert(ui->width() == (degrees % 180 ? 64 : 128));
assert(ui->height() == (degrees % 180 ? 128 : 64));
ui->setFlipped(true); assert(last_rotation == ((degrees / 90 + 2) & 3));
ui->setFlipped(false); assert(last_rotation == degrees / 90);
}
assert(!ui->setRotationDegrees(45));
ui->setTextSize(2); assert(ui->textLineHeight() == 16);
ui->startFrame(); assert(ui->textLineHeight() == 8);
ui->turnOff(); assert(!ui->isOn()); ui->turnOn(); assert(ui->isOn());
Wire.requests.clear();
assert(panel.begin() && allocations == before + 1);
}
assert(active == 0);
}
}
Wire = TwoWire{}; Wire.present[0x3C] = false;
mesh::ui::saved = OLEDControllerMode::Auto;
AutoDetectDisplay absent;
const unsigned before = allocations;
assert(!absent.begin() && !absent.isOn());
absent.turnOn(); assert(!absent.isOn() && allocations == before);
}
'''
class UpstreamAutoDisplayMergeTests(unittest.TestCase):
def test_real_compatibility_driver_detection_settings_rotation_and_memory(self):
vendor = probe.VENDOR.replace("class Adafruit_SSD1306 {",
"inline unsigned last_rotation = 0;\nclass Adafruit_SSD1306 {")
vendor = vendor.replace("public:\n", "public:\n" + DRAWING)
vendor += "\n#define SSD1306_BLACK 0\n#define SSD1306_WHITE 1\n"
vendor += "#define SSD1306_DISPLAYON 0xAF\n#define SSD1306_DISPLAYOFF 0xAE\n"
mocks = {
"Arduino.h": probe.ARDUINO + "\ninline int digitalRead(unsigned) { return 0; }\n",
"Wire.h": probe.WIRE, "Adafruit_SSD1306.h": vendor, "Adafruit_GFX.h": "",
"helpers/IdentityStore.h": "#pragma once\nstruct FakeFS;\n#define FILESYSTEM FakeFS\n",
}
# Existing helper writes only direct children; provide the one nested
# header through a separate include directory in the same test scope.
identity = mocks.pop("helpers/IdentityStore.h")
with tempfile.TemporaryDirectory() as directory:
include = Path(directory)
(include / "helpers").mkdir()
(include / "helpers/IdentityStore.h").write_text(identity)
probe.OledDisplayProbeTests.compile_run(
self, HARNESS, ("-DOLED_AUTO_DETECT=1", "-DPIN_OLED_RESET=-1",
"-Wno-unused-parameter", "-I", str(include)), mocks)
def test_every_rak_display_role_keeps_existing_detection_and_source_budget(self):
recipes = Recipes()
count = 0
for section in recipes.config.sections():
if not section.startswith("env:RAK_4631_"):
continue
flags = recipes.value(section, "build_flags")
if "DISPLAY_CLASS=" not in flags:
continue
with self.subTest(role=section):
self.assertIn("OLED_AUTO_DETECT=1", flags)
self.assertIn("DISPLAY_CLASS=SSD1306Display", flags)
sources = recipes.value(section, "build_src_filter")
self.assertIn("helpers/ui/SSD1306Display.cpp", sources)
self.assertNotIn("helpers/ui/SH1106Display.cpp", sources)
self.assertNotIn("Adafruit SH110X", recipes.value(section, "lib_deps"))
count += 1
self.assertGreater(count, 15)
if __name__ == "__main__":
unittest.main()
+2
View File
@@ -743,12 +743,14 @@ build_flags =
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=512
-D ENABLE_WIFI_INTERFACE
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${heltec_v4_tft.build_src_filter}
+<helpers/ui/ST7789LCDDisplay.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/esp32/*.cpp>
+<helpers/wifi/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
@@ -211,11 +211,13 @@ build_flags =
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D OFFLINE_QUEUE_SIZE=512
-D ENABLE_WIFI_INTERFACE
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=8
; -D MESH_DEBUG=1
build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/wifi/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>