Seeed Tracker X1 Support (#10834)

* Seeed Tracker X1 Support

* silence CPPCheck

* fix macro order regression and adapt stm32hal for new radiolib

* STM32 is a radiolib upstream fix ( https://github.com/jgromes/RadioLib/issues/1825 )

* fix STM32 regression

* Seeed Tracker X1 Support

* silence CPPCheck

* fix macro order regression and adapt stm32hal for new radiolib

* STM32 is a radiolib upstream fix ( https://github.com/jgromes/RadioLib/issues/1825 )

* fix STM32 regression

* address copilot OCD

* Split behavior only when the two LEDs are on distinct pins.

* bring naming in line with the other seeed devices

* update env name to fit convention too

* guarantee evaluation order

* Guard sensor readings against null values, make sensor use less power
This commit is contained in:
Thomas Göttgens
2026-07-02 07:39:52 -05:00
committed by GitHub
co-authored by GitHub
parent 3becaf2d95
commit 7509c1a6dd
17 changed files with 514 additions and 22 deletions
+1
View File
@@ -101,6 +101,7 @@ class ScanI2C
ADS1115,
IIS2MDCTR,
ISM330DHCX,
SPA06,
} DeviceType;
// typedef uint8_t DeviceAddress;
+5 -1
View File
@@ -441,8 +441,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("DPS310", (uint8_t)addr.address);
type = DPS310;
break;
case 0x11:
logFoundDevice("SPA06-003", (uint8_t)addr.address);
type = SPA06;
break;
}
if (type == DPS310) {
if (type == DPS310 || type == SPA06) {
break;
}
default:
+11
View File
@@ -874,6 +874,17 @@ void setup()
delay(10);
#endif
drv.begin();
// Bits Field Value Meaning
// 7 N_ERM_LRA 1 LRA mode (vs 0 = ERM)
// 6:4 FB_BRAKE_FACTOR 3 4× brake factor
// 3:2 LOOP_GAIN 1 medium loop gain
// 1:0 BEMF_GAIN 2 back-EMF gain
#if defined(DRV2605_USE_LRA)
drv.writeRegister8(DRV2605_REG_FEEDBACK, 0xB6);
#endif
drv.selectLibrary(1);
// I2C trigger by sending 'go' command
drv.setMode(DRV2605_MODE_INTTRIG);
+32 -17
View File
@@ -127,7 +127,10 @@ int32_t ExternalNotificationModule::runOnce()
#endif
#ifdef HAS_DRV2605
drv.go();
// Only trigger DRV2605 if vibration alerts are enabled
if (moduleConfig.external_notification.alert_message_vibra || moduleConfig.external_notification.alert_bell_vibra) {
drv.go();
}
#endif
}
@@ -144,7 +147,7 @@ int32_t ExternalNotificationModule::runOnce()
}
#endif
// now let the PWM buzzer play
if (moduleConfig.external_notification.use_pwm && config.device.buzzer_gpio && canBuzz()) {
if (moduleConfig.external_notification.use_pwm && config.device.buzzer_gpio && canBuzz() && buzzerShouldAlert) {
if (rtttl::isPlaying()) {
rtttl::play();
} else if (isNagging && (nagCycleCutoff >= millis())) {
@@ -225,9 +228,18 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
#endif
#ifdef HAS_DRV2605
// Only trigger DRV2605 when setting vibration motor
bool shouldTriggerDRV = false;
if (on) {
if (index == 1 &&
(moduleConfig.external_notification.alert_message_vibra || moduleConfig.external_notification.alert_bell_vibra)) {
shouldTriggerDRV = true;
}
}
if (shouldTriggerDRV) {
drv.go();
} else {
} else if (!on && index == 1) {
drv.stop();
}
#endif
@@ -266,6 +278,7 @@ void ExternalNotificationModule::stopNow()
// Prevent the state machine from immediately re-triggering outputs after a manual stop.
isNagging = false;
buzzerShouldAlert = false;
nagCycleCutoff = UINT32_MAX;
#ifdef HAS_I2S
@@ -404,8 +417,10 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
// Alert GPIO Buzzer when receiving a bell = alertBellBuzzer: true
// Alert GPIO Buzzer when receiving a message = alertMessageBuzzer: true
const bool buzzerShouldAlert = canBuzz() && ((moduleConfig.external_notification.alert_bell_buzzer && containsBell) ||
(moduleConfig.external_notification.alert_message_buzzer && !is_muted));
// If you are already buzzing, keep going
buzzerShouldAlert =
buzzerShouldAlert || (canBuzz() && ((moduleConfig.external_notification.alert_bell_buzzer && containsBell) ||
(moduleConfig.external_notification.alert_message_buzzer && !is_muted)));
if (genericShouldAlert || vibraShouldAlert || buzzerShouldAlert) {
nagCycleCutoff = millis() + (moduleConfig.external_notification.nag_timeout
@@ -422,6 +437,18 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
if (vibraShouldAlert) {
LOG_INFO("externalNotificationModule - Vibra alert");
#ifdef HAS_DRV2605
// Set DRV2605 waveform when vibration alert is triggered
drv.setWaveform(0, 16); // Long buzzer 100%
drv.setWaveform(1, 0); // Pause
drv.setWaveform(2, 16);
drv.setWaveform(3, 0);
drv.setWaveform(4, 16);
drv.setWaveform(5, 0);
drv.setWaveform(6, 16);
drv.setWaveform(7, 0);
drv.go();
#endif
setExternalState(1, true);
}
@@ -431,18 +458,6 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
LOG_INFO("Message buzzer was suppressed because buzzer mode DIRECT_MSG_ONLY");
} else {
// Buzz if buzzer mode is not in DIRECT_MSG_ONLY or is DM to us
#ifdef HAS_DRV2605
drv.setWaveform(0, 16); // Long buzzer 100%
drv.setWaveform(1, 0); // Pause
drv.setWaveform(2, 16);
drv.setWaveform(3, 0);
drv.setWaveform(4, 16);
drv.setWaveform(5, 0);
drv.setWaveform(6, 16);
drv.setWaveform(7, 0);
drv.go();
#endif
if (moduleConfig.external_notification.use_i2s_as_buzzer) {
#ifdef HAS_I2S
audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
+1
View File
@@ -90,6 +90,7 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
bool isNagging = false;
bool isSilenced = false;
bool buzzerShouldAlert = false;
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,
+16
View File
@@ -215,9 +215,25 @@ int32_t StatusLEDModule::runOnce()
#ifdef PCA_LED_ENABLE
io.digitalWrite(PCA_LED_ENABLE, CHARGE_LED_state);
#endif
#ifdef LED_POWER
#ifdef LED_POWER_CRITICAL
if (LED_POWER != LED_POWER_CRITICAL) {
if (power_state == critical) {
digitalWrite(LED_POWER, 0);
digitalWrite(LED_POWER_CRITICAL, CHARGE_LED_state);
} else {
digitalWrite(LED_POWER, CHARGE_LED_state);
digitalWrite(LED_POWER_CRITICAL, 0);
}
} else {
digitalWrite(LED_POWER, CHARGE_LED_state);
}
#else
digitalWrite(LED_POWER, CHARGE_LED_state);
#endif
#endif
#ifdef LED_PAIRING
digitalWrite(LED_PAIRING, PAIRING_LED_state);
#endif
@@ -119,6 +119,10 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#include "Sensor/T1000xSensor.h"
#endif
#if __has_include(<Adafruit_SPA06_003.h>)
#include "Sensor/SPA06Sensor.h"
#endif
#ifdef SENSECAP_INDICATOR
#include "Sensor/IndicatorSensor.h"
#endif
@@ -167,12 +171,15 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
// Not a real I2C device, uses UART
addSensor<IndicatorSensor>(i2cScanner, ScanI2C::DeviceType::NONE);
#endif
addSensor<RCWL9620Sensor>(i2cScanner, ScanI2C::DeviceType::RCWL9620);
addSensor<CGRadSensSensor>(i2cScanner, ScanI2C::DeviceType::CGRADSENS);
#if HAS_SPA06 && __has_include(<Adafruit_SPA06_003.h>)
addSensor<SPA06Sensor>(i2cScanner, ScanI2C::DeviceType::SPA06);
#endif
#endif
#endif
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
addSensor<RCWL9620Sensor>(i2cScanner, ScanI2C::DeviceType::RCWL9620);
addSensor<CGRadSensSensor>(i2cScanner, ScanI2C::DeviceType::CGRADSENS);
#if __has_include(<DFRobot_LarkWeatherStation.h>)
addSensor<DFRobotLarkSensor>(i2cScanner, ScanI2C::DeviceType::DFROBOT_LARK);
#endif
@@ -0,0 +1,60 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<Adafruit_SPA06_003.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "SPA06Sensor.h"
#include "TelemetrySensor.h"
#include <Adafruit_SPA06_003.h>
SPA06Sensor::SPA06Sensor()
: TelemetrySensor(meshtastic_TelemetrySensorType_SPA06, "SPA06"), spa_temp(nullptr), spa_pressure(nullptr)
{
}
bool SPA06Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
status = spa06.begin(dev->address.address, bus);
if (!status) {
return status;
}
// Set moderate precision for faster sampling
spa06.setPressureOversampling(SPA06_003_OVERSAMPLE_8); // 8x oversampling
spa06.setTemperatureOversampling(SPA06_003_OVERSAMPLE_8); // 8x oversampling
// Set measurement rate. 1 Hz is ample for telemetry (polled every tens of seconds)
// and draws far less power than 32 Hz, while staying in continuous mode so getMetrics()
// remains non-blocking (fresh data always available).
spa06.setPressureMeasureRate(SPA06_003_RATE_1); // 1 Hz
spa06.setTemperatureMeasureRate(SPA06_003_RATE_1); // 1 Hz
spa06.setMeasurementMode(SPA06_003_MEAS_CONTINUOUS_BOTH);
spa_temp = spa06.getTemperatureSensor();
spa_pressure = spa06.getPressureSensor();
initI2CSensor();
return status;
}
bool SPA06Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
if (!spa_temp || !spa_pressure) {
return false;
}
sensors_event_t temp, press;
if (!spa_temp->getEvent(&temp) || !spa_pressure->getEvent(&press)) {
LOG_DEBUG("SPA06 getEvents no data");
return false;
}
measurement->variant.environment_metrics.has_temperature = true;
measurement->variant.environment_metrics.has_barometric_pressure = true;
measurement->variant.environment_metrics.temperature = temp.temperature;
measurement->variant.environment_metrics.barometric_pressure = press.pressure;
return true;
}
#endif
@@ -0,0 +1,22 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<Adafruit_SPA06_003.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include <Adafruit_SPA06_003.h>
class SPA06Sensor : public TelemetrySensor
{
private:
Adafruit_SPA06_003 spa06;
Adafruit_Sensor *spa_temp;
Adafruit_Sensor *spa_pressure;
public:
SPA06Sensor();
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
+2
View File
@@ -113,6 +113,8 @@
#define HW_VENDOR meshtastic_HardwareModel_WIO_WM1110
#elif defined(TRACKER_T1000_E)
#define HW_VENDOR meshtastic_HardwareModel_TRACKER_T1000_E
#elif defined(MESH_TRACKER_X1)
#define HW_VENDOR meshtastic_HardwareModel_MESH_TRACKER_X1
#elif defined(ME25LS01_4Y10TD)
#define HW_VENDOR meshtastic_HardwareModel_ME25LS01_4Y10TD
#elif defined(MS24SF1)
+7 -1
View File
@@ -266,20 +266,26 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
digitalWrite(SDCARD_CS, LOW);
#endif
#ifdef TRACKER_T1000_E
#if defined(TRACKER_T1000_E) || defined(MESH_TRACKER_X1)
#ifdef GNSS_AIROHA
digitalWrite(GPS_VRTC_EN, LOW);
digitalWrite(PIN_GPS_RESET, LOW);
digitalWrite(GPS_SLEEP_INT, LOW);
digitalWrite(GPS_RTC_INT, LOW);
#ifdef GPS_RESETB_OUT
pinMode(GPS_RESETB_OUT, OUTPUT);
digitalWrite(GPS_RESETB_OUT, LOW);
#endif
#endif
#ifdef BUZZER_EN_PIN
digitalWrite(BUZZER_EN_PIN, LOW);
#endif
#ifdef PIN_DRV_EN
digitalWrite(PIN_DRV_EN, LOW);
#endif
#ifdef PIN_3V3_EN
digitalWrite(PIN_3V3_EN, LOW);
#endif