From 43fc4003f5bb2870e236feed124ed3cecec525ef Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Sun, 12 Jul 2026 01:57:28 +0800 Subject: [PATCH 1/2] Generalize RTTTL exclusion into MESHTASTIC_EXCLUDE_RTTTL ExternalNotificationModule already stubbed out RTTTL playback for STM32WL/portduino/ESP32C6 via a raw ARCH/CONFIG_IDF check, but the ringtone config plumbing around it (protobuf message, encode/decode tables, /prefs/ringtone.proto persistence, admin get/set-ringtone handlers) still compiled in even though it can never do anything on those platforms. Introduce MESHTASTIC_EXCLUDE_RTTTL and gate the dead ringtone plumbing behind it too. The flag is set in each architecture's *_base build_flags (stm32_base, esp32c6_base, portduino_base) rather than in the module itself - this matches how every other MESHTASTIC_EXCLUDE_* flag in the tree is set (e.g. stm32_base already sets ten of them directly, and esp32c6_base already excludes PAXCOUNTER for an analogous platform-can't-support-this reason), rather than introducing a new per-architecture C header pattern. Behavior is unchanged on all three platforms; overridable via -D like every other MESHTASTIC_EXCLUDE_* flag. Also guard the two HAS_I2S ringtone-playback call sites with !MESHTASTIC_EXCLUDE_RTTTL alongside HAS_I2S, since rtttlConfig itself is now only declared when RTTTL is not excluded. No current platform defines both HAS_I2S and MESHTASTIC_EXCLUDE_RTTTL simultaneously, so this has no effect today, but prevents a future HAS_I2S platform that also excludes RTTTL from failing to compile. Saves 368 bytes flash / 236 bytes RAM on wio-e5 with no loss to the GPIO on/off notification toggle itself, which does not depend on RTTTL. Signed-off-by: Andrew Yong Assisted-by: Claude Sonnet 5 --- src/modules/ExternalNotificationModule.cpp | 19 +++++++++++++++---- src/modules/ExternalNotificationModule.h | 6 ++++-- variants/esp32c6/esp32c6.ini | 2 ++ variants/native/portduino.ini | 1 + variants/stm32/stm32.ini | 1 + 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index a6757e04bcb..a5b6ec22786 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -57,7 +57,10 @@ bool ascending = true; #define ASCII_BELL 0x07 +#if !MESHTASTIC_EXCLUDE_RTTTL meshtastic_RTTTLConfig rtttlConfig; +static const char *rtttlConfigFile = "/prefs/ringtone.proto"; +#endif ExternalNotificationModule *externalNotificationModule; @@ -65,8 +68,6 @@ bool externalCurrentState[3] = {}; uint32_t externalTurnedOn[3] = {}; -static const char *rtttlConfigFile = "/prefs/ringtone.proto"; - int32_t ExternalNotificationModule::runOnce() { if (!moduleConfig.external_notification.enabled) { @@ -135,7 +136,7 @@ int32_t ExternalNotificationModule::runOnce() } // Play RTTTL over i2s audio interface if enabled as buzzer -#ifdef HAS_I2S +#if defined(HAS_I2S) && !MESHTASTIC_EXCLUDE_RTTTL if (moduleConfig.external_notification.use_i2s_as_buzzer) { if (audioThread->isPlaying()) { // Continue playing @@ -146,6 +147,7 @@ int32_t ExternalNotificationModule::runOnce() delay = EXT_NOTIFICATION_FAST_THREAD_MS; } #endif +#if !MESHTASTIC_EXCLUDE_RTTTL // now let the PWM buzzer play if (moduleConfig.external_notification.use_pwm && config.device.buzzer_gpio && canBuzz() && buzzerShouldAlert) { if (rtttl::isPlaying()) { @@ -157,6 +159,7 @@ int32_t ExternalNotificationModule::runOnce() // we need fast updates to play the RTTTL delay = EXT_NOTIFICATION_FAST_THREAD_MS; } +#endif return delay; } @@ -324,12 +327,14 @@ ExternalNotificationModule::ExternalNotificationModule() if (inputBroker) // put our callback in the inputObserver list inputObserver.observe(inputBroker); #endif +#if !MESHTASTIC_EXCLUDE_RTTTL if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), &meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) { memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone)); // The default ringtone is always loaded from userPrefs.jsonc strncpy(rtttlConfig.ringtone, USERPREFS_RINGTONE_RTTTL, sizeof(rtttlConfig.ringtone)); } +#endif LOG_INFO("Init External Notification Module"); @@ -459,11 +464,13 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP } else { // Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us if (moduleConfig.external_notification.use_i2s_as_buzzer) { -#ifdef HAS_I2S +#if defined(HAS_I2S) && !MESHTASTIC_EXCLUDE_RTTTL audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone)); #endif } else if (moduleConfig.external_notification.use_pwm) { +#if !MESHTASTIC_EXCLUDE_RTTTL rtttl::begin(config.device.buzzer_gpio, rtttlConfig.ringtone); +#endif } else { setExternalState(2, true); } @@ -495,6 +502,7 @@ AdminMessageHandleResult ExternalNotificationModule::handleAdminMessageForModule AdminMessageHandleResult result; switch (request->which_payload_variant) { +#if !MESHTASTIC_EXCLUDE_RTTTL case meshtastic_AdminMessage_get_ringtone_request_tag: LOG_INFO("Client getting ringtone"); this->handleGetRingtone(mp, response); @@ -506,6 +514,7 @@ AdminMessageHandleResult ExternalNotificationModule::handleAdminMessageForModule this->handleSetRingtone(request->set_canned_message_module_messages); result = AdminMessageHandleResult::HANDLED; break; +#endif default: result = AdminMessageHandleResult::NOT_HANDLED; @@ -514,6 +523,7 @@ AdminMessageHandleResult ExternalNotificationModule::handleAdminMessageForModule return result; } +#if !MESHTASTIC_EXCLUDE_RTTTL void ExternalNotificationModule::handleGetRingtone(const meshtastic_MeshPacket &req, meshtastic_AdminMessage *response) { LOG_INFO("*** handleGetRingtone"); @@ -537,6 +547,7 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg) nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig); } } +#endif int ExternalNotificationModule::handleInputEvent(const InputEvent *event) { diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 03eac036bb6..352959ef043 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -23,10 +23,10 @@ extern AmbientLightingThread *ambientLightingThread; #endif #endif -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6) +#if !MESHTASTIC_EXCLUDE_RTTTL #include #else -// Noop class for portduino. +// Noop class for portduino/STM32WL/ESP32C6 - none can drive PWM RTTTL playback. class rtttl { public: @@ -73,8 +73,10 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency: void stopNow(); +#if !MESHTASTIC_EXCLUDE_RTTTL void handleGetRingtone(const meshtastic_MeshPacket &req, meshtastic_AdminMessage *response); void handleSetRingtone(const char *from_msg); +#endif protected: /** Called to handle a particular incoming message diff --git a/variants/esp32c6/esp32c6.ini b/variants/esp32c6/esp32c6.ini index ae91e6e81fd..4c75fa90b9b 100644 --- a/variants/esp32c6/esp32c6.ini +++ b/variants/esp32c6/esp32c6.ini @@ -9,6 +9,8 @@ build_flags = ; Exclude Paxcounter, it uses 'esp_vhci_host_send_packet' whch is not available on ESP32-C6 ; https://github.com/espressif/arduino-esp32/issues/11716 -DMESHTASTIC_EXCLUDE_PAXCOUNTER + ; No PWM/RTTTL ringtone playback support on this platform. + -DMESHTASTIC_EXCLUDE_RTTTL build_src_filter = ${esp32_common.build_src_filter} diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini index 98cbaf6c3d7..20e0e0d7c61 100644 --- a/variants/native/portduino.ini +++ b/variants/native/portduino.ini @@ -49,6 +49,7 @@ build_flags_common = -fPIC -Isrc/platform/portduino -DRADIOLIB_EEPROM_UNSUPPORTED + -DMESHTASTIC_EXCLUDE_RTTTL ; No PWM/RTTTL ringtone playback support on this platform. -lpthread -lyaml-cpp -ljsoncpp diff --git a/variants/stm32/stm32.ini b/variants/stm32/stm32.ini index 5726fd369a2..4f3ed8cbb97 100644 --- a/variants/stm32/stm32.ini +++ b/variants/stm32/stm32.ini @@ -26,6 +26,7 @@ build_flags = -DMESHTASTIC_EXCLUDE_WIFI=1 -DMESHTASTIC_EXCLUDE_TZ=1 ; Exclude TZ to save some flash space. -DMESHTASTIC_EXCLUDE_XEDDSA=1 ; The Ed25519 signing code does not fit in the 256KB flash. Packets are sent unsigned, like pre-XEdDSA firmware. + -DMESHTASTIC_EXCLUDE_RTTTL=1 ; No PWM/RTTTL ringtone playback support on this platform. -DSERIAL_RX_BUFFER_SIZE=256 ; For GPS - the default of 64 is too small. -DHAS_SCREEN=0 ; Always disable screen for STM32, it is not supported. ;-DPIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF ; Enable this if enabling debugg logging. It is REQUIRED for at least traceroute debug prints - without it the length returned by printf ends up uninitialized. From aff3a3b26c59b858001848b4cc7e89cb72689570 Mon Sep 17 00:00:00 2001 From: Andrew Yong Date: Sun, 12 Jul 2026 01:59:07 +0800 Subject: [PATCH 2/2] Skip unused InputBroker observer in ExternalNotificationModule The inputObserver CallbackObserver member was declared unconditionally, even though its only use site was already gated behind MESHTASTIC_EXCLUDE_INPUTBROKER (set for all of stm32 in stm32.ini). Because it's a non-trivial member, the compiler still generated its constructor/destructor as part of ExternalNotificationModule's own lifecycle even when InputBroker is compiled out entirely. Gate the member and its only consumer, handleInputEvent(), behind the same flag as their use site, and match the codebase's dominant !MESHTASTIC_EXCLUDE_X style (used ~330 times) rather than !defined(MESHTASTIC_EXCLUDE_X) (used ~20 times) while touching this flag's other call site. Saves an additional 288 bytes flash on wio-e5, no RAM change, no functional impact since InputBroker was already unused on this platform. Signed-off-by: Andrew Yong Assisted-by: Claude Sonnet 5 --- src/modules/ExternalNotificationModule.cpp | 4 +++- src/modules/ExternalNotificationModule.h | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index a5b6ec22786..0bebb64aadb 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -323,7 +323,7 @@ ExternalNotificationModule::ExternalNotificationModule() // moduleConfig.external_notification.alert_message_buzzer = true; if (moduleConfig.external_notification.enabled) { -#if !defined(MESHTASTIC_EXCLUDE_INPUTBROKER) +#if !MESHTASTIC_EXCLUDE_INPUTBROKER if (inputBroker) // put our callback in the inputObserver list inputObserver.observe(inputBroker); #endif @@ -549,6 +549,7 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg) } #endif +#if !MESHTASTIC_EXCLUDE_INPUTBROKER int ExternalNotificationModule::handleInputEvent(const InputEvent *event) { if (nagCycleCutoff != UINT32_MAX) { @@ -557,3 +558,4 @@ int ExternalNotificationModule::handleInputEvent(const InputEvent *event) } return 0; } +#endif diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 352959ef043..ea4759ad59f 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -47,8 +47,10 @@ class rtttl */ class ExternalNotificationModule : public SinglePortModule, private concurrency::OSThread { +#if !MESHTASTIC_EXCLUDE_INPUTBROKER CallbackObserver inputObserver = CallbackObserver(this, &ExternalNotificationModule::handleInputEvent); +#endif uint32_t output = 0; #ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN @@ -58,7 +60,9 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency: public: ExternalNotificationModule(); +#if !MESHTASTIC_EXCLUDE_INPUTBROKER int handleInputEvent(const InputEvent *arg); +#endif uint32_t nagCycleCutoff = 1;