Detect SX126x and SX127x in universal T-Beam and T3 S3 builds

This commit is contained in:
mikecarper committed 2026-10-31 01:23:49 -07:00
1 parent 14b6a0aed1
commit 23f94adf3d
16 files changed
+1135 -13

No files matched your search

+17 -9
View File
@@ -506,6 +506,8 @@ for section, options in data:
Ebyte_EoRa-S3_companion_radio_ble
Tbeam_SX1262_companion_radio_ble
Tbeam_SX1276_companion_radio_ble
Tbeam_companion_radio_ble
LilyGo_T3S3_companion_radio_ble
T_Beam_S3_Supreme_SX1262_companion_radio_ble
heltec_v4_expansionkit_tft_companion_radio_ble_femon
)
@@ -2482,8 +2484,8 @@ get_unified_full_infrastructure_target() {
# T-Beam keeps the observer as its ordinary release choice. TLora retains
# both identities: its UART cannot fit alongside MQTT and ESP-NOW.
case "${mqtt_base,,}" in
tbeam_sx1262_repeater|tbeam_sx1276_repeater|\
tbeam_sx1262_room_server|tbeam_sx1276_room_server|\
tbeam_repeater|tbeam_sx1262_repeater|tbeam_sx1276_repeater|\
tbeam_room_server|tbeam_sx1262_room_server|tbeam_sx1276_room_server|\
lilygo_tlora_v2_1_1_6_repeater) return 1 ;;
esac
@@ -3100,12 +3102,13 @@ requires_dram_limited_neighbors() {
heltec_v2_repeater_lora_ota_no_external_sensors|\
meshadventurer_sx1262_repeater_lora_ota_no_external_sensors|\
meshadventurer_sx1268_repeater_lora_ota_no_external_sensors|\
tbeam_repeater_lora_ota_no_external_sensors|\
tbeam_sx1262_repeater_lora_ota_no_external_sensors|\
tbeam_sx1276_repeater_lora_ota_no_external_sensors)
return 0 ;;
generic_e22_sx1262_repeater|generic_e22_sx1268_repeater|\
heltec_v2_repeater|meshadventurer_sx1262_repeater|\
meshadventurer_sx1268_repeater|tbeam_sx1262_repeater|\
meshadventurer_sx1268_repeater|tbeam_repeater|tbeam_sx1262_repeater|\
tbeam_sx1276_repeater)
if [ "$ESP32_FULL_BUILD" = "1" ]; then return 0; fi ;;
esac
@@ -3123,6 +3126,7 @@ requires_dram_limited_neighbors() {
|lilygo_tlora_v2_1_1_6_repeater_bridge_rs232 \
|meshadventurer_sx1262_repeater_bridge_espnow \
|meshadventurer_sx1268_repeater_bridge_espnow \
|tbeam_repeater_bridge_espnow \
|tbeam_sx1262_repeater_bridge_espnow \
|tbeam_sx1276_repeater_bridge_espnow)
return 0
@@ -3132,7 +3136,7 @@ requires_dram_limited_neighbors() {
case "${1,,}" in
lilygo_tlora_v2_1_1_6_repeater_observer_mqtt_)
return 0 ;;
tbeam_sx1262_repeater_observer_mqtt|tbeam_sx1262_room_server_observer_mqtt|tbeam_sx1276_repeater_observer_mqtt|tbeam_sx1276_room_server_observer_mqtt|rak_3x72_repeater|tiny_relay_repeater|wio-e5-mini_repeater|wio-e5-repeater_bridge_rs232|wio-e5_repeater) return 0 ;;
tbeam_repeater_observer_mqtt|tbeam_room_server_observer_mqtt|tbeam_sx1262_repeater_observer_mqtt|tbeam_sx1262_room_server_observer_mqtt|tbeam_sx1276_repeater_observer_mqtt|tbeam_sx1276_room_server_observer_mqtt|rak_3x72_repeater|tiny_relay_repeater|wio-e5-mini_repeater|wio-e5-repeater_bridge_rs232|wio-e5_repeater) return 0 ;;
*) return 1 ;;
esac
}
@@ -3438,6 +3442,7 @@ is_esp32_partition_migration_full_target() {
ebyte_eora-s3_repeater|ebyte_eora-s3_room_server|\
generic_e22_sx1262_repeater|generic_e22_sx1268_repeater|\
heltec_ct62_repeater|heltec_ct62_sensor|\
lilygo_t3s3_repeater|lilygo_t3s3_room_server|\
lilygo_t3s3_sx1262_repeater|lilygo_t3s3_sx1262_room_server|\
lilygo_t3s3_sx1276_repeater|lilygo_t3s3_sx1276_room_server|\
station_g2_repeater|station_g2_logging_repeater|station_g2_room_server|\
@@ -3846,6 +3851,8 @@ apply_esp32_constrained_companion_size_profile() {
station_g2_companion_radio_ble \
|thinknode_m2_companion_radio_ble \
|thinknode_m2_companion_radio_wifi \
|lilygo_t3s3_companion_radio_ble \
|lilygo_t3s3_companion_radio_ble_ps \
|lilygo_t3s3_sx1262_companion_radio_ble \
|lilygo_t3s3_sx1262_companion_radio_ble_ps \
|lilygo_t3s3_sx1276_companion_radio_ble \
@@ -3872,8 +3879,8 @@ apply_esp32_iram_advert_storage_profile() {
lilygo_tlora_v2_1_1_6_repeater_observer_mqtt|lilygo_tlora_v2_1_1_6_repeater_observer_mqtt_|\
lilygo_tlora_v2_1_1_6_room_server|lilygo_tlora_v2_1_1_6_room_server_|\
lilygo_tlora_v2_1_1_6_room_server_observer_mqtt|lilygo_tlora_v2_1_1_6_room_server_observer_mqtt_|\
tbeam_sx1262_repeater_observer_mqtt|tbeam_sx1276_repeater_observer_mqtt|\
tbeam_sx1262_room_server_observer_mqtt|tbeam_sx1276_room_server_observer_mqtt)
tbeam_repeater_observer_mqtt|tbeam_sx1262_repeater_observer_mqtt|tbeam_sx1276_repeater_observer_mqtt|\
tbeam_room_server_observer_mqtt|tbeam_sx1262_room_server_observer_mqtt|tbeam_sx1276_room_server_observer_mqtt)
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFLOOD_ADVERT_IRAM_WORD_STORAGE=1"
;;
esac
@@ -3909,13 +3916,13 @@ apply_esp32_full_size_profile() {
case "${env_name,,}" in
lilygo_tlora_v2_1_1_6_room_server|lilygo_tlora_v2_1_1_6_room_server_|\
lilygo_tlora_v2_1_1_6_room_server_observer_mqtt|lilygo_tlora_v2_1_1_6_room_server_observer_mqtt_|\
tbeam_sx1262_room_server_observer_mqtt|tbeam_sx1276_room_server_observer_mqtt)
tbeam_room_server_observer_mqtt|tbeam_sx1262_room_server_observer_mqtt|tbeam_sx1276_room_server_observer_mqtt)
# Preserve all 32 Room posts in genuine byte-accessible RTC SLOW memory.
# The bounded proof implementation serves the same maximum 4096 leaves
# without retaining the legacy 16 KiB leaf cache alongside Room services.
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DROOM_POSTS_RTC_SLOW_STORAGE=1 -DOTA_BOUNDED_SOURCE_PROOFS=1"
;;
tbeam_sx1262_repeater_observer_mqtt|tbeam_sx1276_repeater_observer_mqtt)
tbeam_repeater_observer_mqtt|tbeam_sx1262_repeater_observer_mqtt|tbeam_sx1276_repeater_observer_mqtt)
# MQTT/TLS plus OTA left only 3.3 KiB of static-region headroom with
# 63 rules. Retain the complete rule engine with a 31-entry table.
append_platformio_build_unflags "-DFLOOD_PACKET_FILTER_SLOTS=63"
@@ -3929,6 +3936,7 @@ apply_esp32_full_size_profile() {
lilygo_tlora_v2_1_1_6_repeater_bridge_espnow|\
meshadventurer_sx1262_repeater_bridge_espnow|\
meshadventurer_sx1268_repeater_bridge_espnow|\
tbeam_repeater_bridge_espnow|\
tbeam_sx1262_repeater_bridge_espnow|\
tbeam_sx1276_repeater_bridge_espnow)
if [ "${PACKET_LOGGING_OVERRIDE,,}" = "on" ]; then
@@ -7132,7 +7140,7 @@ get_ordinary_full_priority() {
case "${base,,}" in
*_observer_mqtt) echo 20 ;;
*_bridge_espnow) echo 10 ;;
tbeam_sx1262_repeater|tbeam_sx1276_repeater) echo 0 ;;
tbeam_repeater|tbeam_sx1262_repeater|tbeam_sx1276_repeater) echo 0 ;;
*)
# G2's deployed Full identity is the observer. Other audited plain
# targets keep their own identity, with observer sources where needed.
+1
View File
@@ -578,6 +578,7 @@ def select_ordinary_full_records(records: list[dict]) -> list[dict]:
"station_g2_repeater", "station_g2_room_server",
"tbeam_sx1262_repeater", "tbeam_sx1276_repeater",
"tbeam_sx1262_room_server", "tbeam_sx1276_room_server",
"tbeam_repeater", "tbeam_room_server",
}
# Capacity exceptions may still need a dedicated bridge. Old plain
# Meshadventurer images without the linked driver must not supersede it.
+12
View File
@@ -6,6 +6,18 @@ import sys
from optional_display_profiles import resolved_defines, spec_for_defines
SPECS = (
dict(key="autoLoRaRadio", capability="radio.autodetect.sx126x_sx127x",
marker="meshcore.capability.radio.autodetect.sx126x_sx127x.v1",
platform="ESP32_PLATFORM", board="ttgo-t-beam",
target=r"Tbeam_(?:repeater(?:_bridge_espnow)?|room_server|kiss_modem|companion_radio_(?:ble|usb|wifi|full)(?:_ps)?)(?:_observer_mqtt)?",
defines={"MESH_RADIO_AUTO_FAMILY": "1", "RADIO_CLASS": "UniversalLoRaRadio",
"WRAPPER_CLASS": "UniversalLoRaRadio"}),
dict(key="autoLoRaRadio", capability="radio.autodetect.sx126x_sx127x",
marker="meshcore.capability.radio.autodetect.sx126x_sx127x.v1",
platform="ESP32_PLATFORM", board="t3_s3_v1_x",
target=r"LilyGo_T3S3_(?:repeater(?:_bridge_espnow)?|room_server|kiss_modem|terminal_chat|companion_radio_(?:ble|usb|wifi|full)(?:_ps)?)(?:_observer_mqtt)?",
defines={"MESH_RADIO_AUTO_FAMILY": "1", "RADIO_CLASS": "UniversalLoRaRadio",
"WRAPPER_CLASS": "UniversalLoRaRadio"}),
dict(key="unifiedV4Display", capability="display.heltec_v4.unified",
marker="meshcore.capability.display.heltec_v4.unified.v1",
platform="ESP32_PLATFORM", board="heltec_v4",
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include "LoRaRadioFamily.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
namespace mesh {
// SX1276/78 and RFM9x share this register layout. SX1272 (0x22) does not.
inline bool isSX1276Version(uint8_t version) {
return version == 0x12 || version == 0x13 || version == 0x11;
}
// IO supplies reset(), readSX127Version(), ready(), readSX126Version(),
// now(), and pause(). Identification never starts RX, CAD or transmission.
template <typename IO>
LoRaRadioFamily probeLoRaChip(IO& io, uint32_t timeout_ms = 250) {
io.reset();
const uint8_t version = io.readSX127Version();
if (isSX1276Version(version) && io.readSX127Version() == version)
return LoRaRadioFamily::SX127x;
// Discard the unrecognized command/status left on an SX126x by the first
// read. A reset enters RC standby, so no speculative mode-setting is needed.
io.reset();
const uint32_t started = io.now();
while (uint32_t(io.now() - started) < timeout_ms) {
if (io.ready()) {
char signature[6] = {};
char confirm[6] = {};
// SX1262 intentionally reports the SX1261 family signature in RadioLib.
if (io.readSX126Version(signature, sizeof(signature))
&& memcmp(signature, "SX1261", sizeof(signature)) == 0
&& io.ready()
&& io.readSX126Version(confirm, sizeof(confirm))
&& memcmp(signature, confirm, sizeof(signature)) == 0)
return LoRaRadioFamily::SX126x;
}
io.pause(1);
}
return LoRaRadioFamily::None;
}
} // namespace mesh
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <stdint.h>
namespace mesh {
enum class LoRaRadioFamily : uint8_t { None, SX126x, SX127x };
}
+18 -3
View File
@@ -7,6 +7,10 @@
#include <nvs.h>
//#include <RadioLib.h>
#if defined(MESH_RADIO_AUTO_FAMILY) && MESH_RADIO_AUTO_FAMILY
extern bool prepareUniversalLoRaRadio();
#endif
uint32_t deviceOnline = 0x00;
#if !defined(PORTABLE_MQTT_OBSERVER) || defined(TBEAM_SUPREME_SX1262)
@@ -42,17 +46,28 @@ void TBeamBoard::begin() {
digitalWrite(P_LORA_TX_LED, HIGH); //inverted pin for SX1276 - HIGH for off
#endif
//radiotype_detect();
// The radio rail must be powered before probing. Selection also supplies
// the actual wake IRQ before regulator/tuning setup runs in the role.
#if defined(MESH_RADIO_AUTO_FAMILY) && MESH_RADIO_AUTO_FAMILY
prepareUniversalLoRaRadio();
#endif
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {
uint64_t wakeup_source = esp_sleep_get_ext1_wakeup_status();
if (wakeup_source & (1ULL << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
#if defined(MESH_RADIO_AUTO_FAMILY) && MESH_RADIO_AUTO_FAMILY
const uint32_t radio_irq = getIRQGpio();
#else
const uint32_t radio_irq = P_LORA_DIO_1;
#endif
if (radio_irq != static_cast<uint32_t>(-1)
&& (wakeup_source & (1ULL << radio_irq))) {
startup_reason = BD_STARTUP_RX_PACKET;
}
mesh::esp32::releaseRtcGpioIfSupported((gpio_num_t)P_LORA_NSS);
mesh::esp32::deinitRtcGpioIfSupported((gpio_num_t)P_LORA_DIO_1);
if (radio_irq != static_cast<uint32_t>(-1))
mesh::esp32::deinitRtcGpioIfSupported((gpio_num_t)radio_irq);
}
}
+78
View File
@@ -0,0 +1,78 @@
#pragma once
#include <Arduino.h>
#include <SPI.h>
#include <RadioLib.h>
#include <helpers/LoRaChipProbe.h>
#include <helpers/UsbLogging.h>
namespace mesh {
struct LoRaChipProbePins {
uint32_t nss;
uint32_t reset;
uint32_t busy;
};
class ESP32LoRaChipProbeIO {
SPIClass& spi_;
const LoRaChipProbePins& pins_;
public:
ESP32LoRaChipProbeIO(SPIClass& spi, const LoRaChipProbePins& pins)
: spi_(spi), pins_(pins) {}
uint32_t now() const { return millis(); }
void pause(uint32_t ms) {
#if MESH_USB_CONSOLE_COOPERATIVE
serviceUsbLoggingPort();
serviceUsbTerminalPort();
#endif
delay(ms);
}
void reset() {
digitalWrite(pins_.nss, HIGH);
pinMode(pins_.nss, OUTPUT);
pinMode(pins_.busy, INPUT_PULLDOWN);
digitalWrite(pins_.reset, HIGH);
pinMode(pins_.reset, OUTPUT);
digitalWrite(pins_.reset, LOW);
pause(1);
digitalWrite(pins_.reset, HIGH);
pause(10);
}
bool ready() const { return digitalRead(pins_.busy) == LOW; }
uint8_t readSX127Version() {
spi_.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
digitalWrite(pins_.nss, LOW);
spi_.transfer(0x42); // SX127x RegVersion; read, not write.
const uint8_t version = spi_.transfer(0);
digitalWrite(pins_.nss, HIGH);
spi_.endTransaction();
return version;
}
bool readSX126Version(char* data, size_t length) {
if (!ready()) return false;
spi_.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
digitalWrite(pins_.nss, LOW);
spi_.transfer(RADIOLIB_SX126X_CMD_READ_REGISTER);
spi_.transfer(RADIOLIB_SX126X_REG_VERSION_STRING >> 8);
spi_.transfer(RADIOLIB_SX126X_REG_VERSION_STRING & 0xff);
spi_.transfer(0); // command status precedes register contents.
for (size_t i = 0; i < length; ++i) data[i] = spi_.transfer(0);
digitalWrite(pins_.nss, HIGH);
spi_.endTransaction();
delayMicroseconds(1);
return true;
}
};
// Caller powers the radio and starts this board's SPI pins first. Leave NSS
// deselected; the chosen chip's normal initialization performs its own reset.
inline LoRaRadioFamily probeUniversalLoRaChip(SPIClass& spi,
const LoRaChipProbePins& pins) {
ESP32LoRaChipProbeIO io(spi, pins);
const auto family = probeLoRaChip(io);
if (family == LoRaRadioFamily::SX127x) pinMode(pins.busy, INPUT);
return family;
}
} // namespace mesh
+5 -1
View File
@@ -259,7 +259,11 @@ protected:
// The T096 SX1262 bench test found RSSI valid and stable by 7 ms after RX
// entry, including the 500 kHz/SF5 worst-case profile. Retain the
// conservative 20 ms path for the other radio families.
#if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LLCC68)
#if defined(MESH_RADIO_AUTO_FAMILY) && MESH_RADIO_AUTO_FAMILY
// A universal image shares this implementation across both families. Keep
// SX127x's settling margin even in TUs that also include the SX126x wrapper.
static constexpr uint32_t NoiseFloorSettleMillis = 20UL;
#elif defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LLCC68)
static constexpr uint32_t NoiseFloorSettleMillis = 7UL;
#else
static constexpr uint32_t NoiseFloorSettleMillis = 20UL;
@@ -0,0 +1,91 @@
#if defined(MESH_RADIO_AUTO_FAMILY) && MESH_RADIO_AUTO_FAMILY
#include "UniversalLoRaRadio.h"
#include <new>
extern const char meshcore_capability_universal_lora_radio[];
const char meshcore_capability_universal_lora_radio[] __attribute__((used)) =
"meshcore.capability.radio.autodetect.sx126x_sx127x.v1";
UniversalLoRaRadio::UniversalLoRaRadio(Module& sx126_module, Module& sx127_module,
mesh::MainBoard& board)
: sx126_module_(sx126_module), sx127_module_(sx127_module), board_(board) {}
UniversalLoRaRadio::~UniversalLoRaRadio() {
if (family_ == mesh::LoRaRadioFamily::SX126x) {
static_cast<CustomSX1262Wrapper*>(backend_)->~CustomSX1262Wrapper();
static_cast<CustomSX1262*>(physical_)->~CustomSX1262();
} else if (family_ == mesh::LoRaRadioFamily::SX127x) {
static_cast<SX127Backend*>(backend_)->~SX127Backend();
static_cast<CustomSX1276*>(physical_)->~CustomSX1276();
}
}
bool UniversalLoRaRadio::selectFamily(mesh::LoRaRadioFamily family) {
volatile const char* capability = meshcore_capability_universal_lora_radio;
(void)*capability;
// Selection never touches SPI. The board must positively identify the family
// before calling this; a later hardware failure cannot rebind active IRQs.
if (family != mesh::LoRaRadioFamily::SX126x
&& family != mesh::LoRaRadioFamily::SX127x) return false;
if (backend_) return family_ == family;
if (family == mesh::LoRaRadioFamily::SX126x) {
auto* chip = new (&chip_storage_) CustomSX1262(&sx126_module_);
physical_ = chip;
backend_ = new (&driver_storage_) CustomSX1262Wrapper(*chip, board_);
} else {
auto* chip = new (&chip_storage_) CustomSX1276(&sx127_module_);
physical_ = chip;
backend_ = new (&driver_storage_) SX127Backend(*chip, board_);
}
family_ = family;
return true;
}
bool UniversalLoRaRadio::stdInit(SPIClass* spi) {
if (family_ == mesh::LoRaRadioFamily::SX126x)
return static_cast<CustomSX1262*>(physical_)->std_init(spi);
if (family_ == mesh::LoRaRadioFamily::SX127x)
return static_cast<CustomSX1276*>(physical_)->std_init(spi);
return false;
}
const char* UniversalLoRaRadio::familyName() const {
if (family_ == mesh::LoRaRadioFamily::SX126x) return "SX126x";
if (family_ == mesh::LoRaRadioFamily::SX127x) return "SX127x";
return "none";
}
void UniversalLoRaRadio::powerOff() {
if (family_ == mesh::LoRaRadioFamily::SX126x)
static_cast<CustomSX1262Wrapper*>(backend_)->powerOff();
else if (family_ == mesh::LoRaRadioFamily::SX127x)
static_cast<SX127Backend*>(backend_)->powerOff();
}
bool UniversalLoRaRadio::SX127Backend::validateProfile(
const mesh::RadioProfileParams& params) const {
// The common .cpp is compiled once for a universal image. Keep SX127x's
// actual SF/range and protocol capabilities instead of its SX126x defaults.
if (!mesh::RadioProfiles::valid(params, false)
|| params.freq < 137.0f || params.freq > 1020.0f
|| params.sf < 7 || params.bw > 500.0f) return false;
if (params.protocol == mesh::RadioProfileProtocol::Meshtext && !supportsMeshtext()) return false;
if (params.protocol == mesh::RadioProfileProtocol::Ethernet && !supportsEthernetTunnel()) return false;
#if MESH_ENABLE_HOST_KISS
if (params.protocol == mesh::RadioProfileProtocol::Kiss && !supportsHostKiss()) return false;
#else
if (params.protocol == mesh::RadioProfileProtocol::Kiss) return false;
#endif
return true;
}
bool UniversalLoRaRadio::SX127Backend::checkBeaconOutputPower(
float frequency_mhz, int8_t power) const {
(void)frequency_mhz;
// Match SX127x::setOutputPower(): values below 2 dBm select RFO, not PA_BOOST.
return static_cast<SX1276*>(_radio)->checkOutputPower(power, nullptr, power < 2)
== RADIOLIB_ERR_NONE;
}
#endif
+243
View File
@@ -0,0 +1,243 @@
#pragma once
#if defined(MESH_RADIO_AUTO_FAMILY) && MESH_RADIO_AUTO_FAMILY
#include <type_traits>
#include <helpers/LoRaRadioFamily.h>
#include "RadioLibWrappers.h"
#include "CustomSX1262Wrapper.h"
#include "CustomSX1276Wrapper.h"
// Mesh and Dispatcher retain this address before setup(). Only the selected
// backend is constructed, after the board powers and identifies its radio.
// Existing wrappers continue to own IRQs, recovery, profiles and modem state.
class UniversalLoRaRadio : public mesh::Radio {
class SX127Backend : public CustomSX1276Wrapper {
public:
SX127Backend(CustomSX1276& radio, mesh::MainBoard& board)
: CustomSX1276Wrapper(radio, board) {}
bool validateProfile(const mesh::RadioProfileParams& params) const override;
protected:
bool checkBeaconOutputPower(float frequency_mhz, int8_t power) const override;
};
using ChipStorage = typename std::aligned_union<0, CustomSX1262, CustomSX1276>::type;
using DriverStorage = typename std::aligned_union<0, CustomSX1262Wrapper, SX127Backend>::type;
ChipStorage chip_storage_;
DriverStorage driver_storage_;
Module& sx126_module_;
Module& sx127_module_;
mesh::MainBoard& board_;
PhysicalLayer* physical_ = nullptr;
RadioLibWrapper* backend_ = nullptr;
mesh::LoRaRadioFamily family_ = mesh::LoRaRadioFamily::None;
public:
// Compile both policies; the runtime capability still rejects RXPS on SX127x.
static constexpr bool SupportsRxPowerSaving = true;
UniversalLoRaRadio(Module& sx126_module, Module& sx127_module, mesh::MainBoard& board);
~UniversalLoRaRadio();
UniversalLoRaRadio(const UniversalLoRaRadio&) = delete;
UniversalLoRaRadio& operator=(const UniversalLoRaRadio&) = delete;
bool selectFamily(mesh::LoRaRadioFamily family);
bool stdInit(SPIClass* spi);
mesh::LoRaRadioFamily family() const { return family_; }
PhysicalLayer* physicalLayer() { return physical_; }
const PhysicalLayer* physicalLayer() const { return physical_; }
const char* familyName() const;
// These small forwarding methods preserve the complete mesh::Radio contract.
// An unidentified radio never transmits or reports successful reconfiguration.
#define MESH_AUTO_LORA_RET(type, method, params, args, fallback) \
type method params override { return backend_ ? backend_->method args : fallback; }
#define MESH_AUTO_LORA_CONST(type, method, params, args, fallback) \
type method params const override { return backend_ ? static_cast<const RadioLibWrapper*>(backend_)->method args : fallback; }
#define MESH_AUTO_LORA_VOID(method, params, args) \
void method params override { if (backend_) backend_->method args; }
#define MESH_AUTO_LORA_EXTRA(type, method, params, args, fallback) \
type method params { return backend_ ? backend_->method args : fallback; }
#define MESH_AUTO_LORA_EXTRA_CONST(type, method, params, args, fallback) \
type method params const { return backend_ ? static_cast<const RadioLibWrapper*>(backend_)->method args : fallback; }
MESH_AUTO_LORA_CONST(uint8_t, regulatorModes, (), (), 0)
MESH_AUTO_LORA_CONST(mesh::power::RegulatorMode, defaultRegulatorMode, (), (), mesh::power::RegulatorMode::Default)
MESH_AUTO_LORA_RET(bool, configureBootRegulator, (mesh::power::RegulatorMode mode), (mode), false)
MESH_AUTO_LORA_CONST(mesh::power::RegulatorMode, activeRegulatorMode, (), (), mesh::power::RegulatorMode::Default)
MESH_AUTO_LORA_CONST(bool, supportsCarrierWave, (), (), false)
MESH_AUTO_LORA_RET(mesh::RadioParamApplyResult, setCarrierWave, (uint8_t profile, uint32_t duration_ms), (profile, duration_ms), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_CONST(bool, isCarrierWaveActive, (), (), false)
MESH_AUTO_LORA_CONST(uint8_t, carrierWaveProfile, (), (), 0)
MESH_AUTO_LORA_CONST(uint32_t, carrierWaveRemainingMillis, (), (), 0)
MESH_AUTO_LORA_CONST(bool, supportsMorseTx, (), (), false)
MESH_AUTO_LORA_RET(mesh::MorseTxResult, startMorseTx, (float frequency_mhz, uint8_t wpm, const char* text), (frequency_mhz, wpm, text), mesh::MorseTxResult::Unsupported)
MESH_AUTO_LORA_RET(mesh::MorseTxResult, stopMorseTx, (), (), mesh::MorseTxResult::Unsupported)
MESH_AUTO_LORA_CONST(bool, isMorseTxActive, (), (), false)
MESH_AUTO_LORA_CONST(float, morseTxFrequency, (), (), mesh::MorseTx::DefaultFrequencyMHz)
MESH_AUTO_LORA_CONST(uint8_t, morseTxWpm, (), (), mesh::MorseTx::DefaultWpm)
MESH_AUTO_LORA_CONST(uint32_t, morseTxRemainingMillis, (), (), 0)
MESH_AUTO_LORA_CONST(bool, supportsMorseRx, (), (), false)
MESH_AUTO_LORA_RET(mesh::MorseRxResult, startMorseRx, (float frequency_mhz, uint8_t wpm, uint32_t duration_ms), (frequency_mhz, wpm, duration_ms), mesh::MorseRxResult::Unsupported)
MESH_AUTO_LORA_RET(mesh::MorseRxResult, stopMorseRx, (), (), mesh::MorseRxResult::Unsupported)
MESH_AUTO_LORA_CONST(bool, isMorseRxActive, (), (), false)
MESH_AUTO_LORA_CONST(bool, morseRxRecovering, (), (), false)
MESH_AUTO_LORA_CONST(bool, morseRxLearning, (), (), false)
MESH_AUTO_LORA_CONST(float, morseRxFrequency, (), (), mesh::MorseTx::DefaultFrequencyMHz)
MESH_AUTO_LORA_CONST(float, morseRxBandwidthKHz, (), (), 0.0f)
MESH_AUTO_LORA_CONST(uint32_t, morseRxRemainingMillis, (), (), 0)
MESH_AUTO_LORA_CONST(uint32_t, morseCaptureGeneration, (), (), 0)
bool morseRxDiagnostics(mesh::MorseRxDiagnostics& out) const override {
if (backend_) return backend_->morseRxDiagnostics(out);
out = {};
return false;
}
MESH_AUTO_LORA_CONST(size_t, morseCaptureLength, (), (), 0)
MESH_AUTO_LORA_CONST(bool, morseCaptureTruncated, (), (), false)
MESH_AUTO_LORA_CONST(const char*, morseCaptureText, (), (), "")
size_t readMorseCapture(size_t offset, char* out, size_t capacity) const override {
if (backend_) return backend_->readMorseCapture(offset, out, capacity);
if (out && capacity) out[0] = 0;
return 0;
}
MESH_AUTO_LORA_VOID(clearMorseCapture, (), ())
#if defined(MESH_MORSE_HIL) && MESH_MORSE_HIL
MESH_AUTO_LORA_CONST(size_t, morseTraceLength, (), (), 0)
MESH_AUTO_LORA_CONST(uint32_t, morseTraceTotal, (), (), 0)
MESH_AUTO_LORA_CONST(bool, morseTraceTruncated, (), (), false)
MESH_AUTO_LORA_CONST(bool, readMorseTrace, (size_t offset, mesh::MorseRssiTraceEntry& out), (offset, out), false)
#endif
MESH_AUTO_LORA_VOID(begin, (), ())
MESH_AUTO_LORA_RET(mesh::RadioProfiles*, profiles, (), (), nullptr)
MESH_AUTO_LORA_CONST(const mesh::RadioProfiles*, profiles, (), (), nullptr)
#if MESH_RADIO_SWITCH_TUNING
MESH_AUTO_LORA_CONST(mesh::RadioSwitchCapabilities, switchTuningCapabilities, (), (), mesh::RadioSwitchCapabilities{})
MESH_AUTO_LORA_CONST(mesh::RadioSwitchTuning, switchTuning, (), (), mesh::RadioSwitchTuning{})
MESH_AUTO_LORA_CONST(uint32_t, switchSpiHz, (), (), 0)
MESH_AUTO_LORA_RET(bool, configureBootSwitchTuning, (const mesh::RadioSwitchTuning& tuning), (tuning), false)
MESH_AUTO_LORA_RET(mesh::RadioParamApplyResult, trySetSwitchTuning,
(const mesh::RadioSwitchTuning& tuning,
bool (*persist)(void*, const mesh::RadioSwitchTuning&) = nullptr, void* context = nullptr),
(tuning, persist, context), mesh::RadioParamApplyResult::FAILED)
#endif
MESH_AUTO_LORA_CONST(bool, validateProfile, (const mesh::RadioProfileParams& params), (params), false)
MESH_AUTO_LORA_CONST(bool, supportsMeshtext, (), (), false)
MESH_AUTO_LORA_CONST(bool, supportsEthernetTunnel, (), (), false)
#if MESH_ENABLE_HOST_KISS
MESH_AUTO_LORA_CONST(bool, supportsHostKiss, (), (), false)
#endif
#if MESH_ENABLE_TX_BEACONS
MESH_AUTO_LORA_CONST(bool, validateBeaconPower, (float frequency_mhz, int8_t power), (frequency_mhz, power), false)
MESH_AUTO_LORA_RET(mesh::RadioParamApplyResult, prepareBeaconTransmit, (const mesh::RadioProfileParams& params, int8_t power), (params, power), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_RET(mesh::RadioParamApplyResult, restoreBeaconReceive, (), (), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_CONST(bool, isBeaconRadioOwned, (), (), false)
MESH_AUTO_LORA_RET(bool, isBeaconChannelBusy, (), (), true)
MESH_AUTO_LORA_RET(uint32_t, getBeaconAirtime, (const mesh::RadioProfileParams& params, int length), (params, length), 0)
#endif
MESH_AUTO_LORA_CONST(uint8_t, receiveProfile, (), (), 0)
MESH_AUTO_LORA_CONST(uint32_t, receiveProfileGeneration, (), (), 0)
MESH_AUTO_LORA_RET(mesh::RadioParamApplyResult, prepareTransmitProfile, (uint8_t profile), (profile), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_RET(mesh::RadioParamApplyResult, prepareTransmitProfile, (uint8_t profile, bool reply_rx_override), (profile, reply_rx_override), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_CONST(uint16_t, profilePreamble, (uint8_t profile), (profile), 0)
MESH_AUTO_LORA_RET(uint32_t, getProfileAirtime, (uint8_t profile, int len_bytes, uint8_t cr = 0), (profile, len_bytes, cr), 0)
MESH_AUTO_LORA_RET(mesh::RadioParamApplyResult, trySetPrimaryParams, (const mesh::RadioProfileParams& params, bool temporary, const uint32_t* timings = nullptr), (params, temporary, timings), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_RET(int, recvRaw, (uint8_t* bytes, int size), (bytes, size), 0)
MESH_AUTO_LORA_VOID(onReceiveProcessed, (), ())
MESH_AUTO_LORA_RET(uint32_t, getEstAirtimeFor, (int len_bytes), (len_bytes), 0)
MESH_AUTO_LORA_RET(float, packetScore, (float snr, int packet_len), (snr, packet_len), 0.0f)
MESH_AUTO_LORA_RET(bool, startSendRaw, (const uint8_t* bytes, int length), (bytes, length), false)
MESH_AUTO_LORA_RET(bool, setCodingRate, (uint8_t cr), (cr), false)
MESH_AUTO_LORA_RET(mesh::RadioParamApplyResult, tryRestoreCodingRate, (uint8_t cr), (cr), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_RET(bool, isSendComplete, (), (), false)
MESH_AUTO_LORA_VOID(onSendFinished, (), ())
MESH_AUTO_LORA_VOID(loop, (), ())
MESH_AUTO_LORA_VOID(idle, (), ())
MESH_AUTO_LORA_VOID(startRecv, (), ())
MESH_AUTO_LORA_RET(bool, setTrackerSleep, (bool sleeping), (sleeping), false)
MESH_AUTO_LORA_RET(bool, recoverRadio, (bool hard), (hard), false)
MESH_AUTO_LORA_CONST(int, getNoiseFloor, (), (), 0)
MESH_AUTO_LORA_CONST(float, getNoiseFloorDbm, (), (), 0.0f)
MESH_AUTO_LORA_CONST(float, getNoiseFloorDbm, (uint8_t profile), (profile), 0.0f)
#if MESH_ENABLE_TELEMETRY_HISTORY
MESH_AUTO_LORA_CONST(bool, getNoiseFloorReading, (uint8_t profile, float& dbm, uint32_t& published_at_ms), (profile, dbm, published_at_ms), false)
MESH_AUTO_LORA_VOID(requestNoiseFloorMeasurement, (), ())
#endif
MESH_AUTO_LORA_CONST(float, getNoiseFloorCalibrationSecondsRemaining, (), (), 0.0f)
MESH_AUTO_LORA_CONST(float, getNoiseFloorCalibrationSecondsRemaining, (uint8_t profile), (profile), 0.0f)
MESH_AUTO_LORA_CONST(bool, isCalibratingNoiseFloor, (), (), false)
MESH_AUTO_LORA_CONST(bool, getNoiseFloorWakeDelay, (uint32_t now, uint32_t& delay_ms), (now, delay_ms), false)
MESH_AUTO_LORA_CONST(bool, getReceiveRetryDelay, (uint32_t now, uint32_t& delay_ms), (now, delay_ms), false)
MESH_AUTO_LORA_CONST(bool, getProfileIdleDelay, (uint32_t& delay_ms), (delay_ms), false)
MESH_AUTO_LORA_VOID(triggerNoiseFloorCalibrate, (int threshold), (threshold))
MESH_AUTO_LORA_VOID(recalibrateNoiseFloor, (), ())
MESH_AUTO_LORA_VOID(setCADEnabled, (bool enabled), (enabled))
MESH_AUTO_LORA_EXTRA(bool, setCADScanTimeoutMillis, (uint32_t timeout_ms), (timeout_ms), false)
MESH_AUTO_LORA_EXTRA_CONST(uint32_t, getCADScanTimeoutMillis, (), (), 0)
MESH_AUTO_LORA_VOID(resetAGC, (), ())
MESH_AUTO_LORA_RET(bool, tryResetAGC, (), (), false)
MESH_AUTO_LORA_CONST(uint32_t, getRecoveryCount, (), (), 0)
MESH_AUTO_LORA_CONST(uint8_t, getRadioState, (), (), 0)
MESH_AUTO_LORA_CONST(unsigned long, getLastRecvMillis, (), (), 0)
MESH_AUTO_LORA_CONST(unsigned long, getLastRadioInterruptMillis, (), (), 0)
MESH_AUTO_LORA_CONST(bool, isInRecvMode, (), (), false)
MESH_AUTO_LORA_CONST(bool, supportsRxPowerSaving, (), (), false)
MESH_AUTO_LORA_RET(bool, setRxPowerSaving, (bool enabled, uint32_t rx_us, uint32_t sleep_us), (enabled, rx_us, sleep_us), false)
MESH_AUTO_LORA_CONST(bool, isRxPowerSavingContinuousFallback, (), (), false)
MESH_AUTO_LORA_CONST(bool, supportsRxPowerSavingRfRxDisable, (), (), false)
MESH_AUTO_LORA_RET(bool, setRxPowerSavingRfRxDisabled, (bool disabled), (disabled), false)
MESH_AUTO_LORA_CONST(bool, isRxPowerSavingRfRxDisabled, (), (), false)
MESH_AUTO_LORA_RET(mesh::RadioParamApplyResult, trySetParams, (float frequency, float bandwidth, uint8_t sf, uint8_t cr, const uint32_t* timings = nullptr), (frequency, bandwidth, sf, cr, timings), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_RET(bool, isReceiving, (), (), false)
MESH_AUTO_LORA_RET(bool, isReceivingPassive, (int interference_margin_db), (interference_margin_db), false)
MESH_AUTO_LORA_CONST(float, getLastRSSI, (), (), 0.0f)
MESH_AUTO_LORA_CONST(float, getLastSNR, (), (), 0.0f)
MESH_AUTO_LORA_CONST(uint32_t, getPacketsRecvErrors, (), (), 0)
// Existing roles use these RadioLibWrapper helpers in addition to mesh::Radio.
MESH_AUTO_LORA_EXTRA(mesh::RadioParamApplyResult, trySetRxPowerSaving, (bool enabled, uint32_t rx_us, uint32_t sleep_us), (enabled, rx_us, sleep_us), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_EXTRA_CONST(RxPowerSavingStatus, getRxPowerSavingStatus, (), (), RxPowerSavingStatus{})
uint32_t rxPowerSavingTransitionUs() const {
return backend_ ? backend_->rxPowerSavingTransitionUs() : rxPowerSavingDefaultTransitionUs();
}
void getRxPowerSavingTimings(uint32_t& rx_us, uint32_t& sleep_us) const {
if (backend_) backend_->getRxPowerSavingTimings(rx_us, sleep_us);
else { rx_us = 0; sleep_us = 0; }
}
MESH_AUTO_LORA_EXTRA(mesh::RadioParamApplyResult, trySetRxPowerSavingRfRxDisabled, (bool disabled), (disabled), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_EXTRA(bool, isChannelActive, (), (), false)
MESH_AUTO_LORA_EXTRA(bool, setParams, (float frequency, float bandwidth, uint8_t sf, uint8_t cr, const uint32_t* timings = nullptr), (frequency, bandwidth, sf, cr, timings), false)
MESH_AUTO_LORA_EXTRA(uint32_t, getRngSeed, (), (), 0)
MESH_AUTO_LORA_EXTRA_CONST(bool, isTxPowerValid, (int8_t power), (power), false)
MESH_AUTO_LORA_EXTRA_CONST(bool, isTxPowerApplied, (int8_t power), (power), false)
MESH_AUTO_LORA_EXTRA(bool, setTxPower, (int8_t power), (power), false)
MESH_AUTO_LORA_EXTRA(mesh::RadioParamApplyResult, trySetTxPower, (int8_t power), (power), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_EXTRA(float, getCurrentRSSI, (), (), NAN)
MESH_AUTO_LORA_EXTRA_CONST(uint8_t, getSpreadingFactor, (), (), 0)
MESH_AUTO_LORA_EXTRA_CONST(uint16_t, preambleLengthForParams, (uint8_t sf, float bandwidth), (sf, bandwidth), 0)
MESH_AUTO_LORA_EXTRA_CONST(uint16_t, currentPreambleLength, (), (), 0)
MESH_AUTO_LORA_EXTRA(bool, updatePreamble, (uint8_t sf, float bandwidth), (sf, bandwidth), false)
MESH_AUTO_LORA_EXTRA(PacketMillis, calcMaxPacketMillis, (uint8_t sf, float bandwidth, uint8_t cr, uint16_t preamble), (sf, bandwidth, cr, preamble), (PacketMillis{0, 0}))
MESH_AUTO_LORA_EXTRA(int16_t, performChannelScan, (), (), RADIOLIB_ERR_UNSUPPORTED)
MESH_AUTO_LORA_EXTRA_CONST(uint32_t, getPacketsRecv, (), (), 0)
MESH_AUTO_LORA_EXTRA_CONST(uint32_t, getPacketsSent, (), (), 0)
MESH_AUTO_LORA_EXTRA_CONST(uint32_t, getRxPsWatchdogSoftCount, (), (), 0)
MESH_AUTO_LORA_EXTRA_CONST(uint32_t, getRxPsWatchdogHardCount, (), (), 0)
MESH_AUTO_LORA_EXTRA_CONST(bool, isWatchdogObserving, (), (), false)
void resetStats() { if (backend_) backend_->resetStats(); }
MESH_AUTO_LORA_EXTRA(bool, setRxBoostedGainMode, (bool enabled), (enabled), false)
MESH_AUTO_LORA_EXTRA(mesh::RadioParamApplyResult, trySetRxBoostedGainMode, (bool enabled), (enabled), mesh::RadioParamApplyResult::FAILED)
MESH_AUTO_LORA_EXTRA_CONST(bool, supportsRxBoostedGainMode, (), (), false)
MESH_AUTO_LORA_EXTRA_CONST(bool, getRxBoostedGainMode, (), (), false)
MESH_AUTO_LORA_EXTRA(bool, configSideDetectors, (const uint8_t* spreading_factors, uint8_t count, float bandwidth), (spreading_factors, count, bandwidth), false)
MESH_AUTO_LORA_EXTRA(mesh::RadioParamApplyResult, tryConfigSideDetectors, (const uint8_t* spreading_factors, uint8_t count, float bandwidth), (spreading_factors, count, bandwidth), mesh::RadioParamApplyResult::FAILED)
void powerOff();
#undef MESH_AUTO_LORA_RET
#undef MESH_AUTO_LORA_CONST
#undef MESH_AUTO_LORA_VOID
#undef MESH_AUTO_LORA_EXTRA
#undef MESH_AUTO_LORA_EXTRA_CONST
};
#endif
@@ -0,0 +1,224 @@
; Startup-detected SX126x/SX127x radio. Legacy chip-specific targets retain
; their original wiring, names, and OTA identities.
[LilyGo_T3S3]
extends = esp32_base
board = t3_s3_v1_x
build_flags =
${esp32_base.build_flags}
-I variants/lilygo_t3s3_universal
-D LILYGO_T3S3
-D P_LORA_DIO_1=33
-D P_LORA_NSS=7
-D P_LORA_RESET=8
-D P_LORA_BUSY=34
-D P_LORA_SCLK=5
-D P_LORA_MISO=3
-D P_LORA_MOSI=6
-D P_LORA_TX_LED=37
-D PIN_VBAT_READ=1
-D PIN_USER_BTN=0
-D PIN_BOARD_SDA=18
-D PIN_BOARD_SCL=17
-D PIN_OLED_RESET=-1
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
-D SX126X_CURRENT_LIMIT=140
-D USE_SX1262
-D RADIO_CLASS=UniversalLoRaRadio
-D WRAPPER_CLASS=UniversalLoRaRadio
-D LORA_TX_POWER=20
-D SX126X_RX_BOOSTED_GAIN=1
-D USE_SX1276
-D MESH_RADIO_AUTO_FAMILY=1
-D SX127X_CURRENT_LIMIT=120
-D P_LORA_DIO_0=9
build_src_filter =
${esp32_base.build_src_filter}
+<../variants/lilygo_t3s3_universal>
lib_deps =
${esp32_base.lib_deps}
adafruit/Adafruit SSD1306 @ ^2.5.13
[env:LilyGo_T3S3_repeater]
extends = env:LilyGo_T3S3_sx1262_repeater
build_flags =
${LilyGo_T3S3.build_flags}
-D LIGHTWEIGHT_WIFI_OTA=1 ; retain WebUI and the original dual-OTA partition map
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"T3 S3 Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
build_src_filter =
${LilyGo_T3S3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_repeater>
lib_deps =
${LilyGo_T3S3.lib_deps}
${esp32_ota.lib_deps}
[env:LilyGo_T3S3_repeater_bridge_espnow]
extends = env:LilyGo_T3S3_sx1262_repeater_bridge_espnow
build_flags =
${LilyGo_T3S3.build_flags}
-D LIGHTWEIGHT_WIFI_OTA=1 ; retain WebUI and the original dual-OTA partition map
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"ESPNow Bridge"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D WITH_ESPNOW_BRIDGE=1
build_src_filter =
${LilyGo_T3S3.build_src_filter}
+<helpers/bridges/ESPNowBridge.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_repeater>
lib_deps =
${LilyGo_T3S3.lib_deps}
${esp32_ota.lib_deps}
[env:LilyGo_T3S3_repeater_observer_mqtt]
extends = env:LilyGo_T3S3_sx1262_repeater_observer_mqtt
build_flags =
${LilyGo_T3S3.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"MQTT Observer"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D WITH_MQTT_BRIDGE=1
-D MQTT_MAX_PACKET_SIZE=1024
-D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
-D ESP32_CPU_FREQ=160
build_src_filter =
${LilyGo_T3S3.build_src_filter}
+<helpers/bridges/MQTTBridge.cpp>
+<helpers/MQTTMessageBuilder.cpp>
+<helpers/JWTHelper.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_repeater>
lib_deps =
${LilyGo_T3S3.lib_deps}
${esp32_ota.lib_deps}
elims/PsychicMqttClient@^0.2.4
bblanchon/ArduinoJson @ 7.4.3
arduino-libraries/NTPClient
JChristensen/Timezone
paulstoffregen/Time@1.6.1
[env:LilyGo_T3S3_room_server_observer_mqtt]
extends = env:LilyGo_T3S3_sx1262_room_server_observer_mqtt
build_flags =
${LilyGo_T3S3.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"T3 S3 Room Observer"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
-D WITH_MQTT_BRIDGE=1
-D MAX_NEIGHBOURS=50
-D MQTT_MAX_PACKET_SIZE=1024
-D MQTT_DEBUG=1
-D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
-D ESP32_CPU_FREQ=160
build_src_filter =
${LilyGo_T3S3.build_src_filter}
+<helpers/bridges/MQTTBridge.cpp>
+<helpers/MQTTMessageBuilder.cpp>
+<helpers/JWTHelper.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_room_server>
lib_deps =
${LilyGo_T3S3.lib_deps}
${esp32_ota.lib_deps}
elims/PsychicMqttClient@^0.2.4
bblanchon/ArduinoJson @ 7.4.3
arduino-libraries/NTPClient
JChristensen/Timezone
paulstoffregen/Time@1.6.1
[env:LilyGo_T3S3_terminal_chat]
extends = env:LilyGo_T3S3_sx1262_terminal_chat
build_flags =
${LilyGo_T3S3.build_flags}
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=1
build_src_filter =
${LilyGo_T3S3.build_src_filter}
+<../examples/simple_secure_chat/main.cpp>
lib_deps =
${LilyGo_T3S3.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:LilyGo_T3S3_room_server]
extends = env:LilyGo_T3S3_sx1262_room_server
build_flags =
${LilyGo_T3S3.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"T3 S3 Room"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
build_src_filter =
${LilyGo_T3S3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_room_server>
lib_deps =
${LilyGo_T3S3.lib_deps}
${esp32_ota.lib_deps}
[env:LilyGo_T3S3_companion_radio_usb]
extends = env:LilyGo_T3S3_sx1262_companion_radio_usb
build_flags =
${LilyGo_T3S3.build_flags}
-I examples/companion_radio/ui-new
-D DISPLAY_CLASS=SSD1306Display
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D ENABLE_USB_INTERFACE
build_src_filter =
${LilyGo_T3S3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${LilyGo_T3S3.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:LilyGo_T3S3_companion_radio_ble]
extends = env:LilyGo_T3S3_sx1262_companion_radio_ble
build_flags =
${LilyGo_T3S3.build_flags}
-I examples/companion_radio/ui-new
-D DISPLAY_CLASS=SSD1306Display
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=512
build_src_filter =
${LilyGo_T3S3.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${LilyGo_T3S3.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:LilyGo_T3S3_companion_radio_ble_ps]
extends = env:LilyGo_T3S3_companion_radio_ble
[env:LilyGo_T3S3_kiss_modem]
extends = env:LilyGo_T3S3_sx1262_kiss_modem
build_src_filter =
${LilyGo_T3S3.build_src_filter}
+<../examples/kiss_modem/>
+48
View File
@@ -0,0 +1,48 @@
#include <Arduino.h>
#include "target.h"
#include <helpers/radiolib/ESP32LoRaChipProbe.h>
#include <helpers/radiolib/SX126xBufferedRadioHal.h>
#include <helpers/radiolib/TunableRadioHal.h>
UniversalT3S3Board board;
static SPIClass spi;
static SX126xBufferedRadioHal sx126x_hal(spi);
static TunableRadioHal sx127x_hal(spi, 2000000);
static Module sx126x_module(&sx126x_hal, P_LORA_NSS, P_LORA_DIO_1,
P_LORA_RESET, P_LORA_BUSY);
static Module sx127x_module(&sx127x_hal, P_LORA_NSS, P_LORA_DIO_0,
P_LORA_RESET, P_LORA_DIO_1);
UniversalLoRaRadio radio_driver(sx126x_module, sx127x_module, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
SensorManager sensors;
#ifdef DISPLAY_CLASS
DISPLAY_CLASS display;
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
#endif
mesh::LoRaRadioFamily universalLoRaRadioFamily() {
return radio_driver.family();
}
bool prepareUniversalLoRaRadio() {
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
const mesh::LoRaChipProbePins pins{P_LORA_NSS, P_LORA_RESET, P_LORA_BUSY};
return radio_driver.selectFamily(mesh::probeUniversalLoRaChip(spi, pins));
}
bool radio_init() {
fallback_clock.begin();
rtc_clock.begin(Wire);
return radio_driver.stdInit(&spi);
}
mesh::LocalIdentity radio_new_identity() {
auto* physical = radio_driver.physicalLayer();
if (!physical) return mesh::LocalIdentity();
RadioNoiseListener rng(*physical);
return mesh::LocalIdentity(&rng);
}
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#include <helpers/radiolib/UniversalLoRaRadio.h>
#include <helpers/ESP32Board.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
#ifdef DISPLAY_CLASS
#include <helpers/ui/SSD1306Display.h>
#include <helpers/ui/MomentaryButton.h>
#endif
bool prepareUniversalLoRaRadio();
mesh::LoRaRadioFamily universalLoRaRadioFamily();
class UniversalT3S3Board : public ESP32Board {
public:
void begin() {
ESP32Board::begin();
prepareUniversalLoRaRadio();
}
uint32_t getIRQGpio() override {
switch (universalLoRaRadioFamily()) {
case mesh::LoRaRadioFamily::SX126x: return P_LORA_DIO_1;
case mesh::LoRaRadioFamily::SX127x: return P_LORA_DIO_0;
default: return static_cast<uint32_t>(-1);
}
}
const char* getManufacturerName() const override {
switch (universalLoRaRadioFamily()) {
case mesh::LoRaRadioFamily::SX126x: return "LilyGo T3 S3 SX126x";
case mesh::LoRaRadioFamily::SX127x: return "LilyGo T3 S3 SX127x";
default: return "LilyGo T3 S3 (radio undetected)";
}
}
};
extern UniversalT3S3Board board;
extern UniversalLoRaRadio radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors;
#ifdef DISPLAY_CLASS
extern DISPLAY_CLASS display;
extern MomentaryButton user_btn;
#endif
bool radio_init();
mesh::LocalIdentity radio_new_identity();
@@ -0,0 +1,194 @@
; Startup-detected SX126x/SX127x radio. Legacy chip-specific targets retain
; their original wiring, names, and OTA identities.
[LilyGo_TBeam]
extends = esp32_base
board = ttgo-t-beam
build_unflags =
-fexceptions
build_flags =
${esp32_base.build_flags}
-fno-exceptions
-D PORTABLE_ESP32_ROM_NANO_FORMAT=1
-I variants/lilygo_tbeam_universal
-D TBEAM_SX1262
-D P_LORA_DIO_0=26
-D P_LORA_DIO_1=33
-D P_LORA_BUSY=32
-D P_LORA_NSS=18
-D P_LORA_RESET=23
-D P_LORA_SCLK=5
-D P_LORA_MISO=19
-D P_LORA_MOSI=27
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
-D SX126X_CURRENT_LIMIT=140
-D SX126X_RX_BOOSTED_GAIN=1
-D USE_SX1262
-D RADIO_CLASS=UniversalLoRaRadio
-D WRAPPER_CLASS=UniversalLoRaRadio
-D DISPLAY_CLASS=SSD1306Display
-D LORA_TX_POWER=20
-D P_LORA_TX_LED=4
-D PIN_BOARD_SDA=21
-D PIN_BOARD_SCL=22
-D PIN_GPS_RX=12
-D PIN_GPS_TX=34
-D PIN_USER_BTN=38
-D ENV_INCLUDE_GPS=1
-D USE_SX1276
-D MESH_RADIO_AUTO_FAMILY=1
-D SX127X_CURRENT_LIMIT=120
build_src_filter =
${esp32_base.build_src_filter}
+<../variants/lilygo_tbeam_universal>
+<helpers/ui/SSD1306Display.cpp>
+<helpers/esp32/TBeamBoard.cpp>
+<helpers/sensors>
board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
lib_deps =
${esp32_base.lib_deps}
lewisxhe/XPowersLib@^0.2.7
adafruit/Adafruit SSD1306 @ ^2.5.13
stevemarple/MicroNMEA @ ^2.0.6
boschsensortec/BSEC Software Library @ ^1.8.1492
[env:Tbeam_companion_radio_ble]
extends = env:Tbeam_SX1262_companion_radio_ble
build_flags =
${LilyGo_TBeam.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=160
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D OFFLINE_QUEUE_SIZE=128
build_src_filter =
${LilyGo_TBeam.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${LilyGo_TBeam.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Tbeam_companion_radio_ble_ps]
extends = env:Tbeam_companion_radio_ble
[env:Tbeam_repeater]
extends = env:Tbeam_SX1262_repeater
build_flags =
${LilyGo_TBeam.build_flags}
-D ADVERT_NAME='"T-Beam Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D FLOOD_CHANNEL_SCOPE_SLOTS=31 ; classic ESP32 DRAM cannot fit the default 255 with LoRa OTA
build_src_filter =
${LilyGo_TBeam.build_src_filter}
+<../examples/simple_repeater>
lib_deps =
${LilyGo_TBeam.lib_deps}
${esp32_ota.lib_deps}
[env:Tbeam_repeater_bridge_espnow]
extends = env:Tbeam_SX1262_repeater_bridge_espnow
build_flags =
${LilyGo_TBeam.build_flags}
-D ADVERT_NAME='"ESPNow Bridge"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D FLOOD_CHANNEL_SCOPE_SLOTS=31 ; classic ESP32 DRAM cannot fit the default 255 with ESP-NOW
-D WITH_ESPNOW_BRIDGE=1
-D DISABLE_LORA_OTA=1 ; ESP-NOW plus LoRa OTA overflows this board's internal DRAM
build_src_filter =
${LilyGo_TBeam.build_src_filter}
+<helpers/bridges/ESPNowBridge.cpp>
+<../examples/simple_repeater>
lib_deps =
${LilyGo_TBeam.lib_deps}
${esp32_ota.lib_deps}
[env:Tbeam_room_server]
extends = env:Tbeam_SX1262_room_server
build_flags =
${LilyGo_TBeam.build_flags}
-D ADVERT_NAME='"T-Beam Room"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
build_src_filter =
${LilyGo_TBeam.build_src_filter}
+<../examples/simple_room_server>
lib_deps =
${LilyGo_TBeam.lib_deps}
${esp32_ota.lib_deps}
[env:Tbeam_kiss_modem]
extends = env:Tbeam_SX1262_kiss_modem
build_src_filter =
${LilyGo_TBeam.build_src_filter}
+<../examples/kiss_modem/>
[env:Tbeam_repeater_observer_mqtt]
extends = env:Tbeam_SX1262_repeater_observer_mqtt
build_flags =
${LilyGo_TBeam.build_flags}
-D ADVERT_NAME='"T-Beam MQTT Observer"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D WITH_MQTT_BRIDGE=1
-D FLOOD_CHANNEL_SCOPE_SLOTS=31 ; retain scope support within this observer's tight internal DRAM
-D MQTT_MAX_PACKET_SIZE=1024
-D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
build_src_filter =
${LilyGo_TBeam.build_src_filter}
+<helpers/bridges/MQTTBridge.cpp>
+<helpers/MQTTMessageBuilder.cpp>
+<helpers/JWTHelper.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_repeater>
lib_deps =
${LilyGo_TBeam.lib_deps}
${esp32_ota.lib_deps}
elims/PsychicMqttClient@^0.2.4
bblanchon/ArduinoJson @ 7.4.3
arduino-libraries/NTPClient
JChristensen/Timezone
paulstoffregen/Time@1.6.1
[env:Tbeam_room_server_observer_mqtt]
extends = env:Tbeam_SX1262_room_server_observer_mqtt
build_flags =
${LilyGo_TBeam.build_flags}
-D ADVERT_NAME='"T-Beam Room Observer"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
-D WITH_MQTT_BRIDGE=1
-D MAX_NEIGHBOURS=50
-D MQTT_MAX_PACKET_SIZE=1024
-D MQTT_DEBUG=1
-D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
build_src_filter =
${LilyGo_TBeam.build_src_filter}
+<helpers/bridges/MQTTBridge.cpp>
+<helpers/MQTTMessageBuilder.cpp>
+<helpers/JWTHelper.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_room_server>
lib_deps =
${LilyGo_TBeam.lib_deps}
${esp32_ota.lib_deps}
elims/PsychicMqttClient@^0.2.4
bblanchon/ArduinoJson @ 7.4.3
arduino-libraries/NTPClient
JChristensen/Timezone
paulstoffregen/Time@1.6.1
@@ -0,0 +1,57 @@
#include <Arduino.h>
#include "target.h"
#include <helpers/radiolib/ESP32LoRaChipProbe.h>
#include <helpers/radiolib/SX126xBufferedRadioHal.h>
#include <helpers/radiolib/TunableRadioHal.h>
UniversalTBeamBoard board;
static SPIClass spi;
static SX126xBufferedRadioHal sx126x_hal(spi);
static TunableRadioHal sx127x_hal(spi, 2000000);
static Module sx126x_module(&sx126x_hal, P_LORA_NSS, P_LORA_DIO_1,
P_LORA_RESET, P_LORA_BUSY);
static Module sx127x_module(&sx127x_hal, P_LORA_NSS, P_LORA_DIO_0,
P_LORA_RESET, P_LORA_DIO_1);
UniversalLoRaRadio radio_driver(sx126x_module, sx127x_module, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
#if ENV_INCLUDE_GPS
#include <helpers/sensors/MicroNMEALocationProvider.h>
MicroNMEALocationProvider nmea(Serial1, &rtc_clock);
EnvironmentSensorManager sensors(nmea);
#else
EnvironmentSensorManager sensors;
#endif
#ifdef DISPLAY_CLASS
DISPLAY_CLASS display;
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
#endif
mesh::LoRaRadioFamily universalLoRaRadioFamily() {
return radio_driver.family();
}
bool prepareUniversalLoRaRadio() {
// TBeamBoard calls this after enabling the PMU's radio rail. Probe before
// regulator policy or any modem initialization can touch the wrong chip.
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
const mesh::LoRaChipProbePins pins{P_LORA_NSS, P_LORA_RESET, P_LORA_BUSY};
return radio_driver.selectFamily(mesh::probeUniversalLoRaChip(spi, pins));
}
bool radio_init() {
fallback_clock.begin();
rtc_clock.begin(Wire);
return radio_driver.stdInit(&spi);
}
mesh::LocalIdentity radio_new_identity() {
auto* physical = radio_driver.physicalLayer();
if (!physical) return mesh::LocalIdentity();
RadioNoiseListener rng(*physical);
return mesh::LocalIdentity(&rng);
}
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include <helpers/radiolib/UniversalLoRaRadio.h>
#include <helpers/esp32/TBeamBoard.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/sensors/EnvironmentSensorManager.h>
#ifdef DISPLAY_CLASS
#include <helpers/ui/SSD1306Display.h>
#include <helpers/ui/MomentaryButton.h>
#endif
bool prepareUniversalLoRaRadio();
mesh::LoRaRadioFamily universalLoRaRadioFamily();
class UniversalTBeamBoard : public TBeamBoard {
public:
uint32_t getIRQGpio() override {
switch (universalLoRaRadioFamily()) {
case mesh::LoRaRadioFamily::SX126x: return P_LORA_DIO_1;
case mesh::LoRaRadioFamily::SX127x: return P_LORA_DIO_0;
default: return static_cast<uint32_t>(-1);
}
}
const char* getManufacturerName() const override {
switch (universalLoRaRadioFamily()) {
case mesh::LoRaRadioFamily::SX126x: return "LilyGo T-Beam SX126x";
case mesh::LoRaRadioFamily::SX127x: return "LilyGo T-Beam SX127x";
default: return "LilyGo T-Beam (radio undetected)";
}
}
};
extern UniversalTBeamBoard board;
extern UniversalLoRaRadio radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern EnvironmentSensorManager sensors;
#ifdef DISPLAY_CLASS
extern DISPLAY_CLASS display;
extern MomentaryButton user_btn;
#endif
bool radio_init();
mesh::LocalIdentity radio_new_identity();