diff --git a/boards/mesh-tracker-x1.json b/boards/mesh-tracker-x1.json new file mode 100644 index 0000000000..5a7dce86b3 --- /dev/null +++ b/boards/mesh-tracker-x1.json @@ -0,0 +1,60 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v7.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x8029"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"], + ["0x239A", "0x802A"], + ["0x2886", "0x0057"] + ], + "usb_product": "X1-BOOT", + "mcu": "nrf52840", + "variant": "Seeed_Mesh-Tracker-X1", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "7.3.0", + "sd_fwid": "0x0123" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": ["arduino"], + "name": "Seeed Mesh Tracker X1", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink", + "cmsis-dap", + "blackmagic" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://www.seeedstudio.com/SenseCAP-MeshTracker-X1-for-Meshtastic-p-6793.html", + "vendor": "Seeed Studio" +} diff --git a/platformio.ini b/platformio.ini index 4560bfa8ea..48cd420e3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -121,7 +121,7 @@ lib_deps = [radiolib_base] lib_deps = # renovate: datasource=github-tags depName=RadioLib packageName=jgromes/RadioLib - https://github.com/jgromes/RadioLib/archive/refs/tags/7.6.0.zip + https://github.com/jgromes/RadioLib/archive/3c960d0e42fe14d39fd7255bdce81368d62d3f44.zip [device-ui_base] lib_deps = @@ -201,6 +201,8 @@ lib_deps = https://github.com/wollewald/BH1750_WE/archive/refs/tags/1.1.10.zip # renovate: datasource=github-tags depName=Adafruit SHTC3 packageName=adafruit/Adafruit_SHTC3 https://github.com/adafruit/Adafruit_SHTC3/archive/refs/tags/1.0.2.zip + # renovate: datasource=github-tags depName=SPA06_003 packageName=adafruit/Adafruit_SPA06_003 + https://github.com/adafruit/Adafruit_SPA06_003/archive/refs/tags/1.0.2.zip ; Common environmental sensor libraries (not included in native / portduino) [environmental_extra_common] diff --git a/src/detect/LoRaRadioType.h b/src/detect/LoRaRadioType.h index a059a3668b..a66a40d347 100644 --- a/src/detect/LoRaRadioType.h +++ b/src/detect/LoRaRadioType.h @@ -11,7 +11,8 @@ enum LoRaRadioType { SX1280_RADIO, LR1110_RADIO, LR1120_RADIO, - LR1121_RADIO + LR1121_RADIO, + LR2021_RADIO, }; extern LoRaRadioType radioType; \ No newline at end of file diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 7c4f2f0e3e..f0d60cb5f2 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -99,6 +99,7 @@ class ScanI2C CW2015, SCD30, ADS1115, + SPA06, } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index fe5d727ced..e515b46b08 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -394,8 +394,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: diff --git a/src/main.cpp b/src/main.cpp index 084dd29146..c3ccdd34b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -794,6 +794,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); diff --git a/src/mesh/InterfacesTemplates.cpp b/src/mesh/InterfacesTemplates.cpp index c246141dce..6e27b56f39 100644 --- a/src/mesh/InterfacesTemplates.cpp +++ b/src/mesh/InterfacesTemplates.cpp @@ -1,11 +1,17 @@ +#include "configuration.h" + #include "LR11x0Interface.cpp" #include "LR11x0Interface.h" +#include "LR20x0Interface.cpp" +#include "LR20x0Interface.h" #include "SX126xInterface.cpp" #include "SX126xInterface.h" #include "SX128xInterface.cpp" #include "SX128xInterface.h" +#ifndef ARCH_PORTDUINO_WASM // TCP socket API server excluded in the browser/wasm build #include "api/ServerAPI.cpp" #include "api/ServerAPI.h" +#endif // We need this declaration for proper linking in derived classes #if RADIOLIB_EXCLUDE_SX126X != 1 @@ -21,6 +27,9 @@ template class LR11x0Interface; template class LR11x0Interface; template class LR11x0Interface; #endif +#if defined(USE_LR2021) && RADIOLIB_EXCLUDE_LR2021 != 1 +template class LR20x0Interface; +#endif #ifdef ARCH_STM32WL template class SX126xInterface; #endif diff --git a/src/mesh/LR2021Interface.cpp b/src/mesh/LR2021Interface.cpp new file mode 100644 index 0000000000..9aa4d5f1ab --- /dev/null +++ b/src/mesh/LR2021Interface.cpp @@ -0,0 +1,18 @@ +#include "configuration.h" + +#if defined(USE_LR2021) && RADIOLIB_EXCLUDE_LR2021 != 1 + +#include "LR2021Interface.h" +#include "error.h" + +LR2021Interface::LR2021Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy) + : LR20x0Interface(hal, cs, irq, rst, busy) +{ +} + +bool LR2021Interface::wideLora() +{ + return true; +} +#endif diff --git a/src/mesh/LR2021Interface.h b/src/mesh/LR2021Interface.h new file mode 100644 index 0000000000..52c04ee903 --- /dev/null +++ b/src/mesh/LR2021Interface.h @@ -0,0 +1,15 @@ +#pragma once +#if RADIOLIB_EXCLUDE_LR2021 != 1 +#include "LR20x0Interface.h" + +/** + * Our adapter for LR2021 radios + */ +class LR2021Interface : public LR20x0Interface +{ + public: + LR2021Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, + RADIOLIB_PIN_TYPE busy); + bool wideLora() override; +}; +#endif diff --git a/src/mesh/LR20x0Interface.cpp b/src/mesh/LR20x0Interface.cpp index 0d496acc1c..95dad82e92 100644 --- a/src/mesh/LR20x0Interface.cpp +++ b/src/mesh/LR20x0Interface.cpp @@ -211,7 +211,7 @@ template bool LR20x0Interface::reconfigure() err = lora.setOutputPower(power); assert(err == RADIOLIB_ERR_NONE); - // Apply RX gain mode — valid in STDBY, matches resetAGC() pattern + // Apply RX gain mode - valid in STDBY, matches resetAGC() pattern err = lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain); if (err != RADIOLIB_ERR_NONE) LOG_WARN("LR20x0 setRxBoostedGainMode %s%d", radioLibErr, err); @@ -332,7 +332,7 @@ template void LR20x0Interface::resetAGC() LOG_DEBUG("LR20x0 AGC reset: warm sleep + Calibrate(0x3F)"); - // 1. Warm sleep — powers down the analog frontend, resetting AGC state + // 1. Warm sleep - powers down the analog frontend, resetting AGC state lora.sleep(true, 0); // 2. Wake to RC standby for stable calibration @@ -377,7 +377,7 @@ template bool LR20x0Interface::sleep() template int16_t LR20x0Interface::getCurrentRSSI() { - float rssi = lora.getRSSI(); + float rssi = lora.getRSSI(false, true); return (int16_t)round(rssi); } #endif diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 2a5ae32228..de594d3702 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -5,6 +5,7 @@ #include "LR1110Interface.h" #include "LR1120Interface.h" #include "LR1121Interface.h" +#include "LR2021Interface.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -460,6 +461,20 @@ std::unique_ptr initLoRa() } #endif +#if defined(USE_LR2021) && RADIOLIB_EXCLUDE_LR2021 != 1 + if (!rIf) { + rIf = std::unique_ptr( + new LR2021Interface(loraHal, LR2021_SPI_NSS_PIN, LR2021_IRQ_PIN, LR2021_NRESET_PIN, LR2021_BUSY_PIN)); + if (!rIf->init()) { + LOG_WARN("No LR2021 radio"); + rIf = nullptr; + } else { + LOG_INFO("LR2021 init success"); + radioType = LR2021_RADIO; + } + } +#endif + #if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1 if (!rIf) { rIf = std::unique_ptr(new SX1280Interface(loraHal, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY)); diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 68f59ae2e0..a3eca415ab 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -95,8 +95,11 @@ class RadioInterface const uint8_t NUM_SYM_CAD = 2; // Number of symbols used for CAD, 2 is the default since RadioLib 6.3.0 as per AN1200.48 const uint8_t NUM_SYM_CAD_24GHZ = 4; // Number of symbols used for CAD in 2.4 GHz, 4 is recommended in AN1200.22 of SX1280 uint32_t slotTimeMsec = computeSlotTimeMsec(); - uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving - uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast + uint16_t preambleLength = 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving + static constexpr uint16_t preambleLengthDefault = + 16; // 8 is default, but we use longer to increase the amount of sleep time when receiving + static constexpr uint16_t wideLoraPreambleLengthDefault = 12; // 12 is default for wide Lora + uint32_t preambleTimeMsec = 165; // calculated on startup, this is the default for LongFast const uint32_t PROCESSING_TIME_MSEC = 4500; // time to construct, process and construct a packet again (empirically determined) const uint8_t CWmin = 3; // minimum CWsize diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 4d233c9b7c..69ff48c94b 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -308,9 +308,9 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_TDISPLAY_S3_PRO = 126, /* Heltec Mesh Node T096 board features an nRF52840 CPU and a TFT screen. */ meshtastic_HardwareModel_HELTEC_MESH_NODE_T096 = 127, - /* Seeed studio T1000-E Pro tracker card. NRF52840 w/ LR2021 radio, + /* Seeed studio Mesh Tracker X1card. NRF52840 w/ LR2021 radio, GPS, button, buzzer, and sensors. */ - meshtastic_HardwareModel_TRACKER_T1000_E_PRO = 128, + meshtastic_HardwareModel_MESH_TRACKER_X1 = 128, /* Elecrow ThinkNode M7, M8 and M9 */ meshtastic_HardwareModel_THINKNODE_M7 = 129, meshtastic_HardwareModel_THINKNODE_M8 = 130, diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h index e8d337049d..965a93157a 100644 --- a/src/mesh/generated/meshtastic/telemetry.pb.h +++ b/src/mesh/generated/meshtastic/telemetry.pb.h @@ -119,7 +119,9 @@ typedef enum _meshtastic_TelemetrySensorType { /* MMC5983MA 3-Axis Digital Magnetic Sensor */ meshtastic_TelemetrySensorType_MMC5983MA = 52, /* ICM-42607-P 6‑Axis IMU */ - meshtastic_TelemetrySensorType_ICM42607P = 53 + meshtastic_TelemetrySensorType_ICM42607P = 53, + /* SPA06 pressure and temperature */ + meshtastic_TelemetrySensorType_SPA06 = 54 } meshtastic_TelemetrySensorType; /* Struct definitions */ @@ -500,8 +502,8 @@ extern "C" { /* Helper constants for enums */ #define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET -#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_ICM42607P -#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_ICM42607P+1)) +#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_SPA06 +#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_SPA06+1)) diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp index 0a1c4a6dd4..5aaecbd648 100644 --- a/src/modules/ExternalNotificationModule.cpp +++ b/src/modules/ExternalNotificationModule.cpp @@ -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)); diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h index 8781c1ca84..03eac036bb 100644 --- a/src/modules/ExternalNotificationModule.h +++ b/src/modules/ExternalNotificationModule.h @@ -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, diff --git a/src/modules/StatusLEDModule.cpp b/src/modules/StatusLEDModule.cpp index 353317d25e..1e68b03460 100644 --- a/src/modules/StatusLEDModule.cpp +++ b/src/modules/StatusLEDModule.cpp @@ -215,9 +215,29 @@ int32_t StatusLEDModule::runOnce() #ifdef PCA_LED_ENABLE io.digitalWrite(PCA_LED_ENABLE, CHARGE_LED_state); #endif + #ifdef LED_POWER +#ifdef LED_POWER_CRITICAL + // Split behavior only when the two LEDs are on distinct pins. If a board maps + // LED_POWER and LED_POWER_CRITICAL to the same GPIO, the two writes would race + // (second wins) and invert normal/critical; fall back to a single write there. + // Both are compile-time constants, so the unused branch folds away at build time. + 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 diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index b7b6e04a98..415a7ee9d0 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -127,6 +127,10 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c #include "Sensor/T1000xSensor.h" #endif +#if __has_include() +#include "Sensor/SPA06Sensor.h" +#endif + #ifdef SENSECAP_INDICATOR #include "Sensor/IndicatorSensor.h" #endif @@ -166,12 +170,15 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner) // Not a real I2C device, uses UART addSensor(i2cScanner, ScanI2C::DeviceType::NONE); #endif - addSensor(i2cScanner, ScanI2C::DeviceType::RCWL9620); - addSensor(i2cScanner, ScanI2C::DeviceType::CGRADSENS); +#if HAS_SPA06 && __has_include() + addSensor(i2cScanner, ScanI2C::DeviceType::SPA06); +#endif #endif #endif #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL + addSensor(i2cScanner, ScanI2C::DeviceType::RCWL9620); + addSensor(i2cScanner, ScanI2C::DeviceType::CGRADSENS); #if __has_include() addSensor(i2cScanner, ScanI2C::DeviceType::DFROBOT_LARK); #endif @@ -449,7 +456,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt bool isCooldownOver = (now - lastAlertTime > 60000); if (isOwnTelemetry && bannerMsg && isCooldownOver) { - LOG_INFO("drawFrame: IAQ %d (own) — showing banner: %s", m.iaq, bannerMsg); + LOG_INFO("drawFrame: IAQ %d (own) - showing banner: %s", m.iaq, bannerMsg); screen->showSimpleBanner(bannerMsg, 3000); // Only buzz if IAQ is over 200 diff --git a/src/modules/Telemetry/Sensor/SPA06Sensor.cpp b/src/modules/Telemetry/Sensor/SPA06Sensor.cpp new file mode 100644 index 0000000000..052e3f8ebe --- /dev/null +++ b/src/modules/Telemetry/Sensor/SPA06Sensor.cpp @@ -0,0 +1,60 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "SPA06Sensor.h" +#include "TelemetrySensor.h" +#include + +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 diff --git a/src/modules/Telemetry/Sensor/SPA06Sensor.h b/src/modules/Telemetry/Sensor/SPA06Sensor.h new file mode 100644 index 0000000000..01aadb5cfd --- /dev/null +++ b/src/modules/Telemetry/Sensor/SPA06Sensor.h @@ -0,0 +1,22 @@ +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include() + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include + +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 diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index a4a17b1d46..ca73a27f77 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -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) diff --git a/src/sleep.cpp b/src/sleep.cpp index 64bd0c4803..6d30a807bd 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -252,20 +252,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 diff --git a/variants/nrf52840/seeed_mesh_tracker_X1/platformio.ini b/variants/nrf52840/seeed_mesh_tracker_X1/platformio.ini new file mode 100644 index 0000000000..e12fbfd9a7 --- /dev/null +++ b/variants/nrf52840/seeed_mesh_tracker_X1/platformio.ini @@ -0,0 +1,33 @@ +[env:seeed_mesh_tracker_X1] +custom_meshtastic_support_level = 1 +custom_meshtastic_images = seeed-mesh-tracker-x1.svg +custom_meshtastic_tags = Seeed +custom_meshtastic_hw_model = 128 +custom_meshtastic_hw_model_slug = MESH_TRACKER_X1 +custom_meshtastic_architecture = nrf52840 +custom_meshtastic_display_name = Seeed SenseCAP Mesh-Tracker-X1 +custom_meshtastic_actively_supported = true + +extends = nrf52840_base +board = mesh-tracker-x1 +board_level = pr +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/seeed_mesh_tracker_X1 + -Isrc/platform/nrf52/softdevice + -Isrc/platform/nrf52/softdevice/nrf52 + -DMESH_TRACKER_X1 + -DCONFIG_NFCT_PINS_AS_GPIOS=1 + -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL=1 + -DMESHTASTIC_EXCLUDE_CANNEDMESSAGES=1 + -DMESHTASTIC_EXCLUDE_SCREEN=1 + -DMESHTASTIC_EXCLUDE_DETECTIONSENSOR=1 + -DMESHTASTIC_EXCLUDE_WIFI=1 +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_mesh_tracker_X1> +lib_deps = + ${nrf52840_base.lib_deps} + adafruit/Adafruit DRV2605 Library@1.2.4 + +debug_tool = jlink +; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) +upload_protocol = nrfutil diff --git a/variants/nrf52840/seeed_mesh_tracker_X1/rfswitch.h b/variants/nrf52840/seeed_mesh_tracker_X1/rfswitch.h new file mode 100644 index 0000000000..a007b37801 --- /dev/null +++ b/variants/nrf52840/seeed_mesh_tracker_X1/rfswitch.h @@ -0,0 +1,9 @@ +#include "RadioLib.h" +#include "nrf.h" + +static const uint32_t lr2021_rfswitch_dio_pins[] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t lr2021_rfswitch_table[] = { + {LR2021::MODE_STBY, {}}, {LR2021::MODE_RX, {}}, {LR2021::MODE_TX, {}}, + {LR2021::MODE_RX_HF, {}}, {LR2021::MODE_TX_HF, {}}, END_OF_MODE_TABLE, +}; diff --git a/variants/nrf52840/seeed_mesh_tracker_X1/variant.cpp b/variants/nrf52840/seeed_mesh_tracker_X1/variant.cpp new file mode 100644 index 0000000000..2ad8b92b89 --- /dev/null +++ b/variants/nrf52840/seeed_mesh_tracker_X1/variant.cpp @@ -0,0 +1,71 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "DebugConfiguration.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); + + pinMode(PIN_BAT_ADC_EN, OUTPUT); + digitalWrite(PIN_BAT_ADC_EN, HIGH); + + pinMode(PIN_LED1, OUTPUT); + digitalWrite(PIN_LED1, LOW); + + pinMode(PIN_LED2, OUTPUT); + digitalWrite(PIN_LED2, LOW); + + pinMode(PIN_LED3, OUTPUT); + digitalWrite(PIN_LED3, LOW); + + pinMode(PIN_DRV_EN, OUTPUT); + digitalWrite(PIN_DRV_EN, LOW); + + pinMode(PIN_RTC_EN, OUTPUT); + digitalWrite(PIN_RTC_EN, LOW); + + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, LOW); + + pinMode(GPS_VRTC_EN, OUTPUT); + digitalWrite(GPS_VRTC_EN, HIGH); + + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, LOW); + + pinMode(GPS_SLEEP_INT, OUTPUT); + digitalWrite(GPS_SLEEP_INT, HIGH); + + pinMode(GPS_RTC_INT, OUTPUT); + digitalWrite(GPS_RTC_INT, LOW); +} diff --git a/variants/nrf52840/seeed_mesh_tracker_X1/variant.h b/variants/nrf52840/seeed_mesh_tracker_X1/variant.h new file mode 100644 index 0000000000..b9882d7198 --- /dev/null +++ b/variants/nrf52840/seeed_mesh_tracker_X1/variant.h @@ -0,0 +1,172 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_MESH_TRACKER_X1_ +#define _VARIANT_MESH_TRACKER_X1_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// Use the native nrf52 usb power detection +#define NRF_APM + +// PIN_3V3_EN use in deep sleep , as power pin. +#define PIN_3V3_EN (32 + 7) // P1.7, Power to Sensors +#define PIN_BAT_ADC_EN (32 + 6) // P1.6, Power to battery ADC +#define PIN_RTC_EN (0 + 9) // P0.9, Power to RTC + +#define PIN_LED1 (0 + 3) // P0.3, red led +#define PIN_LED2 (0 + 24) // P0.24, green led +#define PIN_LED3 (0 + 28) // P0.28, blue led +#define LED_POWER PIN_LED2 +#define LED_RED PIN_LED1 +#define LED_BLUE -1 // Actually green +#define LED_STATE_ON 1 // State when LED is lit +#define LED_POWER_CRITICAL LED_RED // red LED doubles as the low-battery/critical indicator + +// ARCH_NRF52 auto define HAS_BUTTON 1 +#define HAS_BUTTON 1 +#define BUTTON_PIN (0 + 6) // P0.06 +#define BUTTON_ACTIVE_LOW false +#define BUTTON_ACTIVE_PULLUP false +#define BUTTON_SENSE_TYPE 0x5 // enable input pull-down + +/**************** Sensor *******************/ + +#define HAS_WIRE 1 +#define WIRE_INTERFACES_COUNT 1 // have 1 I2C interface, Wire + +#define PIN_WIRE_SDA (32 + 15) // P1.15 +#define PIN_WIRE_SCL (32 + 14) // P1.14 +#define I2C_NO_RESCAN // I2C is a bit finicky, don't scan too much + +#define HAS_DRV2605 1 // haptic driver +#define DRV2605_USE_LRA +#define PIN_DRV_EN (32 + 5) // P1.05, Power to haptic driver + +#define HAS_SPA06 1 + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (0 + 14) // P0.14 +#define PIN_SERIAL1_TX (0 + 13) // P0.13 + +#define PIN_SERIAL2_RX (0 + 17) // P0.17 +#define PIN_SERIAL2_TX (0 + 16) // P0.16 + +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (32 + 8) // P1.08 +#define PIN_SPI_MOSI (32 + 9) // P1.09 +#define PIN_SPI_SCK (0 + 11) // P0.11 +#define PIN_SPI_NSS (0 + 12) // P0.12 + +#define LORA_RESET (32 + 10) // P1.10 // RST +#define LORA_DIO1 (32 + 1) // P1.01 // IRQ +#define LORA_DIO2 (0 + 7) // P0.07 // BUSY +#define LORA_SCK PIN_SPI_SCK +#define LORA_MISO PIN_SPI_MISO +#define LORA_MOSI PIN_SPI_MOSI +#define LORA_CS PIN_SPI_NSS + +// supported modules list +#define USE_LR2021 +#define IRQ_DIO_NUM 8 + +#define LR2021_IRQ_PIN LORA_DIO1 +#define LR2021_NRESET_PIN LORA_RESET +#define LR2021_BUSY_PIN LORA_DIO2 +#define LR2021_SPI_NSS_PIN LORA_CS +#define LR2021_SPI_SCK_PIN LORA_SCK +#define LR2021_SPI_MOSI_PIN LORA_MOSI +#define LR2021_SPI_MISO_PIN LORA_MISO + +#define LR2021_DIO3_TCXO_VOLTAGE 1.6 +// #define LR2021_DIO_AS_RF_SWITCH + +// GPS +#define HAS_GPS 1 +#define GNSS_AIROHA +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +#define GPS_BAUDRATE 115200 +#define GPS_PROBETRIES 8 + +#define PIN_GPS_EN (32 + 11) // P1.11 +#define GPS_EN_ACTIVE HIGH + +#define PIN_GPS_RESET (0 + 8) // P0.8 +#define GPS_RESET_MODE HIGH + +#define GPS_VRTC_EN (32 + 13) // P1.13, always high +#define GPS_SLEEP_INT (0 + 30) // P0.30, always high +#define GPS_RTC_INT (0 + 29) // P0.29, normal is LOW, wake by HIGH + +#define BATTERY_PIN 2 // P0.02/AIN0, BAT_ADC +#define BATTERY_IMMUTABLE +#define ADC_MULTIPLIER (2.0F) +// P0.04/AIN2 is VCC_ADC, P0.05/AIN3 is CHARGER_DET, P1.03 is CHARGE_STA, P1.04 is CHARGE_DONE + +#define EXT_CHRG_DETECT (32 + 3) // P1.03 +#define EXT_CHRG_DETECT_VALUE LOW +// #define EXT_IS_CHRGD (32 + 4) // P1.04 +// #define EXT_IS_CHRGD_VALUE LOW +#define EXT_PWR_DETECT (0 + 5) // P0.05 + +#define ADC_RESOLUTION 14 +#define BATTERY_SENSE_RESOLUTION_BITS 12 + +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 + +#define OCV_ARRAY 4290, 4118, 4002, 3889, 3798, 3728, 3677, 3643, 3594, 3500, 3100 + +// Buzzer +#define PIN_BUZZER (0 + 25) // P0.25, pwm output + +#define HAS_SCREEN 0 + +#ifdef __cplusplus +} +#endif +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif // _VARIANT_MESH_TRACKER_X1_