diff --git a/boards/t-beam-1w.json b/boards/t-beam-1w.json
new file mode 100644
index 000000000..ac1d1f15e
--- /dev/null
+++ b/boards/t-beam-1w.json
@@ -0,0 +1,50 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld",
+ "memory_type": "qio_opi"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DBOARD_HAS_PSRAM",
+ "-DLILYGO_TBEAM_1W",
+ "-DARDUINO_USB_CDC_ON_BOOT=1",
+ "-DARDUINO_USB_MODE=0",
+ "-DARDUINO_RUNNING_CORE=1",
+ "-DARDUINO_EVENT_RUNNING_CORE=1"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "psram_type": "opi",
+ "hwids": [
+ [
+ "0x303A",
+ "0x1001"
+ ]
+ ],
+ "mcu": "esp32s3",
+ "variant": "t-beam-1w"
+ },
+ "connectivity": [
+ "wifi",
+ "bluetooth",
+ "lora"
+ ],
+ "debug": {
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": [
+ "arduino"
+ ],
+ "name": "LilyGo TBeam-1W",
+ "upload": {
+ "flash_size": "16MB",
+ "maximum_ram_size": 327680,
+ "maximum_size": 16777216,
+ "require_upload_port": true,
+ "speed": 921600
+ },
+ "url": "http://www.lilygo.cn/",
+ "vendor": "LilyGo"
+}
\ No newline at end of file
diff --git a/src/configuration.h b/src/configuration.h
index 650e1cc71..ec1b9acc2 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -444,6 +444,18 @@ along with this program. If not, see .
#endif
#endif
+// BME680 BSEC2 support detection
+#if !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
+#if defined(RAK_4631) || defined(TBEAM_V10)
+
+#define MESHTASTIC_BME680_BSEC2_SUPPORTED 1
+#define MESHTASTIC_BME680_HEADER
+#else
+#define MESHTASTIC_BME680_BSEC2_SUPPORTED 0
+#define MESHTASTIC_BME680_HEADER
+#endif // defined(RAK_4631)
+#endif // !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
+
// -----------------------------------------------------------------------------
// Global switches to turn off features for a minimized build
// -----------------------------------------------------------------------------
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index e1f07a32b..2d7996a13 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -62,6 +62,11 @@ template bool SX126xInterface::init()
digitalWrite(LORA_PA_TX_EN, LOW);
#endif
+#ifdef RF95_FAN_EN
+ digitalWrite(RF95_FAN_EN, HIGH);
+ pinMode(RF95_FAN_EN, OUTPUT);
+#endif
+
#if ARCH_PORTDUINO
tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) {
@@ -85,6 +90,13 @@ template bool SX126xInterface::init()
power = -9;
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
+
+#ifdef SX126X_PA_RAMP_US
+ // Set custom PA ramp time for boards requiring longer stabilization (e.g., T-Beam 1W needs >800us)
+ if (res == RADIOLIB_ERR_NONE) {
+ lora.setPaRampTime(SX126X_PA_RAMP_US);
+ }
+#endif
// \todo Display actual typename of the adapter, not just `SX126x`
LOG_INFO("SX126x init result %d", res);
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 41062662b..843d7b8d5 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -53,7 +53,7 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#include "Sensor/LTR390UVSensor.h"
#endif
-#if __has_include()
+#if __has_include(MESHTASTIC_BME680_HEADER)
#include "Sensor/BME680Sensor.h"
#endif
@@ -214,7 +214,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include()
addSensor(i2cScanner, ScanI2C::DeviceType::LTR390UV);
#endif
-#if __has_include()
+#if __has_include(MESHTASTIC_BME680_HEADER)
addSensor(i2cScanner, ScanI2C::DeviceType::BME_680);
#endif
#if __has_include()
diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp
index 95f3dc5f0..22330ca75 100644
--- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp
@@ -1,6 +1,6 @@
#include "configuration.h"
-#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include()
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "BME680Sensor.h"
@@ -10,6 +10,7 @@
BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {}
+#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
int32_t BME680Sensor::runOnce()
{
if (!bme680.run()) {
@@ -17,10 +18,13 @@ int32_t BME680Sensor::runOnce()
}
return 35;
}
+#endif // defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
status = 0;
+
+#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
if (!bme680.begin(dev->address.address, *bus))
checkStatus("begin");
@@ -42,12 +46,25 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
if (status == 0)
LOG_DEBUG("BME680Sensor::runOnce: bme680.status %d", bme680.status);
+#else
+ bme680 = makeBME680(bus);
+
+ if (!bme680->begin(dev->address.address)) {
+ LOG_ERROR("Init sensor: %s failed at begin()", sensorName);
+ return status;
+ }
+
+ status = 1;
+
+#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
+
initI2CSensor();
return status;
}
bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
+#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0)
return false;
@@ -65,9 +82,27 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
// Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms)
measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal;
updateState();
+#else
+ if (!bme680->performReading()) {
+ LOG_ERROR("BME680Sensor::getMetrics: performReading failed");
+ return false;
+ }
+
+ measurement->variant.environment_metrics.has_temperature = true;
+ measurement->variant.environment_metrics.has_relative_humidity = true;
+ measurement->variant.environment_metrics.has_barometric_pressure = true;
+ measurement->variant.environment_metrics.has_gas_resistance = true;
+
+ measurement->variant.environment_metrics.temperature = bme680->readTemperature();
+ measurement->variant.environment_metrics.relative_humidity = bme680->readHumidity();
+ measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F;
+ measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0;
+
+#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
return true;
}
+#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
void BME680Sensor::loadState()
{
#ifdef FSCom
@@ -144,5 +179,6 @@ void BME680Sensor::checkStatus(const char *functionName)
else if (bme680.sensor.status > BME68X_OK)
LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status);
}
+#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif
diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h
index f4ead95f7..9bef56e1e 100644
--- a/src/modules/Telemetry/Sensor/BME680Sensor.h
+++ b/src/modules/Telemetry/Sensor/BME680Sensor.h
@@ -1,23 +1,40 @@
#include "configuration.h"
-#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include()
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
+
+#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
+#include
#include
+#else
+#include
+#include
+#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis()
+#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
const uint8_t bsec_config[] = {
#include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt"
};
-
+#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
class BME680Sensor : public TelemetrySensor
{
private:
+#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
Bsec2 bme680;
+#else
+ using BME680Ptr = std::unique_ptr;
+
+ static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique(bus); }
+
+ BME680Ptr bme680;
+#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
protected:
+#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
const char *bsecConfigFileName = "/prefs/bsec.dat";
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
uint8_t accuracy = 0;
@@ -34,10 +51,13 @@ class BME680Sensor : public TelemetrySensor
void loadState();
void updateState();
void checkStatus(const char *functionName);
+#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
public:
BME680Sensor();
+#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
virtual int32_t runOnce() override;
+#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
diff --git a/variants/esp32s3/t-beam-1w/pins_arduino.h b/variants/esp32s3/t-beam-1w/pins_arduino.h
new file mode 100644
index 000000000..92b74615d
--- /dev/null
+++ b/variants/esp32s3/t-beam-1w/pins_arduino.h
@@ -0,0 +1,25 @@
+#ifndef Pins_Arduino_h
+#define Pins_Arduino_h
+
+#include
+
+#define USB_VID 0x303a
+#define USB_PID 0x1001
+
+static const uint8_t TX = 43;
+static const uint8_t RX = 44;
+
+// I2C for OLED and sensors
+static const uint8_t SDA = 8;
+static const uint8_t SCL = 9;
+
+// Default SPI mapped to Radio/SD
+static const uint8_t SS = 15; // LoRa CS
+static const uint8_t MOSI = 11;
+static const uint8_t MISO = 12;
+static const uint8_t SCK = 13;
+
+// SD Card CS
+#define SDCARD_CS 10
+
+#endif /* Pins_Arduino_h */
diff --git a/variants/esp32s3/t-beam-1w/platformio.ini b/variants/esp32s3/t-beam-1w/platformio.ini
new file mode 100644
index 000000000..54ddb6c3e
--- /dev/null
+++ b/variants/esp32s3/t-beam-1w/platformio.ini
@@ -0,0 +1,14 @@
+; LilyGo T-Beam-1W (1 Watt LoRa with external PA)
+[env:t-beam-1w]
+extends = esp32s3_base
+board = t-beam-1w
+board_build.partitions = default_8MB.csv
+board_check = true
+
+lib_deps =
+ ${esp32s3_base.lib_deps}
+
+build_flags =
+ ${esp32s3_base.build_flags}
+ -I variants/esp32s3/t-beam-1w
+ -D T_BEAM_1W
diff --git a/variants/esp32s3/t-beam-1w/variant.h b/variants/esp32s3/t-beam-1w/variant.h
new file mode 100644
index 000000000..01404afcb
--- /dev/null
+++ b/variants/esp32s3/t-beam-1w/variant.h
@@ -0,0 +1,97 @@
+// LilyGo T-Beam-1W variant.h
+// Configuration based on LilyGO utilities.h and RF documentation
+
+// I2C for OLED display (SH1106 at 0x3C)
+#define I2C_SDA 8
+#define I2C_SCL 9
+
+// GPS - Quectel L76K
+#define GPS_RX_PIN 5
+#define GPS_TX_PIN 6
+#define GPS_1PPS_PIN 7
+#define GPS_WAKEUP_PIN 16 // GPS_EN_PIN in LilyGO code
+#define HAS_GPS 1
+#define GPS_BAUDRATE 9600
+
+// Buttons
+#define BUTTON_PIN 0 // BUTTON 1
+#define BUTTON_PIN_ALT 17 // BUTTON 2
+
+// SPI (shared by LoRa and SD)
+#define SPI_MOSI 11
+#define SPI_SCK 13
+#define SPI_MISO 12
+#define SPI_CS 10
+
+// SD Card
+#define HAS_SDCARD
+#define SDCARD_USE_SPI1
+#define SDCARD_CS SPI_CS
+
+// LoRa Radio - SX1262 with 1W PA
+#define USE_SX1262
+
+#define LORA_SCK SPI_SCK
+#define LORA_MISO SPI_MISO
+#define LORA_MOSI SPI_MOSI
+#define LORA_CS 15
+#define LORA_RESET 3
+#define LORA_DIO1 1
+#define LORA_BUSY 38
+
+// CRITICAL: Radio power enable - MUST be HIGH before lora.begin()!
+// GPIO 40 powers the SX1262 + PA module via LDO
+#define SX126X_POWER_EN 40
+
+// TX power offset for external PA (0 = no offset, full SX1262 power)
+#define TX_GAIN_LORA 10
+
+#ifdef USE_SX1262
+#define SX126X_CS LORA_CS
+#define SX126X_DIO1 LORA_DIO1
+#define SX126X_BUSY LORA_BUSY
+#define SX126X_RESET LORA_RESET
+
+// RF switching configuration for 1W PA module
+// DIO2 controls PA (via SX126X_DIO2_AS_RF_SWITCH)
+// CTRL PIN (GPIO 21) controls LNA - must be HIGH during RX
+// Truth table: DIO2=1,CTRL=0 → TX (PA on, LNA off)
+// DIO2=0,CTRL=1 → RX (PA off, LNA on)
+#define SX126X_DIO2_AS_RF_SWITCH
+#define SX126X_RXEN 21 // LNA enable - HIGH during RX
+
+// TCXO voltage - required for radio init
+#define SX126X_DIO3_TCXO_VOLTAGE 1.8
+
+#define SX126X_MAX_POWER 22
+#endif
+
+// LED
+#define LED_PIN 18
+#define LED_STATE_ON 1 // HIGH = ON
+
+// Battery ADC
+#define BATTERY_PIN 4
+#define ADC_CHANNEL ADC1_GPIO4_CHANNEL
+#define BATTERY_SENSE_SAMPLES 30
+#define ADC_MULTIPLIER 2.9333
+
+// NTC temperature sensor
+#define NTC_PIN 14
+
+// Fan control
+#define FAN_CTRL_PIN 41
+// Meshtastic standard fan control pin macro
+#define RF95_FAN_EN FAN_CTRL_PIN
+
+// PA Ramp Time - T-Beam 1W requires >800us stabilization (default is 200us)
+// Value 0x05 = RADIOLIB_SX126X_PA_RAMP_800U
+#define SX126X_PA_RAMP_US 0x05
+
+// Display - SH1106 OLED (128x64)
+#define USE_SH1106
+#define OLED_WIDTH 128
+#define OLED_HEIGHT 64
+
+// 32768 Hz crystal present
+#define HAS_32768HZ 1
diff --git a/variants/native/portduino.ini b/variants/native/portduino.ini
index 76520091c..cc6c39aa2 100644
--- a/variants/native/portduino.ini
+++ b/variants/native/portduino.ini
@@ -34,6 +34,8 @@ lib_deps =
adafruit/Adafruit seesaw Library@1.7.9
# renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main
https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip
+ # renovate: datasource=custom.pio depName=adafruit/Adafruit BME680 Library packageName=adafruit/library/Adafruit BME680
+ adafruit/Adafruit BME680 Library@^2.0.5
build_flags =
${arduino_base.build_flags}