Add T-Beam BPF - 144-148 Mhz LoRa @ ~37 dBm (5 Watts)#10558
Conversation
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This is an appropriate default in the USA but not the EU. The slot override really should follow the region itself, not the regionprofile.
Initial work to add T-Beam BPF (144-148 Mhz LoRa)
Still need to flesh out the default channels
Use 15.6 instead
May cause regressions!!
Firmware Size Report22 targets | vs
Show 17 more target(s)
Updated for 6a5fe81 |
|
This is finally ready to go 😊 |
There was a problem hiding this comment.
Pull request overview
This PR adds support for the LilyGo T-Beam BPF (ESP32-S3 + SX1278) 2‑meter (144–148 MHz) high-power LoRa variant, integrating it into the Meshtastic firmware’s build/board system and platform-specific initialization paths.
Changes:
- Adds a new ESP32-S3 variant (
t-beam-bpf) with board definitions, pins, and PlatformIO environment. - Wires the new hardware model into ESP32 hardware detection and AXP2101 rail enablement (GNSS + SD).
- Adds RF95 power-rail enable/disable handling and constrains the region picker UI to 2m-only regions on ham-band-only hardware.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| variants/esp32s3/t-beam-bpf/variant.h | New variant pinout + radio/PA configuration + ham-2m-only marker define. |
| variants/esp32s3/t-beam-bpf/platformio.ini | New t-beam-bpf PlatformIO environment and build flags. |
| variants/esp32s3/t-beam-bpf/pins_arduino.h | Arduino pin mappings (I2C/SPI/UART/SD CS) for the new board variant. |
| src/Power.cpp | Adds AXP2101 rail enablement for GNSS + SD on T-Beam BPF. |
| src/platform/esp32/architecture.h | Adds HW model selection for T_BEAM_BPF. |
| src/mesh/RF95Interface.cpp | Enables/disables a variant-provided RF95 power rail around init/sleep. |
| src/mesh/NodeDB.cpp | Sets ham-only hardware default licensed mode behavior. |
| src/graphics/draw/MenuHandler.cpp | Limits region picker options to ITU*_2M regions on ham-2m-only hardware. |
| boards/t-beam-bpf.json | Adds PlatformIO board definition for the new hardware target. |
| #ifdef HAS_HAM_2M_ONLY | ||
| // Ham-band-only hardware defaults to licensed operation. The user can still flip this off later | ||
| // (e.g. a commercial operator on an adjacent allocation who wants to keep encryption on) — we | ||
| // only set the default here, not on every boot. | ||
| owner.is_licensed = true; | ||
| #endif |
| // Ham-band-only hardware defaults to licensed operation. The user can still flip this off later | ||
| // (e.g. a commercial operator on an adjacent allocation who wants to keep encryption on) — we | ||
| // only set the default here, not on every boot. | ||
| owner.is_licensed = true; |
| // T-Beam BPF rail map (per schematic LilyGo_TBeam_BPF r2025-05-08): | ||
| // DCDC1 -> ESP32 + OLED 3V3 (always on, protected) | ||
| // ALDO2 -> MicroSD 3V3 (OFF at reset, must enable) | ||
| // ALDO4 -> L76K GNSS 3V3 (OFF at reset, must enable) | ||
| // ALDO1/3, BLDO1/2, DLDO1 -> user headers / unused at boot, leave at reset defaults. |
⚡ Try this PR in the Web FlasherWarning This is an automated, unreviewed CI test build. Back up your device configuration Supported boards built by this PR (25)
Build artifacts expire on 2026-07-29. Updated for |
📝 WalkthroughWalkthroughAdds complete firmware support for the LilyGo T-Beam-BPF (ESP32-S3) board: board JSON, PlatformIO environment, Arduino pin header, and variant.h with all GPIO/radio/PMU macros. Wires the new ChangesT-Beam BPF Board Support
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@boards/t-beam-bpf.json`:
- Around line 8-15: The board definition only exports LILYGO_TBEAM_BPF, while
the rest of the build logic keys off T_BEAM_BPF in platformio.ini and
architecture.h. Update the t-beam-bpf board JSON extra_flags so the canonical
T_BEAM_BPF macro is defined there as well, ensuring builds that select board =
t-beam-bpf take the T-Beam-BPF-specific code path instead of falling back to
PRIVATE_HW.
In `@src/graphics/draw/MenuHandler.cpp`:
- Around line 227-236: The HAM-only region picker in LoraRegionPicker is
blocking all menu-driven licensed-region choices when owner.is_licensed is
false, which can leave the user unable to re-enable licensed mode. Update the
selection flow so HAM region picks are handled before
RadioInterface::checkConfigRegion() rejects them, or explicitly bypass the
licensed-only rejection for the region options shown under HAS_HAM_2M_ONLY. Keep
the existing HamModeConfirm path intact, and ensure the fix applies to the
region selection handling in MenuHandler::LoraRegionPicker and the related menu
branch referenced in the diff.
In `@src/mesh/RF95Interface.cpp`:
- Around line 116-120: In RF95Interface initialization, the RF power rail is
enabled before calling lora->begin() but is not turned off on failure paths.
Update the init flow so every early return and the final false return after a
failed begin() disables RF95_POWER_EN before exiting, keeping the external RF
supply from staying powered on a missing or bad radio.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 9dc0075c-4a9e-4840-b335-d63fe04ccf9e
📒 Files selected for processing (9)
boards/t-beam-bpf.jsonsrc/Power.cppsrc/graphics/draw/MenuHandler.cppsrc/mesh/NodeDB.cppsrc/mesh/RF95Interface.cppsrc/platform/esp32/architecture.hvariants/esp32s3/t-beam-bpf/pins_arduino.hvariants/esp32s3/t-beam-bpf/platformio.inivariants/esp32s3/t-beam-bpf/variant.h
| "extra_flags": [ | ||
| "-DBOARD_HAS_PSRAM", | ||
| "-DLILYGO_TBEAM_BPF", | ||
| "-DARDUINO_USB_CDC_ON_BOOT=1", | ||
| "-DARDUINO_USB_MODE=1", | ||
| "-DARDUINO_RUNNING_CORE=1", | ||
| "-DARDUINO_EVENT_RUNNING_CORE=1" | ||
| ], |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Export the canonical board macro from the board definition.
Everything else in this bring-up keys off T_BEAM_BPF (variants/esp32s3/t-beam-bpf/platformio.ini, Line 23 and src/platform/esp32/architecture.h, Lines 201-202), but this board JSON only defines LILYGO_TBEAM_BPF. Any build that selects board = t-beam-bpf without this exact env overlay will fall through to PRIVATE_HW and skip the T-Beam-BPF-specific paths.
Suggested fix
"extra_flags": [
"-DBOARD_HAS_PSRAM",
+ "-DT_BEAM_BPF",
"-DLILYGO_TBEAM_BPF",
"-DARDUINO_USB_CDC_ON_BOOT=1",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "extra_flags": [ | |
| "-DBOARD_HAS_PSRAM", | |
| "-DLILYGO_TBEAM_BPF", | |
| "-DARDUINO_USB_CDC_ON_BOOT=1", | |
| "-DARDUINO_USB_MODE=1", | |
| "-DARDUINO_RUNNING_CORE=1", | |
| "-DARDUINO_EVENT_RUNNING_CORE=1" | |
| ], | |
| "extra_flags": [ | |
| "-DBOARD_HAS_PSRAM", | |
| "-DT_BEAM_BPF", | |
| "-DLILYGO_TBEAM_BPF", | |
| "-DARDUINO_USB_CDC_ON_BOOT=1", | |
| "-DARDUINO_USB_MODE=1", | |
| "-DARDUINO_RUNNING_CORE=1", | |
| "-DARDUINO_EVENT_RUNNING_CORE=1" | |
| ], |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@boards/t-beam-bpf.json` around lines 8 - 15, The board definition only
exports LILYGO_TBEAM_BPF, while the rest of the build logic keys off T_BEAM_BPF
in platformio.ini and architecture.h. Update the t-beam-bpf board JSON
extra_flags so the canonical T_BEAM_BPF macro is defined there as well, ensuring
builds that select board = t-beam-bpf take the T-Beam-BPF-specific code path
instead of falling back to PRIVATE_HW.
| #ifdef HAS_HAM_2M_ONLY | ||
| // Hardware is restricted to the amateur 2m band — offer only the 2m regions | ||
| // so the user cannot pick a sub-GHz region the RF path cannot emit or receive. | ||
| static const LoraRegionOption regionOptions[] = { | ||
| {"Back", OptionsAction::Back}, | ||
| {"ITU1_2M (144-146)", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ITU1_2M}, | ||
| {"ITU2_2M (144-148)", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ITU2_2M}, | ||
| {"ITU3_2M (144-148)", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ITU3_2M}, | ||
| }; | ||
| #else |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
HAM-only picker can lock the user out of re-enabling licensed mode.
After this change, every visible choice is licensed-only, but LoraRegionPicker() still calls RadioInterface::checkConfigRegion() before it queues HamModeConfirm. If owner.is_licensed is ever false, all three selections are rejected and there is no on-device path back into licensed mode. Prompt first for HAM regions, or bypass the licensed-only rejection for menu-driven HAM selections.
Also applies to: 273-273
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/graphics/draw/MenuHandler.cpp` around lines 227 - 236, The HAM-only
region picker in LoraRegionPicker is blocking all menu-driven licensed-region
choices when owner.is_licensed is false, which can leave the user unable to
re-enable licensed mode. Update the selection flow so HAM region picks are
handled before RadioInterface::checkConfigRegion() rejects them, or explicitly
bypass the licensed-only rejection for the region options shown under
HAS_HAM_2M_ONLY. Keep the existing HamModeConfirm path intact, and ensure the
fix applies to the region selection handling in MenuHandler::LoraRegionPicker
and the related menu branch referenced in the diff.
| #ifdef RF95_POWER_EN | ||
| pinMode(RF95_POWER_EN, OUTPUT); | ||
| digitalWrite(RF95_POWER_EN, HIGH); | ||
| #endif | ||
|
|
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Power the RF rail back down on init failures.
These lines turn the external RF supply on before lora->begin(), but the failure exit at Line 186 and the final false return path leave it high. On a missing/bad radio that keeps the PA chain powered indefinitely.
Suggested fix
bool RF95Interface::init()
{
`#ifdef` RF95_POWER_EN
pinMode(RF95_POWER_EN, OUTPUT);
digitalWrite(RF95_POWER_EN, HIGH);
`#endif`
+ auto powerOffRf95 = []() {
+#ifdef RF95_POWER_EN
+ digitalWrite(RF95_POWER_EN, LOW);
+#endif
+ };
RadioLibInterface::init();
@@
LOG_INFO("RF95 init result %d", res);
- if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
+ if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) {
+ powerOffRf95();
return false;
+ }
@@
- return res == RADIOLIB_ERR_NONE;
+ if (res != RADIOLIB_ERR_NONE)
+ powerOffRf95();
+ return res == RADIOLIB_ERR_NONE;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #ifdef RF95_POWER_EN | |
| pinMode(RF95_POWER_EN, OUTPUT); | |
| digitalWrite(RF95_POWER_EN, HIGH); | |
| #endif | |
| bool RF95Interface::init() | |
| { | |
| `#ifdef` RF95_POWER_EN | |
| pinMode(RF95_POWER_EN, OUTPUT); | |
| digitalWrite(RF95_POWER_EN, HIGH); | |
| `#endif` | |
| auto powerOffRf95 = []() { | |
| `#ifdef` RF95_POWER_EN | |
| digitalWrite(RF95_POWER_EN, LOW); | |
| `#endif` | |
| }; | |
| RadioLibInterface::init(); | |
| @@ | |
| LOG_INFO("RF95 init result %d", res); | |
| if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED) { | |
| powerOffRf95(); | |
| return false; | |
| } | |
| @@ | |
| if (res != RADIOLIB_ERR_NONE) | |
| powerOffRf95(); | |
| return res == RADIOLIB_ERR_NONE; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/mesh/RF95Interface.cpp` around lines 116 - 120, In RF95Interface
initialization, the RF power rail is enabled before calling lora->begin() but is
not turned off on failure paths. Update the init flow so every early return and
the final false return after a failed begin() disables RF95_POWER_EN before
exiting, keeping the external RF supply from staying powered on a missing or bad
radio.
Add LilyGo T-Beam BPF 144-148Mhz LoRa node 🍖
This node contains a +27 dBm PA! 🔥
Observed RF output power measurements:
Requires:
TINY_FASTUpdate protobufs and classes #10614Protobuf regen to include meshtastic/protobufs@2e284d9This is a continuation of original work located at:
Summary by CodeRabbit