diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index af8111a63..ca9f1d37f 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -139,7 +139,8 @@ jobs: contents: write pull-requests: write runs-on: ubuntu-latest - needs: [ + needs: + [ build-esp32, build-esp32-s3, build-esp32-c3, @@ -148,7 +149,7 @@ jobs: build-stm32, package-raspbian, package-raspbian-armv7l, - # package-native, + package-native, ] steps: - name: Checkout code diff --git a/boards/seeed-xiao-s3.json b/boards/seeed-xiao-s3.json new file mode 100644 index 000000000..0b7b432a0 --- /dev/null +++ b/boards/seeed-xiao-s3.json @@ -0,0 +1,41 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=0" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x2886", "0x0059"]], + "mcu": "esp32s3", + "variant": "seeed-xiao-s3" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "seeed-xiao-s3", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 8388608, + "maximum_size": 8388608, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.seeedstudio.com/XIAO-ESP32S3-Sense-p-5639.html", + "vendor": "Seeed Studio" +} diff --git a/platformio.ini b/platformio.ini index 9b869a036..b3898ba93 100644 --- a/platformio.ini +++ b/platformio.ini @@ -136,6 +136,7 @@ lib_deps = adafruit/Adafruit MCP9808 Library@^2.0.0 adafruit/Adafruit INA260 Library@^1.5.0 adafruit/Adafruit INA219@^1.2.0 + adafruit/Adafruit MAX1704X@^1.0.3 adafruit/Adafruit SHTC3 Library@^1.0.0 adafruit/Adafruit LPS2X@^2.0.4 adafruit/Adafruit SHT31 Library@^2.2.2 @@ -148,6 +149,7 @@ lib_deps = adafruit/Adafruit SHT4x Library@^1.0.4 adafruit/Adafruit TSL2591 Library@^1.4.5 sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@^1.0.5 + sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@^1.2.13 ClosedCube OPT3001@^1.1.2 emotibit/EmotiBit MLX90632@^1.0.8 dfrobot/DFRobot_RTU@^1.0.3 @@ -161,4 +163,4 @@ lib_deps = https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee - https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 + https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1 \ No newline at end of file diff --git a/protobufs b/protobufs index 9651aa59e..6ac91926c 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 9651aa59eaa3b54d801b9c251efcfe842790db32 +Subproject commit 6ac91926c201c15c429cb5d41684f0f40cb7dce8 diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h deleted file mode 100644 index 629d63c6a..000000000 --- a/src/AccelerometerThread.h +++ /dev/null @@ -1,319 +0,0 @@ -#pragma once -#include "configuration.h" - -#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR - -#include "PowerFSM.h" -#include "concurrency/OSThread.h" -#include "main.h" -#include "power.h" - -#include -#include -#include -#ifdef STK8XXX_INT -#include -#endif -#include -#include -#include -#ifdef RAK_4631 -#include "Fusion/Fusion.h" -#include "graphics/Screen.h" -#include "graphics/ScreenFonts.h" -#include -#endif - -#define ACCELEROMETER_CHECK_INTERVAL_MS 100 -#define ACCELEROMETER_CLICK_THRESHOLD 40 - -volatile static bool STK_IRQ; - -static inline int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) -{ - Wire.beginTransmission(address); - Wire.write(reg); - Wire.endTransmission(); - Wire.requestFrom((uint8_t)address, (uint8_t)len); - uint8_t i = 0; - while (Wire.available()) { - data[i++] = Wire.read(); - } - return 0; // Pass -} - -static inline int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) -{ - Wire.beginTransmission(address); - Wire.write(reg); - Wire.write(data, len); - return (0 != Wire.endTransmission()); -} - -class AccelerometerThread : public concurrency::OSThread -{ - public: - explicit AccelerometerThread(ScanI2C::DeviceType type) : OSThread("AccelerometerThread") - { - if (accelerometer_found.port == ScanI2C::I2CPort::NO_I2C) { - LOG_DEBUG("AccelerometerThread disabling due to no sensors found\n"); - disable(); - return; - } - acceleremoter_type = type; -#ifndef RAK_4631 - if (!config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) { - LOG_DEBUG("AccelerometerThread disabling due to no interested configurations\n"); - disable(); - return; - } -#endif - init(); - } - - void start() - { - init(); - setIntervalFromNow(0); - }; - - protected: - int32_t runOnce() override - { - canSleep = true; // Assume we should not keep the board awake - - if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.getMotionInterruptStatus()) { - wakeScreen(); - } else if (acceleremoter_type == ScanI2C::DeviceType::STK8BAXX && STK_IRQ) { - STK_IRQ = false; - if (config.display.wake_on_tap_or_motion) { - wakeScreen(); - } - } else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.getClick() > 0) { - uint8_t click = lis.getClick(); - if (!config.device.double_tap_as_button_press) { - wakeScreen(); - } - - if (config.device.double_tap_as_button_press && (click & 0x20)) { - buttonPress(); - return 500; - } - } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) { - if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) { - wakeScreen(); - return 500; - } -#ifdef RAK_4631 - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) { - sBmx160SensorData_t magAccel; - sBmx160SensorData_t gAccel; - - /* Get a new sensor event */ - bmx160.getAllData(&magAccel, NULL, &gAccel); - - // expirimental calibrate routine. Limited to between 10 and 30 seconds after boot - if (millis() > 12 * 1000 && millis() < 30 * 1000) { - if (!showingScreen) { - showingScreen = true; - screen->startAlert((FrameCallback)drawFrameCalibration); - } - if (magAccel.x > highestX) - highestX = magAccel.x; - if (magAccel.x < lowestX) - lowestX = magAccel.x; - if (magAccel.y > highestY) - highestY = magAccel.y; - if (magAccel.y < lowestY) - lowestY = magAccel.y; - if (magAccel.z > highestZ) - highestZ = magAccel.z; - if (magAccel.z < lowestZ) - lowestZ = magAccel.z; - } else if (showingScreen && millis() >= 30 * 1000) { - showingScreen = false; - screen->endAlert(); - } - - int highestRealX = highestX - (highestX + lowestX) / 2; - - magAccel.x -= (highestX + lowestX) / 2; - magAccel.y -= (highestY + lowestY) / 2; - magAccel.z -= (highestZ + lowestZ) / 2; - FusionVector ga, ma; - ga.axis.x = -gAccel.x; // default location for the BMX160 is on the rear of the board - ga.axis.y = -gAccel.y; - ga.axis.z = gAccel.z; - ma.axis.x = -magAccel.x; - ma.axis.y = -magAccel.y; - ma.axis.z = magAccel.z * 3; - - // If we're set to one of the inverted positions - if (config.display.compass_orientation > meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270) { - ma = FusionAxesSwap(ma, FusionAxesAlignmentNXNYPZ); - ga = FusionAxesSwap(ga, FusionAxesAlignmentNXNYPZ); - } - - float heading = FusionCompassCalculateHeading(FusionConventionNed, ga, ma); - - switch (config.display.compass_orientation) { - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0_INVERTED: - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0: - break; - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90: - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90_INVERTED: - heading += 90; - break; - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180: - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180_INVERTED: - heading += 180; - break; - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270: - case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED: - heading += 270; - break; - } - - screen->setHeading(heading); - -#endif - } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) { - wakeScreen(); - return 500; - } - - return ACCELEROMETER_CHECK_INTERVAL_MS; - } - - private: - void init() - { - LOG_DEBUG("AccelerometerThread initializing\n"); - - if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.begin(accelerometer_found.address)) { - LOG_DEBUG("MPU6050 initializing\n"); - // setup motion detection - mpu.setHighPassFilter(MPU6050_HIGHPASS_0_63_HZ); - mpu.setMotionDetectionThreshold(1); - mpu.setMotionDetectionDuration(20); - mpu.setInterruptPinLatch(true); // Keep it latched. Will turn off when reinitialized. - mpu.setInterruptPinPolarity(true); -#ifdef STK8XXX_INT - } else if (acceleremoter_type == ScanI2C::DeviceType::STK8BAXX && stk8baxx.STK8xxx_Initialization(STK8xxx_VAL_RANGE_2G)) { - STK_IRQ = false; - LOG_DEBUG("STX8BAxx initialized\n"); - stk8baxx.STK8xxx_Anymotion_init(); - pinMode(STK8XXX_INT, INPUT_PULLUP); - attachInterrupt( - digitalPinToInterrupt(STK8XXX_INT), [] { STK_IRQ = true; }, RISING); -#endif - } else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.begin(accelerometer_found.address)) { - LOG_DEBUG("LIS3DH initializing\n"); - lis.setRange(LIS3DH_RANGE_2_G); - // Adjust threshold, higher numbers are less sensitive - lis.setClick(config.device.double_tap_as_button_press ? 2 : 1, ACCELEROMETER_CLICK_THRESHOLD); - } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && - bmaSensor.begin(accelerometer_found.address, &readRegister, &writeRegister)) { - LOG_DEBUG("BMA423 initializing\n"); - bmaSensor.configAccelerometer(bmaSensor.RANGE_2G, bmaSensor.ODR_100HZ, bmaSensor.BW_NORMAL_AVG4, - bmaSensor.PERF_CONTINUOUS_MODE); - bmaSensor.enableAccelerometer(); - bmaSensor.configInterrupt(BMA4_LEVEL_TRIGGER, BMA4_ACTIVE_HIGH, BMA4_PUSH_PULL, BMA4_OUTPUT_ENABLE, - BMA4_INPUT_DISABLE); - -#ifdef BMA423_INT - pinMode(BMA4XX_INT, INPUT); - attachInterrupt( - BMA4XX_INT, - [] { - // Set interrupt to set irq value to true - BMA_IRQ = true; - }, - RISING); // Select the interrupt mode according to the actual circuit -#endif - -#ifdef T_WATCH_S3 - // Need to raise the wrist function, need to set the correct axis - bmaSensor.setReampAxes(bmaSensor.REMAP_TOP_LAYER_RIGHT_CORNER); -#else - bmaSensor.setReampAxes(bmaSensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER); -#endif - // bmaSensor.enableFeature(bmaSensor.FEATURE_STEP_CNTR, true); - bmaSensor.enableFeature(bmaSensor.FEATURE_TILT, true); - bmaSensor.enableFeature(bmaSensor.FEATURE_WAKEUP, true); - // bmaSensor.resetPedometer(); - - // Turn on feature interrupt - bmaSensor.enablePedometerIRQ(); - bmaSensor.enableTiltIRQ(); - // It corresponds to isDoubleClick interrupt - bmaSensor.enableWakeupIRQ(); -#ifdef RAK_4631 - } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) { - bmx160.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); // set output data rate - -#endif - } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.begin_I2C(accelerometer_found.address)) { - LOG_DEBUG("LSM6DS3 initializing\n"); - // Default threshold of 2G, less sensitive options are 4, 8 or 16G - lsm.setAccelRange(LSM6DS_ACCEL_RANGE_2_G); -#ifndef LSM6DS3_WAKE_THRESH -#define LSM6DS3_WAKE_THRESH 20 -#endif - lsm.enableWakeup(config.display.wake_on_tap_or_motion, 1, LSM6DS3_WAKE_THRESH); - // Duration is number of occurances needed to trigger, higher threshold is less sensitive - } - } - void wakeScreen() - { - if (powerFSM.getState() == &stateDARK) { - LOG_INFO("Tap or motion detected. Turning on screen\n"); - powerFSM.trigger(EVENT_INPUT); - } - } - - void buttonPress() - { - LOG_DEBUG("Double-tap detected. Firing button press\n"); - powerFSM.trigger(EVENT_PRESS); - } - - ScanI2C::DeviceType acceleremoter_type; - Adafruit_MPU6050 mpu; - Adafruit_LIS3DH lis; -#ifdef STK8XXX_INT - STK8xxx stk8baxx; -#endif - Adafruit_LSM6DS3TRC lsm; - SensorBMA423 bmaSensor; - bool BMA_IRQ = false; -#ifdef RAK_4631 - bool showingScreen = false; - RAK_BMX160 bmx160; - float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; - - static void drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) - { - int x_offset = display->width() / 2; - int y_offset = display->height() <= 80 ? 0 : 32; - display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_MEDIUM); - display->drawString(x, y, "Calibrating\nCompass"); - int16_t compassX = 0, compassY = 0; - uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); - - // coordinates for the center of the compass/circle - if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { - compassX = x + display->getWidth() - compassDiam / 2 - 5; - compassY = y + display->getHeight() / 2; - } else { - compassX = x + display->getWidth() - compassDiam / 2 - 5; - compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; - } - display->drawCircle(compassX, compassY, compassDiam / 2); - screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180); - } -#endif -}; - -#endif \ No newline at end of file diff --git a/src/Power.cpp b/src/Power.cpp index b3a67abd5..bbb6cd2c3 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -77,6 +77,15 @@ INA219Sensor ina219Sensor; INA3221Sensor ina3221Sensor; #endif +#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#include "modules/Telemetry/Sensor/MAX17048Sensor.h" +#include +extern std::pair nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1]; +#if HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY) +MAX17048Sensor max17048Sensor; +#endif +#endif + #if HAS_RAKPROT && !defined(ARCH_PORTDUINO) RAK9154Sensor rak9154Sensor; #endif @@ -167,6 +176,7 @@ static void adcDisable() */ class AnalogBatteryLevel : public HasBatteryLevel { + public: /** * Battery state of charge, from 0 to 100 or -1 for unknown */ @@ -553,7 +563,12 @@ bool Power::analogInit() */ bool Power::setup() { - bool found = axpChipInit() || analogInit(); + // initialise one power sensor (only) + bool found = axpChipInit(); + if (!found) + found = lipoInit(); + if (!found) + found = analogInit(); #ifdef NRF_APM found = true; @@ -1044,4 +1059,106 @@ bool Power::axpChipInit() #else return false; #endif -} \ No newline at end of file +} + +#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) + +/** + * Wrapper class for an I2C MAX17048 Lipo battery sensor. If there is no + * I2C sensor present, the class falls back to analog battery sensing + */ +class LipoBatteryLevel : public AnalogBatteryLevel +{ + private: + MAX17048Singleton *max17048 = nullptr; + + public: + /** + * Init the I2C MAX17048 Lipo battery level sensor + */ + bool runOnce() + { + if (max17048 == nullptr) { + max17048 = MAX17048Singleton::GetInstance(); + } + + // try to start if the sensor has been detected + if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX17048].first != 0) { + return max17048->runOnce(nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX17048].second); + } + return false; + } + + /** + * Battery state of charge, from 0 to 100 or -1 for unknown + */ + virtual int getBatteryPercent() override + { + if (!max17048->isInitialised()) + return AnalogBatteryLevel::getBatteryPercent(); + return max17048->getBusBatteryPercent(); + } + + /** + * The raw voltage of the battery in millivolts, or NAN if unknown + */ + virtual uint16_t getBattVoltage() override + { + if (!max17048->isInitialised()) + return AnalogBatteryLevel::getBattVoltage(); + return max17048->getBusVoltageMv(); + } + + /** + * return true if there is a battery installed in this unit + */ + virtual bool isBatteryConnect() override + { + if (!max17048->isInitialised()) + return AnalogBatteryLevel::isBatteryConnect(); + return max17048->isBatteryConnected(); + } + + /** + * return true if there is an external power source detected + */ + virtual bool isVbusIn() override + { + if (!max17048->isInitialised()) + return AnalogBatteryLevel::isVbusIn(); + return max17048->isExternallyPowered(); + } + + /** + * return true if the battery is currently charging + */ + virtual bool isCharging() override + { + if (!max17048->isInitialised()) + return AnalogBatteryLevel::isCharging(); + return max17048->isBatteryCharging(); + } +}; + +LipoBatteryLevel lipoLevel; + +/** + * Init the Lipo battery level sensor + */ +bool Power::lipoInit() +{ + bool result = lipoLevel.runOnce(); + LOG_DEBUG("Power::lipoInit lipo sensor is %s\n", result ? "ready" : "not ready yet"); + batteryLevel = &lipoLevel; + return true; +} + +#else +/** + * The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel + */ +bool Power::lipoInit() +{ + return false; +} +#endif \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 0b470eef3..55206bf4e 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -122,6 +122,7 @@ along with this program. If not, see . #define INA_ADDR_ALTERNATE 0x41 #define INA_ADDR_WAVESHARE_UPS 0x43 #define INA3221_ADDR 0x42 +#define MAX1704X_ADDR 0x36 #define QMC6310_ADDR 0x1C #define QMI8658_ADDR 0x6B #define QMC5883L_ADDR 0x0D @@ -150,6 +151,8 @@ along with this program. If not, see . #define BMA423_ADDR 0x19 #define LSM6DS3_ADDR 0x6A #define BMX160_ADDR 0x69 +#define ICM20948_ADDR 0x69 +#define ICM20948_ADDR_ALT 0x68 // ----------------------------------------------------------------------------- // LED diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index eaba62a78..a9d70edaa 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -37,8 +37,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const { - ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX}; - return firstOfOrNONE(6, types); + ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948}; + return firstOfOrNONE(7, types); } ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 090b1a968..3b49026ce 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -28,6 +28,7 @@ class ScanI2C INA260, INA219, INA3221, + MAX17048, MCP9808, SHT31, SHT4X, @@ -56,7 +57,8 @@ class ScanI2C DFROBOT_LARK, NAU7802, FT6336U, - STK8BAXX + STK8BAXX, + ICM20948 } DeviceType; // typedef uint8_t DeviceAddress; @@ -67,8 +69,9 @@ class ScanI2C } I2CPort; typedef struct DeviceAddress { - I2CPort port; - uint8_t address; + // set default values for ADDRESS_NONE + I2CPort port = I2CPort::NO_I2C; + uint8_t address = 0; explicit DeviceAddress(I2CPort port, uint8_t address); DeviceAddress(); @@ -119,4 +122,4 @@ class ScanI2C private: bool shouldSuppressScreen = false; -}; +}; \ No newline at end of file diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index c0e70503b..43340765a 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -382,10 +382,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L Highrate 3-Axis magnetic sensor found\n") SCAN_SIMPLE_CASE(HMC5883L_ADDR, HMC5883L, "HMC5883L 3-Axis digital compass found\n") - SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n") - SCAN_SIMPLE_CASE(MPU6050_ADDR, MPU6050, "MPU6050 accelerometer found\n"); - SCAN_SIMPLE_CASE(BMX160_ADDR, BMX160, "BMX160 accelerometer found\n"); SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n"); SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address); SCAN_SIMPLE_CASE(TCA9535_ADDR, TCA9535, "TCA9535 I2C expander found\n"); @@ -396,6 +393,25 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found\n"); SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n"); SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U touchscreen found\n"); + SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048 lipo fuel gauge found\n"); + + case ICM20948_ADDR: // same as BMX160_ADDR + case ICM20948_ADDR_ALT: // same as MPU6050_ADDR + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); + if (registerValue == 0xEA) { + type = ICM20948; + LOG_INFO("ICM20948 9-dof motion processor found\n"); + break; + } else if (addr.address == BMX160_ADDR) { + type = BMX160; + LOG_INFO("BMX160 accelerometer found\n"); + break; + } else { + type = MPU6050; + LOG_INFO("MPU6050 accelerometer found\n"); + break; + } + break; default: LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address); diff --git a/src/main.cpp b/src/main.cpp index df4b622f1..c2d275177 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -101,8 +101,8 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr; #include "AmbientLightingThread.h" #include "PowerFSMThread.h" -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "AccelerometerThread.h" +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C +#include "motion/AccelerometerThread.h" AccelerometerThread *accelerometerThread = nullptr; #endif @@ -555,6 +555,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT31, meshtastic_TelemetrySensorType_SHT31) @@ -573,6 +574,7 @@ void setup() SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10) SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK) + SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948) i2cScanner.reset(); #endif @@ -624,7 +626,13 @@ void setup() buttonThread = new ButtonThread(); #endif - playStartMelody(); + // only play start melody when role is not tracker or sensor + if (config.power.is_power_saving == true && (config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || + config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR)) + LOG_DEBUG("Tracker/Sensor: Skipping start melody\n"); + else + playStartMelody(); // fixed screen override? if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO) @@ -640,7 +648,7 @@ void setup() #endif #if !MESHTASTIC_EXCLUDE_I2C -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) if (acc_info.type != ScanI2C::DeviceType::NONE) { accelerometerThread = new AccelerometerThread(acc_info.type); } diff --git a/src/main.h b/src/main.h index 52a3fff1f..5722f7cf0 100644 --- a/src/main.h +++ b/src/main.h @@ -56,8 +56,8 @@ extern AudioThread *audioThread; // Global Screen singleton. extern graphics::Screen *screen; -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "AccelerometerThread.h" +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C +#include "motion/AccelerometerThread.h" extern AccelerometerThread *accelerometerThread; #endif @@ -86,4 +86,4 @@ void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(), rp2040Setup(), clearB meshtastic_DeviceMetadata getDeviceMetadata(); // We default to 4MHz SPI, SPI mode 0 -extern SPISettings spiSettings; +extern SPISettings spiSettings; \ No newline at end of file diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index 9ba69c612..b323ddf07 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -205,6 +205,10 @@ typedef enum _meshtastic_HardwareModel { meshtastic_HardwareModel_RPI_PICO2 = 79, /* M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/ */ meshtastic_HardwareModel_M5STACK_CORES3 = 80, + /* Seeed XIAO S3 DK */ + meshtastic_HardwareModel_SEEED_XIAO_S3 = 81, + /* Nordic nRF52840+Semtech SX1262 LoRa BLE Combo Module. nRF52840+SX1262 MS24SF1 */ + meshtastic_HardwareModel_MS24SF1 = 82, /* ------------------------------------------------------------------------------------------------------------------------------------------ Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits. ------------------------------------------------------------------------------------------------------------------------------------------ */ diff --git a/src/mesh/generated/meshtastic/module_config.pb.cpp b/src/mesh/generated/meshtastic/module_config.pb.cpp index 88a771d5b..e1c33e2c1 100644 --- a/src/mesh/generated/meshtastic/module_config.pb.cpp +++ b/src/mesh/generated/meshtastic/module_config.pb.cpp @@ -60,3 +60,4 @@ PB_BIND(meshtastic_RemoteHardwarePin, meshtastic_RemoteHardwarePin, AUTO) + diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 48048b054..389a0db8d 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -34,8 +34,8 @@ #include "modules/PositionModule.h" #endif -#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR -#include "AccelerometerThread.h" +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C +#include "motion/AccelerometerThread.h" #endif AdminModule *adminModule; diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp index f94f7956b..969529881 100644 --- a/src/modules/Telemetry/EnvironmentTelemetry.cpp +++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp @@ -83,7 +83,7 @@ int32_t EnvironmentTelemetryModule::runOnce() */ // moduleConfig.telemetry.environment_measurement_enabled = 1; - // moduleConfig.telemetry.environment_screen_enabled = 1; + // moduleConfig.telemetry.environment_screen_enabled = 1; // moduleConfig.telemetry.environment_update_interval = 15; if (!(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) { @@ -144,6 +144,8 @@ int32_t EnvironmentTelemetryModule::runOnce() result = mlx90632Sensor.runOnce(); if (nau7802Sensor.hasSensor()) result = nau7802Sensor.runOnce(); + if (max17048Sensor.hasSensor()) + result = max17048Sensor.runOnce(); #endif } return result; @@ -156,6 +158,7 @@ int32_t EnvironmentTelemetryModule::runOnce() result = bme680Sensor.runTrigger(); } + uint32_t now = millis(); if (((lastSentToMesh == 0) || !Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled( moduleConfig.telemetry.environment_update_interval, @@ -397,6 +400,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m m->variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity; } } + if (max17048Sensor.hasSensor()) { + valid = valid && max17048Sensor.getMetrics(m); + hasSensor = true; + } #endif return valid && hasSensor; @@ -587,6 +594,11 @@ AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule if (result != AdminMessageHandleResult::NOT_HANDLED) return result; } + if (max17048Sensor.hasSensor()) { + result = max17048Sensor.handleAdminMessage(mp, request, response); + if (result != AdminMessageHandleResult::NOT_HANDLED) + return result; + } return result; } diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index a493042a0..6130c2c83 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -60,6 +60,8 @@ int32_t PowerTelemetryModule::runOnce() result = ina260Sensor.runOnce(); if (ina3221Sensor.hasSensor() && !ina3221Sensor.isInitialized()) result = ina3221Sensor.runOnce(); + if (max17048Sensor.hasSensor() && !max17048Sensor.isInitialized()) + result = max17048Sensor.runOnce(); } return result; #else @@ -128,19 +130,23 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s return; } + // Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags display->setFont(FONT_SMALL); - String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C"; display->drawString(x, y += _fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); - if (lastMeasurement.variant.power_metrics.ch1_voltage != 0) { + if (lastMeasurement.variant.power_metrics.has_ch1_voltage || lastMeasurement.variant.power_metrics.has_ch1_current) { display->drawString(x, y += _fontHeight(FONT_SMALL), - "Ch 1 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 0) + "V / " + - String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA"); + "Ch1 Volt: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 2) + + "V / Curr: " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA"); + } + if (lastMeasurement.variant.power_metrics.has_ch2_voltage || lastMeasurement.variant.power_metrics.has_ch2_current) { display->drawString(x, y += _fontHeight(FONT_SMALL), - "Ch 2 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 0) + "V / " + - String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA"); + "Ch2 Volt: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 2) + + "V / Curr: " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA"); + } + if (lastMeasurement.variant.power_metrics.has_ch3_voltage || lastMeasurement.variant.power_metrics.has_ch3_current) { display->drawString(x, y += _fontHeight(FONT_SMALL), - "Ch 3 Volt/Cur: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 0) + "V / " + - String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA"); + "Ch3 Volt: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 2) + + "V / Curr: " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA"); } } @@ -150,8 +156,8 @@ bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &m #ifdef DEBUG_PORT const char *sender = getSenderShortName(mp); - LOG_INFO("(Received from %s): ch1_voltage=%f, ch1_current=%f, ch2_voltage=%f, ch2_current=%f, " - "ch3_voltage=%f, ch3_current=%f\n", + LOG_INFO("(Received from %s): ch1_voltage=%.1f, ch1_current=%.1f, ch2_voltage=%.1f, ch2_current=%.1f, " + "ch3_voltage=%.1f, ch3_current=%.1f\n", sender, t->variant.power_metrics.ch1_voltage, t->variant.power_metrics.ch1_current, t->variant.power_metrics.ch2_voltage, t->variant.power_metrics.ch2_current, t->variant.power_metrics.ch3_voltage, t->variant.power_metrics.ch3_current); @@ -172,12 +178,7 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) m->time = getTime(); m->which_variant = meshtastic_Telemetry_power_metrics_tag; - m->variant.power_metrics.ch1_voltage = 0; - m->variant.power_metrics.ch1_current = 0; - m->variant.power_metrics.ch2_voltage = 0; - m->variant.power_metrics.ch2_current = 0; - m->variant.power_metrics.ch3_voltage = 0; - m->variant.power_metrics.ch3_current = 0; + m->variant.power_metrics = meshtastic_PowerMetrics_init_zero; #if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) if (ina219Sensor.hasSensor()) valid = ina219Sensor.getMetrics(m); @@ -185,6 +186,8 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m) valid = ina260Sensor.getMetrics(m); if (ina3221Sensor.hasSensor()) valid = ina3221Sensor.getMetrics(m); + if (max17048Sensor.hasSensor()) + valid = max17048Sensor.getMetrics(m); #endif return valid; diff --git a/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp b/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp new file mode 100644 index 000000000..a3890df43 --- /dev/null +++ b/src/modules/Telemetry/Sensor/MAX17048Sensor.cpp @@ -0,0 +1,176 @@ +#include "MAX17048Sensor.h" + +#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) + +MAX17048Singleton *MAX17048Singleton::GetInstance() +{ + if (pinstance == nullptr) { + pinstance = new MAX17048Singleton(); + } + return pinstance; +} + +MAX17048Singleton::MAX17048Singleton() {} + +MAX17048Singleton::~MAX17048Singleton() {} + +MAX17048Singleton *MAX17048Singleton::pinstance{nullptr}; + +bool MAX17048Singleton::runOnce(TwoWire *theWire) +{ + initialized = begin(theWire); + LOG_DEBUG("MAX17048Sensor::runOnce %s\n", initialized ? "began ok" : "begin failed"); + return initialized; +} + +bool MAX17048Singleton::isBatteryCharging() +{ + float volts = cellVoltage(); + if (isnan(volts)) { + LOG_DEBUG("MAX17048Sensor::isBatteryCharging is not connected\n"); + return 0; + } + + MAX17048ChargeSample sample; + sample.chargeRate = chargeRate(); // charge/discharge rate in percent/hr + sample.cellPercent = cellPercent(); // state of charge in percent 0 to 100 + chargeSamples.push(sample); // save a sample into a fifo buffer + + // Keep the fifo buffer trimmed + while (chargeSamples.size() > MAX17048_CHARGING_SAMPLES) + chargeSamples.pop(); + + // Based on the past n samples, is the lipo charging, discharging or idle + if (chargeSamples.front().chargeRate > MAX17048_CHARGING_MINIMUM_RATE && + chargeSamples.back().chargeRate > MAX17048_CHARGING_MINIMUM_RATE) { + if (chargeSamples.front().cellPercent > chargeSamples.back().cellPercent) + chargeState = MAX17048ChargeState::EXPORT; + else if (chargeSamples.front().cellPercent < chargeSamples.back().cellPercent) + chargeState = MAX17048ChargeState::IMPORT; + else + chargeState = MAX17048ChargeState::IDLE; + } else { + chargeState = MAX17048ChargeState::IDLE; + } + + LOG_DEBUG("MAX17048Sensor::isBatteryCharging %s volts: %.3f soc: %.3f rate: %.3f\n", chargeLabels[chargeState], volts, + sample.cellPercent, sample.chargeRate); + return chargeState == MAX17048ChargeState::IMPORT; +} + +uint16_t MAX17048Singleton::getBusVoltageMv() +{ + float volts = cellVoltage(); + if (isnan(volts)) { + LOG_DEBUG("MAX17048Sensor::getBusVoltageMv is not connected\n"); + return 0; + } + LOG_DEBUG("MAX17048Sensor::getBusVoltageMv %.3fmV\n", volts); + return (uint16_t)(volts * 1000.0f); +} + +uint8_t MAX17048Singleton::getBusBatteryPercent() +{ + float soc = cellPercent(); + LOG_DEBUG("MAX17048Sensor::getBusBatteryPercent %.1f%%\n", soc); + return clamp(static_cast(round(soc)), static_cast(0), static_cast(100)); +} + +uint16_t MAX17048Singleton::getTimeToGoSecs() +{ + float rate = chargeRate(); // charge/discharge rate in percent/hr + float soc = cellPercent(); // state of charge in percent 0 to 100 + soc = clamp(soc, 0.0f, 100.0f); // clamp soc between 0 and 100% + float ttg = ((100.0f - soc) / rate) * 3600.0f; // calculate seconds to charge/discharge + LOG_DEBUG("MAX17048Sensor::getTimeToGoSecs %.0f seconds\n", ttg); + return (uint16_t)ttg; +} + +bool MAX17048Singleton::isBatteryConnected() +{ + float volts = cellVoltage(); + if (isnan(volts)) { + LOG_DEBUG("MAX17048Sensor::isBatteryConnected is not connected\n"); + return false; + } + + // if a valid voltage is returned, then battery must be connected + return true; +} + +bool MAX17048Singleton::isExternallyPowered() +{ + float volts = cellVoltage(); + if (isnan(volts)) { + // if the battery is not connected then there must be external power + LOG_DEBUG("MAX17048Sensor::isExternallyPowered battery is\n"); + return true; + } + // if the bus voltage is over MAX17048_BUS_POWER_VOLTS, then the external power + // is assumed to be connected + LOG_DEBUG("MAX17048Sensor::isExternallyPowered %s connected\n", volts >= MAX17048_BUS_POWER_VOLTS ? "is" : "is not"); + return volts >= MAX17048_BUS_POWER_VOLTS; +} + +#if (HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY)) + +MAX17048Sensor::MAX17048Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MAX17048, "MAX17048") {} + +int32_t MAX17048Sensor::runOnce() +{ + if (isInitialized()) { + LOG_INFO("Init sensor: %s is already initialised\n", sensorName); + return true; + } + + LOG_INFO("Init sensor: %s\n", sensorName); + if (!hasSensor()) { + return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS; + } + + // Get a singleton instance and initialise the max17048 + if (max17048 == nullptr) { + max17048 = MAX17048Singleton::GetInstance(); + } + status = max17048->runOnce(nodeTelemetrySensorsMap[sensorType].second); + return initI2CSensor(); +} + +void MAX17048Sensor::setup() {} + +bool MAX17048Sensor::getMetrics(meshtastic_Telemetry *measurement) +{ + LOG_DEBUG("MAX17048Sensor::getMetrics id: %i\n", measurement->which_variant); + + float volts = max17048->cellVoltage(); + if (isnan(volts)) { + LOG_DEBUG("MAX17048Sensor::getMetrics battery is not connected\n"); + return false; + } + + float rate = max17048->chargeRate(); // charge/discharge rate in percent/hr + float soc = max17048->cellPercent(); // state of charge in percent 0 to 100 + soc = clamp(soc, 0.0f, 100.0f); // clamp soc between 0 and 100% + float ttg = (100.0f - soc) / rate; // calculate hours to charge/discharge + + LOG_DEBUG("MAX17048Sensor::getMetrics volts: %.3fV soc: %.1f%% ttg: %.1f hours\n", volts, soc, ttg); + if ((int)measurement->which_variant == meshtastic_Telemetry_power_metrics_tag) { + measurement->variant.power_metrics.has_ch1_voltage = true; + measurement->variant.power_metrics.ch1_voltage = volts; + } else if ((int)measurement->which_variant == meshtastic_Telemetry_device_metrics_tag) { + measurement->variant.device_metrics.has_battery_level = true; + measurement->variant.device_metrics.has_voltage = true; + measurement->variant.device_metrics.battery_level = static_cast(round(soc)); + measurement->variant.device_metrics.voltage = volts; + } + return true; +} + +uint16_t MAX17048Sensor::getBusVoltageMv() +{ + return max17048->getBusVoltageMv(); +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/modules/Telemetry/Sensor/MAX17048Sensor.h b/src/modules/Telemetry/Sensor/MAX17048Sensor.h new file mode 100644 index 000000000..20dca324c --- /dev/null +++ b/src/modules/Telemetry/Sensor/MAX17048Sensor.h @@ -0,0 +1,110 @@ +#pragma once + +#ifndef MAX17048_SENSOR_H +#define MAX17048_SENSOR_H + +#include "configuration.h" + +#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) + +// Samples to store in a buffer to determine if the battery is charging or discharging +#define MAX17048_CHARGING_SAMPLES 3 + +// Threshold to determine if the battery is on charge, in percent/hour +#define MAX17048_CHARGING_MINIMUM_RATE 1.0f + +// Threshold to determine if the board has bus power +#define MAX17048_BUS_POWER_VOLTS 4.195f + +#include "../mesh/generated/meshtastic/telemetry.pb.h" +#include "TelemetrySensor.h" +#include "VoltageSensor.h" + +#include "meshUtils.h" +#include +#include + +struct MAX17048ChargeSample { + float cellPercent; + float chargeRate; +}; + +enum MAX17048ChargeState { IDLE, EXPORT, IMPORT }; + +// Singleton wrapper for the Adafruit_MAX17048 class +class MAX17048Singleton : public Adafruit_MAX17048 +{ + private: + static MAX17048Singleton *pinstance; + bool initialized = false; + std::queue chargeSamples; + MAX17048ChargeState chargeState = IDLE; + const String chargeLabels[3] = {F("idle"), F("export"), F("import")}; + + protected: + MAX17048Singleton(); + ~MAX17048Singleton(); + + public: + // Create a singleton instance (not thread safe) + static MAX17048Singleton *GetInstance(); + + // Singletons should not be cloneable. + MAX17048Singleton(MAX17048Singleton &other) = delete; + + // Singletons should not be assignable. + void operator=(const MAX17048Singleton &) = delete; + + // Initialise the sensor (not thread safe) + virtual bool runOnce(TwoWire *theWire = &Wire); + + // Get the current bus voltage + uint16_t getBusVoltageMv(); + + // Get the state of charge in percent 0 to 100 + uint8_t getBusBatteryPercent(); + + // Calculate the seconds to charge/discharge + uint16_t getTimeToGoSecs(); + + // Returns true if the battery sensor has started + inline virtual bool isInitialised() { return initialized; }; + + // Returns true if the battery is currently on charge (not thread safe) + bool isBatteryCharging(); + + // Returns true if a battery is actually connected + bool isBatteryConnected(); + + // Returns true if there is bus or external power connected + bool isExternallyPowered(); +}; + +#if (HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY)) + +class MAX17048Sensor : public TelemetrySensor, VoltageSensor +{ + private: + MAX17048Singleton *max17048 = nullptr; + + protected: + virtual void setup() override; + + public: + MAX17048Sensor(); + + // Initialise the sensor + virtual int32_t runOnce() override; + + // Get the current bus voltage and state of charge + virtual bool getMetrics(meshtastic_Telemetry *measurement) override; + + // Get the current bus voltage + virtual uint16_t getBusVoltageMv() override; +}; + +#endif + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/AccelerometerThread.h b/src/motion/AccelerometerThread.h new file mode 100755 index 000000000..f9cbb49a1 --- /dev/null +++ b/src/motion/AccelerometerThread.h @@ -0,0 +1,149 @@ +#pragma once +#ifndef _ACCELEROMETER_H_ +#define _ACCELEROMETER_H_ + +#include "configuration.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include "../concurrency/OSThread.h" +#include "BMA423Sensor.h" +#include "BMX160Sensor.h" +#include "ICM20948Sensor.h" +#include "LIS3DHSensor.h" +#include "LSM6DS3Sensor.h" +#include "MPU6050Sensor.h" +#include "MotionSensor.h" +#include "STK8XXXSensor.h" + +extern ScanI2C::DeviceAddress accelerometer_found; + +class AccelerometerThread : public concurrency::OSThread +{ + private: + MotionSensor *sensor = nullptr; + bool isInitialised = false; + + public: + explicit AccelerometerThread(ScanI2C::FoundDevice foundDevice) : OSThread("AccelerometerThread") + { + device = foundDevice; + init(); + } + + explicit AccelerometerThread(ScanI2C::DeviceType type) : AccelerometerThread(ScanI2C::FoundDevice{type, accelerometer_found}) + { + } + + void start() + { + init(); + setIntervalFromNow(0); + }; + + protected: + int32_t runOnce() override + { + // Assume we should not keep the board awake + canSleep = true; + + if (isInitialised) + return sensor->runOnce(); + + return MOTION_SENSOR_CHECK_INTERVAL_MS; + } + + private: + ScanI2C::FoundDevice device; + + void init() + { + if (isInitialised) + return; + + if (device.address.port == ScanI2C::I2CPort::NO_I2C || device.address.address == 0 || device.type == ScanI2C::NONE) { + LOG_DEBUG("AccelerometerThread disabling due to no sensors found\n"); + disable(); + return; + } + +#ifndef RAK_4631 + if (!config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) { + LOG_DEBUG("AccelerometerThread disabling due to no interested configurations\n"); + disable(); + return; + } +#endif + + switch (device.type) { + case ScanI2C::DeviceType::BMA423: + sensor = new BMA423Sensor(device); + break; + case ScanI2C::DeviceType::MPU6050: + sensor = new MPU6050Sensor(device); + break; + case ScanI2C::DeviceType::BMX160: + sensor = new BMX160Sensor(device); + break; + case ScanI2C::DeviceType::LIS3DH: + sensor = new LIS3DHSensor(device); + break; + case ScanI2C::DeviceType::LSM6DS3: + sensor = new LSM6DS3Sensor(device); + break; + case ScanI2C::DeviceType::STK8BAXX: + sensor = new STK8XXXSensor(device); + break; + case ScanI2C::DeviceType::ICM20948: + sensor = new ICM20948Sensor(device); + break; + default: + disable(); + return; + } + + isInitialised = sensor->init(); + if (!isInitialised) { + clean(); + } + LOG_DEBUG("AccelerometerThread::init %s\n", isInitialised ? "ok" : "failed"); + } + + // Copy constructor (not implemented / included to avoid cppcheck warnings) + AccelerometerThread(const AccelerometerThread &other) : OSThread::OSThread("AccelerometerThread") { this->copy(other); } + + // Destructor (included to avoid cppcheck warnings) + virtual ~AccelerometerThread() { clean(); } + + // Copy assignment (not implemented / included to avoid cppcheck warnings) + AccelerometerThread &operator=(const AccelerometerThread &other) + { + this->copy(other); + return *this; + } + + // Take a very shallow copy (does not copy OSThread state nor the sensor object) + // If for some reason this is ever used, make sure to call init() after any copy + void copy(const AccelerometerThread &other) + { + if (this != &other) { + clean(); + this->device = ScanI2C::FoundDevice(other.device.type, + ScanI2C::DeviceAddress(other.device.address.port, other.device.address.address)); + } + } + + // Cleanup resources + void clean() + { + isInitialised = false; + if (sensor != nullptr) { + delete sensor; + sensor = nullptr; + } + } +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/BMA423Sensor.cpp b/src/motion/BMA423Sensor.cpp new file mode 100755 index 000000000..ec07b7c40 --- /dev/null +++ b/src/motion/BMA423Sensor.cpp @@ -0,0 +1,62 @@ +#include "BMA423Sensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +using namespace MotionSensorI2C; + +BMA423Sensor::BMA423Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +bool BMA423Sensor::init() +{ + if (sensor.begin(deviceAddress(), &MotionSensorI2C::readRegister, &MotionSensorI2C::writeRegister)) { + sensor.configAccelerometer(sensor.RANGE_2G, sensor.ODR_100HZ, sensor.BW_NORMAL_AVG4, sensor.PERF_CONTINUOUS_MODE); + sensor.enableAccelerometer(); + sensor.configInterrupt(BMA4_LEVEL_TRIGGER, BMA4_ACTIVE_HIGH, BMA4_PUSH_PULL, BMA4_OUTPUT_ENABLE, BMA4_INPUT_DISABLE); + +#ifdef BMA423_INT + pinMode(BMA4XX_INT, INPUT); + attachInterrupt( + BMA4XX_INT, + [] { + // Set interrupt to set irq value to true + BMA_IRQ = true; + }, + RISING); // Select the interrupt mode according to the actual circuit +#endif + +#ifdef T_WATCH_S3 + // Need to raise the wrist function, need to set the correct axis + sensor.setReampAxes(sensor.REMAP_TOP_LAYER_RIGHT_CORNER); +#else + sensor.setReampAxes(sensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER); +#endif + // sensor.enableFeature(sensor.FEATURE_STEP_CNTR, true); + sensor.enableFeature(sensor.FEATURE_TILT, true); + sensor.enableFeature(sensor.FEATURE_WAKEUP, true); + // sensor.resetPedometer(); + + // Turn on feature interrupt + sensor.enablePedometerIRQ(); + sensor.enableTiltIRQ(); + + // It corresponds to isDoubleClick interrupt + sensor.enableWakeupIRQ(); + LOG_DEBUG("BMA423Sensor::init ok\n"); + return true; + } + LOG_DEBUG("BMA423Sensor::init failed\n"); + return false; +} + +int32_t BMA423Sensor::runOnce() +{ + if (sensor.readIrqStatus() != DEV_WIRE_NONE) { + if (sensor.isTilt() || sensor.isDoubleTap()) { + wakeScreen(); + return 500; + } + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif \ No newline at end of file diff --git a/src/motion/BMA423Sensor.h b/src/motion/BMA423Sensor.h new file mode 100755 index 000000000..0bc0ddf8c --- /dev/null +++ b/src/motion/BMA423Sensor.h @@ -0,0 +1,26 @@ +#pragma once +#ifndef _BMA423_SENSOR_H_ +#define _BMA423_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include +#include + +class BMA423Sensor : public MotionSensor +{ + private: + SensorBMA423 sensor; + volatile bool BMA_IRQ = false; + + public: + explicit BMA423Sensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/BMX160Sensor.cpp b/src/motion/BMX160Sensor.cpp new file mode 100755 index 000000000..f18c4b586 --- /dev/null +++ b/src/motion/BMX160Sensor.cpp @@ -0,0 +1,100 @@ +#include "BMX160Sensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +BMX160Sensor::BMX160Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +#ifdef RAK_4631 + +// screen is defined in main.cpp +extern graphics::Screen *screen; + +bool BMX160Sensor::init() +{ + if (sensor.begin()) { + // set output data rate + sensor.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); + LOG_DEBUG("BMX160Sensor::init ok\n"); + return true; + } + LOG_DEBUG("BMX160Sensor::init failed\n"); + return false; +} + +int32_t BMX160Sensor::runOnce() +{ + sBmx160SensorData_t magAccel; + sBmx160SensorData_t gAccel; + + /* Get a new sensor event */ + sensor.getAllData(&magAccel, NULL, &gAccel); + + // experimental calibrate routine. Limited to between 10 and 30 seconds after boot + if (millis() > 12 * 1000 && millis() < 30 * 1000) { + if (!showingScreen) { + showingScreen = true; + screen->startAlert((FrameCallback)drawFrameCalibration); + } + if (magAccel.x > highestX) + highestX = magAccel.x; + if (magAccel.x < lowestX) + lowestX = magAccel.x; + if (magAccel.y > highestY) + highestY = magAccel.y; + if (magAccel.y < lowestY) + lowestY = magAccel.y; + if (magAccel.z > highestZ) + highestZ = magAccel.z; + if (magAccel.z < lowestZ) + lowestZ = magAccel.z; + } else if (showingScreen && millis() >= 30 * 1000) { + showingScreen = false; + screen->endAlert(); + } + + int highestRealX = highestX - (highestX + lowestX) / 2; + + magAccel.x -= (highestX + lowestX) / 2; + magAccel.y -= (highestY + lowestY) / 2; + magAccel.z -= (highestZ + lowestZ) / 2; + FusionVector ga, ma; + ga.axis.x = -gAccel.x; // default location for the BMX160 is on the rear of the board + ga.axis.y = -gAccel.y; + ga.axis.z = gAccel.z; + ma.axis.x = -magAccel.x; + ma.axis.y = -magAccel.y; + ma.axis.z = magAccel.z * 3; + + // If we're set to one of the inverted positions + if (config.display.compass_orientation > meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270) { + ma = FusionAxesSwap(ma, FusionAxesAlignmentNXNYPZ); + ga = FusionAxesSwap(ga, FusionAxesAlignmentNXNYPZ); + } + + float heading = FusionCompassCalculateHeading(FusionConventionNed, ga, ma); + + switch (config.display.compass_orientation) { + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0_INVERTED: + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0: + break; + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90: + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90_INVERTED: + heading += 90; + break; + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180: + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180_INVERTED: + heading += 180; + break; + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270: + case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED: + heading += 270; + break; + } + screen->setHeading(heading); + + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/BMX160Sensor.h b/src/motion/BMX160Sensor.h new file mode 100755 index 000000000..26f477271 --- /dev/null +++ b/src/motion/BMX160Sensor.h @@ -0,0 +1,41 @@ +#pragma once + +#ifndef _BMX160_SENSOR_H_ +#define _BMX160_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#ifdef RAK_4631 + +#include "Fusion/Fusion.h" +#include + +class BMX160Sensor : public MotionSensor +{ + private: + RAK_BMX160 sensor; + bool showingScreen = false; + float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0; + + public: + explicit BMX160Sensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#else + +// Stub +class BMX160Sensor : public MotionSensor +{ + public: + explicit BMX160Sensor(ScanI2C::FoundDevice foundDevice); +}; + +#endif + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/ICM20948Sensor.cpp b/src/motion/ICM20948Sensor.cpp new file mode 100755 index 000000000..d16968e06 --- /dev/null +++ b/src/motion/ICM20948Sensor.cpp @@ -0,0 +1,186 @@ +#include "ICM20948Sensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +// Flag when an interrupt has been detected +volatile static bool ICM20948_IRQ = false; + +// Interrupt service routine +void ICM20948SetInterrupt() +{ + ICM20948_IRQ = true; +} + +ICM20948Sensor::ICM20948Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +bool ICM20948Sensor::init() +{ + // Initialise the sensor + sensor = ICM20948Singleton::GetInstance(); + if (!sensor->init(device)) + return false; + + // Enable simple Wake on Motion + return sensor->setWakeOnMotion(); +} + +#ifdef ICM_20948_INT_PIN + +int32_t ICM20948Sensor::runOnce() +{ + // Wake on motion using hardware interrupts - this is the most efficient way to check for motion + if (ICM20948_IRQ) { + ICM20948_IRQ = false; + sensor->clearInterrupts(); + wakeScreen(); + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#else + +int32_t ICM20948Sensor::runOnce() +{ + // Wake on motion using polling - this is not as efficient as using hardware interrupt pin (see above) + auto status = sensor->setBank(0); + if (sensor->status != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::isWakeOnMotion failed to set bank - %s\n", sensor->statusString()); + return MOTION_SENSOR_CHECK_INTERVAL_MS; + } + + ICM_20948_INT_STATUS_t int_stat; + status = sensor->read(AGB0_REG_INT_STATUS, (uint8_t *)&int_stat, sizeof(ICM_20948_INT_STATUS_t)); + if (status != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::isWakeOnMotion failed to read interrupts - %s\n", sensor->statusString()); + return MOTION_SENSOR_CHECK_INTERVAL_MS; + } + + if (int_stat.WOM_INT != 0) { + // Wake up! + wakeScreen(); + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif + +// ---------------------------------------------------------------------- +// ICM20948Singleton +// ---------------------------------------------------------------------- + +// Get a singleton wrapper for an Sparkfun ICM_20948_I2C +ICM20948Singleton *ICM20948Singleton::GetInstance() +{ + if (pinstance == nullptr) { + pinstance = new ICM20948Singleton(); + } + return pinstance; +} + +ICM20948Singleton::ICM20948Singleton() {} + +ICM20948Singleton::~ICM20948Singleton() {} + +ICM20948Singleton *ICM20948Singleton::pinstance{nullptr}; + +// Initialise the ICM20948 Sensor +bool ICM20948Singleton::init(ScanI2C::FoundDevice device) +{ +#ifdef ICM_20948_DEBUG + // Set ICM_20948_DEBUG to enable helpful debug messages on Serial + enableDebugging(); +#endif + +// startup +#ifdef Wire1 + ICM_20948_Status_e status = + begin(device.address.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire, device.address.address == ICM20948_ADDR ? 1 : 0); +#else + ICM_20948_Status_e status = begin(Wire, device.address.address == ICM20948_ADDR ? 1 : 0); +#endif + if (status != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::init begin - %s\n", statusString()); + return false; + } + + // SW reset to make sure the device starts in a known state + if (swReset() != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::init reset - %s\n", statusString()); + return false; + } + delay(200); + + // Now wake the sensor up + if (sleep(false) != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::init wake - %s\n", statusString()); + return false; + } + + if (lowPower(false) != ICM_20948_Stat_Ok) { + LOG_DEBUG("ICM20948Sensor::init high power - %s\n", statusString()); + return false; + } + +#ifdef ICM_20948_INT_PIN + + // Active low + cfgIntActiveLow(true); + LOG_DEBUG("ICM20948Sensor::init set cfgIntActiveLow - %s\n", statusString()); + + // Push-pull + cfgIntOpenDrain(false); + LOG_DEBUG("ICM20948Sensor::init set cfgIntOpenDrain - %s\n", statusString()); + + // If enabled, *ANY* read will clear the INT_STATUS register. + cfgIntAnyReadToClear(true); + LOG_DEBUG("ICM20948Sensor::init set cfgIntAnyReadToClear - %s\n", statusString()); + + // Latch the interrupt until cleared + cfgIntLatch(true); + LOG_DEBUG("ICM20948Sensor::init set cfgIntLatch - %s\n", statusString()); + + // Set up an interrupt pin with an internal pullup for active low + pinMode(ICM_20948_INT_PIN, INPUT_PULLUP); + + // Set up an interrupt service routine + attachInterrupt(ICM_20948_INT_PIN, ICM20948SetInterrupt, FALLING); + +#endif + return true; +} + +#ifdef ICM_20948_DMP_IS_ENABLED + +// Stub +bool ICM20948Sensor::initDMP() +{ + return false; +} + +#endif + +bool ICM20948Singleton::setWakeOnMotion() +{ + // Set WoM threshold in milli G's + auto status = WOMThreshold(ICM_20948_WOM_THRESHOLD); + if (status != ICM_20948_Stat_Ok) + return false; + + // Enable WoM Logic mode 1 = Compare the current sample with the previous sample + status = WOMLogic(true, 1); + LOG_DEBUG("ICM20948Sensor::init set WOMLogic - %s\n", statusString()); + if (status != ICM_20948_Stat_Ok) + return false; + + // Enable interrupts on WakeOnMotion + status = intEnableWOM(true); + LOG_DEBUG("ICM20948Sensor::init set intEnableWOM - %s\n", statusString()); + return status == ICM_20948_Stat_Ok; + + // Clear any current interrupts + ICM20948_IRQ = false; + clearInterrupts(); + return true; +} + +#endif \ No newline at end of file diff --git a/src/motion/ICM20948Sensor.h b/src/motion/ICM20948Sensor.h new file mode 100755 index 000000000..d5e246c8d --- /dev/null +++ b/src/motion/ICM20948Sensor.h @@ -0,0 +1,96 @@ +#pragma once +#ifndef _ICM_20948_SENSOR_H_ +#define _ICM_20948_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include + +// Set the default gyro scale - dps250, dps500, dps1000, dps2000 +#ifndef ICM_20948_MPU_GYRO_SCALE +#define ICM_20948_MPU_GYRO_SCALE dps250 +#endif + +// Set the default accelerometer scale - gpm2, gpm4, gpm8, gpm16 +#ifndef ICM_20948_MPU_ACCEL_SCALE +#define ICM_20948_MPU_ACCEL_SCALE gpm2 +#endif + +// Define a threshold for Wake on Motion Sensing (0mg to 1020mg) +#ifndef ICM_20948_WOM_THRESHOLD +#define ICM_20948_WOM_THRESHOLD 16U +#endif + +// Define a pin in variant.h to use interrupts to read the ICM-20948 +#ifndef ICM_20948_WOM_THRESHOLD +#define ICM_20948_INT_PIN 255 +#endif + +// Uncomment this line to enable helpful debug messages on Serial +// #define ICM_20948_DEBUG 1 + +// Uncomment this line to enable the onboard digital motion processor (to be added in a future PR) +// #define ICM_20948_DMP_IS_ENABLED 1 + +// Check for a mandatory compiler flag to use the DMP (to be added in a future PR) +#ifdef ICM_20948_DMP_IS_ENABLED +#ifndef ICM_20948_USE_DMP +#error To use the digital motion processor, please either set the compiler flag ICM_20948_USE_DMP or uncomment line 29 (#define ICM_20948_USE_DMP) in ICM_20948_C.h +#endif +#endif + +// The I2C address of the Accelerometer (if found) from main.cpp +extern ScanI2C::DeviceAddress accelerometer_found; + +// Singleton wrapper for the Sparkfun ICM_20948_I2C class +class ICM20948Singleton : public ICM_20948_I2C +{ + private: + static ICM20948Singleton *pinstance; + + protected: + ICM20948Singleton(); + ~ICM20948Singleton(); + + public: + // Create a singleton instance (not thread safe) + static ICM20948Singleton *GetInstance(); + + // Singletons should not be cloneable. + ICM20948Singleton(ICM20948Singleton &other) = delete; + + // Singletons should not be assignable. + void operator=(const ICM20948Singleton &) = delete; + + // Initialise the motion sensor singleton for normal operation + bool init(ScanI2C::FoundDevice device); + + // Enable Wake on Motion interrupts (sensor must be initialised first) + bool setWakeOnMotion(); + +#ifdef ICM_20948_DMP_IS_ENABLED + // Initialise the motion sensor singleton for digital motion processing + bool initDMP(); +#endif +}; + +class ICM20948Sensor : public MotionSensor +{ + private: + ICM20948Singleton *sensor = nullptr; + + public: + explicit ICM20948Sensor(ScanI2C::FoundDevice foundDevice); + + // Initialise the motion sensor + virtual bool init() override; + + // Called each time our sensor gets a chance to run + virtual int32_t runOnce() override; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/LIS3DHSensor.cpp b/src/motion/LIS3DHSensor.cpp new file mode 100755 index 000000000..e2df60b1c --- /dev/null +++ b/src/motion/LIS3DHSensor.cpp @@ -0,0 +1,36 @@ +#include "LIS3DHSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +LIS3DHSensor::LIS3DHSensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +bool LIS3DHSensor::init() +{ + if (sensor.begin(deviceAddress())) { + sensor.setRange(LIS3DH_RANGE_2_G); + // Adjust threshold, higher numbers are less sensitive + sensor.setClick(config.device.double_tap_as_button_press ? 2 : 1, MOTION_SENSOR_CHECK_INTERVAL_MS); + LOG_DEBUG("LIS3DHSensor::init ok\n"); + return true; + } + LOG_DEBUG("LIS3DHSensor::init failed\n"); + return false; +} + +int32_t LIS3DHSensor::runOnce() +{ + if (sensor.getClick() > 0) { + uint8_t click = sensor.getClick(); + if (!config.device.double_tap_as_button_press) { + wakeScreen(); + } + + if (config.device.double_tap_as_button_press && (click & 0x20)) { + buttonPress(); + return 500; + } + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif \ No newline at end of file diff --git a/src/motion/LIS3DHSensor.h b/src/motion/LIS3DHSensor.h new file mode 100755 index 000000000..603d195a8 --- /dev/null +++ b/src/motion/LIS3DHSensor.h @@ -0,0 +1,24 @@ +#pragma once +#ifndef _LIS3DH_SENSOR_H_ +#define _LIS3DH_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include + +class LIS3DHSensor : public MotionSensor +{ + private: + Adafruit_LIS3DH sensor; + + public: + explicit LIS3DHSensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/LSM6DS3Sensor.cpp b/src/motion/LSM6DS3Sensor.cpp new file mode 100755 index 000000000..64ef9a23b --- /dev/null +++ b/src/motion/LSM6DS3Sensor.cpp @@ -0,0 +1,33 @@ +#include "LSM6DS3Sensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +LSM6DS3Sensor::LSM6DS3Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +bool LSM6DS3Sensor::init() +{ + if (sensor.begin_I2C(deviceAddress())) { + + // Default threshold of 2G, less sensitive options are 4, 8 or 16G + sensor.setAccelRange(LSM6DS_ACCEL_RANGE_2_G); + + // Duration is number of occurances needed to trigger, higher threshold is less sensitive + sensor.enableWakeup(config.display.wake_on_tap_or_motion, 1, LSM6DS3_WAKE_THRESH); + + LOG_DEBUG("LSM6DS3Sensor::init ok\n"); + return true; + } + LOG_DEBUG("LSM6DS3Sensor::init failed\n"); + return false; +} + +int32_t LSM6DS3Sensor::runOnce() +{ + if (sensor.shake()) { + wakeScreen(); + return 500; + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif \ No newline at end of file diff --git a/src/motion/LSM6DS3Sensor.h b/src/motion/LSM6DS3Sensor.h new file mode 100755 index 000000000..77069ef3c --- /dev/null +++ b/src/motion/LSM6DS3Sensor.h @@ -0,0 +1,28 @@ +#pragma once +#ifndef _LSM6DS3_SENSOR_H_ +#define _LSM6DS3_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#ifndef LSM6DS3_WAKE_THRESH +#define LSM6DS3_WAKE_THRESH 20 +#endif + +#include + +class LSM6DS3Sensor : public MotionSensor +{ + private: + Adafruit_LSM6DS3TRC sensor; + + public: + explicit LSM6DS3Sensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/MPU6050Sensor.cpp b/src/motion/MPU6050Sensor.cpp new file mode 100755 index 000000000..77aaca46d --- /dev/null +++ b/src/motion/MPU6050Sensor.cpp @@ -0,0 +1,31 @@ +#include "MPU6050Sensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +MPU6050Sensor::MPU6050Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +bool MPU6050Sensor::init() +{ + if (sensor.begin(deviceAddress())) { + // setup motion detection + sensor.setHighPassFilter(MPU6050_HIGHPASS_0_63_HZ); + sensor.setMotionDetectionThreshold(1); + sensor.setMotionDetectionDuration(20); + sensor.setInterruptPinLatch(true); // Keep it latched. Will turn off when reinitialized. + sensor.setInterruptPinPolarity(true); + LOG_DEBUG("MPU6050Sensor::init ok\n"); + return true; + } + LOG_DEBUG("MPU6050Sensor::init failed\n"); + return false; +} + +int32_t MPU6050Sensor::runOnce() +{ + if (sensor.getMotionInterruptStatus()) { + wakeScreen(); + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif \ No newline at end of file diff --git a/src/motion/MPU6050Sensor.h b/src/motion/MPU6050Sensor.h new file mode 100755 index 000000000..2e6eafecd --- /dev/null +++ b/src/motion/MPU6050Sensor.h @@ -0,0 +1,24 @@ +#pragma once +#ifndef _MPU6050_SENSOR_H_ +#define _MPU6050_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include + +class MPU6050Sensor : public MotionSensor +{ + private: + Adafruit_MPU6050 sensor; + + public: + explicit MPU6050Sensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/MotionSensor.cpp b/src/motion/MotionSensor.cpp new file mode 100755 index 000000000..20e396bba --- /dev/null +++ b/src/motion/MotionSensor.cpp @@ -0,0 +1,79 @@ +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +// screen is defined in main.cpp +extern graphics::Screen *screen; + +MotionSensor::MotionSensor(ScanI2C::FoundDevice foundDevice) +{ + device.address.address = foundDevice.address.address; + device.address.port = foundDevice.address.port; + device.type = foundDevice.type; + LOG_DEBUG("MotionSensor::MotionSensor port: %s address: 0x%x type: %d\n", + devicePort() == ScanI2C::I2CPort::WIRE1 ? "Wire1" : "Wire", (uint8_t)deviceAddress(), deviceType()); +} + +ScanI2C::DeviceType MotionSensor::deviceType() +{ + return device.type; +} + +uint8_t MotionSensor::deviceAddress() +{ + return device.address.address; +} + +ScanI2C::I2CPort MotionSensor::devicePort() +{ + return device.address.port; +} + +#ifdef RAK_4631 +void MotionSensor::drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + // int x_offset = display->width() / 2; + // int y_offset = display->height() <= 80 ? 0 : 32; + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_MEDIUM); + display->drawString(x, y, "Calibrating\nCompass"); + int16_t compassX = 0, compassY = 0; + uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight()); + + // coordinates for the center of the compass/circle + if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) { + compassX = x + display->getWidth() - compassDiam / 2 - 5; + compassY = y + display->getHeight() / 2; + } else { + compassX = x + display->getWidth() - compassDiam / 2 - 5; + compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2; + } + display->drawCircle(compassX, compassY, compassDiam / 2); + screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180); +} +#endif + +#if !MESHTASTIC_EXCLUDE_POWER_FSM +void MotionSensor::wakeScreen() +{ + if (powerFSM.getState() == &stateDARK) { + LOG_DEBUG("MotionSensor::wakeScreen detected\n"); + powerFSM.trigger(EVENT_INPUT); + } +} + +void MotionSensor::buttonPress() +{ + LOG_DEBUG("MotionSensor::buttonPress detected\n"); + powerFSM.trigger(EVENT_PRESS); +} + +#else + +void MotionSensor::wakeScreen() {} + +void MotionSensor::buttonPress() {} + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/MotionSensor.h b/src/motion/MotionSensor.h new file mode 100755 index 000000000..0f7f3479b --- /dev/null +++ b/src/motion/MotionSensor.h @@ -0,0 +1,85 @@ +#pragma once +#ifndef _MOTION_SENSOR_H_ +#define _MOTION_SENSOR_H_ + +#define MOTION_SENSOR_CHECK_INTERVAL_MS 100 +#define MOTION_SENSOR_CLICK_THRESHOLD 40 + +#include "../configuration.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#include "../PowerFSM.h" +#include "../detect/ScanI2C.h" +#include "../graphics/Screen.h" +#include "../graphics/ScreenFonts.h" +#include "../power.h" + +// Base class for motion processing +class MotionSensor +{ + public: + explicit MotionSensor(ScanI2C::FoundDevice foundDevice); + virtual ~MotionSensor(){}; + + // Get the device type + ScanI2C::DeviceType deviceType(); + + // Get the device address + uint8_t deviceAddress(); + + // Get the device port + ScanI2C::I2CPort devicePort(); + + // Initialise the motion sensor + inline virtual bool init() { return false; }; + + // The method that will be called each time our sensor gets a chance to run + // Returns the desired period for next invocation (or RUN_SAME for no change) + // Refer to /src/concurrency/OSThread.h for more information + inline virtual int32_t runOnce() { return MOTION_SENSOR_CHECK_INTERVAL_MS; }; + + protected: + // Turn on the screen when a tap or motion is detected + virtual void wakeScreen(); + + // Register a button press when a double-tap is detected + virtual void buttonPress(); + +#ifdef RAK_4631 + // draw an OLED frame (currently only used by the RAK4631 BMX160 sensor) + static void drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); +#endif + + ScanI2C::FoundDevice device; +}; + +namespace MotionSensorI2C +{ + +static inline int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) +{ + Wire.beginTransmission(address); + Wire.write(reg); + Wire.endTransmission(); + Wire.requestFrom((uint8_t)address, (uint8_t)len); + uint8_t i = 0; + while (Wire.available()) { + data[i++] = Wire.read(); + } + return 0; // Pass +} + +static inline int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len) +{ + Wire.beginTransmission(address); + Wire.write(reg); + Wire.write(data, len); + return (0 != Wire.endTransmission()); +} + +} // namespace MotionSensorI2C + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/STK8XXXSensor.cpp b/src/motion/STK8XXXSensor.cpp new file mode 100755 index 000000000..d4d69ef99 --- /dev/null +++ b/src/motion/STK8XXXSensor.cpp @@ -0,0 +1,40 @@ +#include "STK8XXXSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +STK8XXXSensor::STK8XXXSensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {} + +#ifdef STK8XXX_INT + +volatile static bool STK_IRQ; + +bool STK8XXXSensor::init() +{ + if (sensor.STK8xxx_Initialization(STK8xxx_VAL_RANGE_2G)) { + STK_IRQ = false; + sensor.STK8xxx_Anymotion_init(); + pinMode(STK8XXX_INT, INPUT_PULLUP); + attachInterrupt( + digitalPinToInterrupt(STK8XXX_INT), [] { STK_IRQ = true; }, RISING); + + LOG_DEBUG("STK8XXXSensor::init ok\n"); + return true; + } + LOG_DEBUG("STK8XXXSensor::init failed\n"); + return false; +} + +int32_t STK8XXXSensor::runOnce() +{ + if (STK_IRQ) { + STK_IRQ = false; + if (config.display.wake_on_tap_or_motion) { + wakeScreen(); + } + } + return MOTION_SENSOR_CHECK_INTERVAL_MS; +} + +#endif + +#endif \ No newline at end of file diff --git a/src/motion/STK8XXXSensor.h b/src/motion/STK8XXXSensor.h new file mode 100755 index 000000000..190b916b4 --- /dev/null +++ b/src/motion/STK8XXXSensor.h @@ -0,0 +1,37 @@ +#pragma once +#ifndef _STK8XXX_SENSOR_H_ +#define _STK8XXX_SENSOR_H_ + +#include "MotionSensor.h" + +#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C + +#ifdef STK8XXX_INT + +#include + +class STK8XXXSensor : public MotionSensor +{ + private: + STK8xxx sensor; + + public: + explicit STK8XXXSensor(ScanI2C::FoundDevice foundDevice); + virtual bool init() override; + virtual int32_t runOnce() override; +}; + +#else + +// Stub +class STK8XXXSensor : public MotionSensor +{ + public: + explicit STK8XXXSensor(ScanI2C::FoundDevice foundDevice); +}; + +#endif + +#endif + +#endif \ No newline at end of file diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 90c4c392d..a2e7dfc4e 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -170,6 +170,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_NODE_T114 #elif defined(SENSECAP_INDICATOR) #define HW_VENDOR meshtastic_HardwareModel_SENSECAP_INDICATOR +#elif defined(SEEED_XIAO_S3) +#define HW_VENDOR meshtastic_HardwareModel_SEEED_XIAO_S3 #endif // ----------------------------------------------------------------------------- diff --git a/src/power.h b/src/power.h index a4307ee07..19a4f1228 100644 --- a/src/power.h +++ b/src/power.h @@ -48,6 +48,11 @@ extern INA219Sensor ina219Sensor; extern INA3221Sensor ina3221Sensor; #endif +#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) +#include "modules/Telemetry/Sensor/MAX17048Sensor.h" +extern MAX17048Sensor max17048Sensor; +#endif + #if HAS_RAKPROT && !defined(ARCH_PORTDUINO) #include "../variants/rak2560/RAK9154Sensor.h" extern RAK9154Sensor rak9154Sensor; @@ -82,6 +87,8 @@ class Power : private concurrency::OSThread bool axpChipInit(); /// Setup a simple ADC input based battery sensor bool analogInit(); + /// Setup a Lipo battery level sensor + bool lipoInit(); private: // open circuit voltage lookup table diff --git a/test/test_crypto/test_main.cpp b/test/test_crypto/test_main.cpp index 129c88283..0c820178a 100644 --- a/test/test_crypto/test_main.cpp +++ b/test/test_crypto/test_main.cpp @@ -129,6 +129,7 @@ void setup() { // NOTE!!! Wait for >2 secs // if board doesn't support software reset via Serial.DTR/RTS + delay(10); delay(2000); UNITY_BEGIN(); // IMPORTANT LINE! diff --git a/variants/seeed_xiao_s3/pins_arduino.h b/variants/seeed_xiao_s3/pins_arduino.h new file mode 100644 index 000000000..52e96eaeb --- /dev/null +++ b/variants/seeed_xiao_s3/pins_arduino.h @@ -0,0 +1,21 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x2886 +#define USB_PID 0x0059 + +// GPIO48 Reference: https://github.com/espressif/arduino-esp32/pull/8600 + +// The default Wire will be mapped to Screen and Sensors +static const uint8_t SDA = 47; +static const uint8_t SCL = 48; + +// Default SPI will be mapped to Radio +static const uint8_t MISO = 8; +static const uint8_t SCK = 7; +static const uint8_t MOSI = 9; +static const uint8_t SS = 41; + +#endif /* Pins_Arduino_h */ diff --git a/variants/seeed_xiao_s3/platformio.ini b/variants/seeed_xiao_s3/platformio.ini new file mode 100644 index 000000000..3d10d7136 --- /dev/null +++ b/variants/seeed_xiao_s3/platformio.ini @@ -0,0 +1,17 @@ +[env:seeed-xiao-s3] +extends = esp32s3_base +board = seeed-xiao-s3 +board_check = true +board_build.mcu = esp32s3 +upload_protocol = esptool +upload_speed = 921600 +lib_deps = + ${esp32s3_base.lib_deps} +build_unflags = + ${esp32s3_base.build_unflags} + -DARDUINO_USB_MODE=1 +build_flags = + ${esp32s3_base.build_flags} -DSEEED_XIAO_S3 -I variants/seeed_xiao_s3 + -DBOARD_HAS_PSRAM + + -DARDUINO_USB_MODE=0 \ No newline at end of file diff --git a/variants/seeed_xiao_s3/variant.h b/variants/seeed_xiao_s3/variant.h new file mode 100644 index 000000000..ab886d354 --- /dev/null +++ b/variants/seeed_xiao_s3/variant.h @@ -0,0 +1,84 @@ +/* + ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄ +▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░▌ +▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌ +▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ +▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌ ▐░▌ +▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌ + ▀▀▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░▌ ▐░▌ + ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌ + ▄▄▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌ +▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░▌ + ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ + + ▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ + ▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ + ▐░▌ ▐░▌ ▀▀▀▀█░█▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌ ▐░█▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀█░▌ + ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ + ▐░▐░▌ ▐░▌ ▐░█▄▄▄▄▄▄▄█░▌▐░▌ ▐░▌ ▐░█▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄█░▌ + ▐░▌ ▐░▌ ▐░░░░░░░░░░░▌▐░▌ ▐░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ + ▐░▌░▌ ▐░▌ ▐░█▀▀▀▀▀▀▀█░▌▐░▌ ▐░▌ ▀▀▀▀▀▀▀▀▀█░▌ ▀▀▀▀▀▀▀▀▀█░▌ + ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌ + ▐░▌ ▐░▌ ▄▄▄▄█░█▄▄▄▄ ▐░▌ ▐░▌▐░█▄▄▄▄▄▄▄█░▌ ▄▄▄▄▄▄▄▄▄█░▌ ▄▄▄▄▄▄▄▄▄█░▌ + ▐░▌ ▐░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░░░░░░░░░░░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌ + ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ +*/ + +/* +Board Information: https://www.seeedstudio.com/XIAO-ESP32S3-Sense-p-5639.html +Expansion Board Infomation : https://www.seeedstudio.com/Seeeduino-XIAO-Expansion-board-p-4746.html +L76K GPS Module Information : https://www.seeedstudio.com/L76K-GNSS-Module-for-Seeed-Studio-XIAO-p-5864.html +*/ + +#define LED_PIN 48 +#define LED_STATE_ON 1 // State when LED is lit + +#define BUTTON_PIN 21 // This is the Program Button +#define BUTTON_NEED_PULLUP + +/*Warning: + https://www.seeedstudio.com/L76K-GNSS-Module-for-Seeed-Studio-XIAO-p-5864.html + L76K Expansion Board can not directly used, L76K Reset Pin needs to override or physically remove it, + otherwise it will conflict with the SPI pins +*/ +// #define GPS_L76K +#ifdef GPS_L76K +#define GPS_RX_PIN 44 +#define GPS_TX_PIN 43 +#define HAS_GPS 1 +#define GPS_BAUDRATE 9600 +#define GPS_THREAD_INTERVAL 50 +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX +#define PIN_GPS_STANDBY 1 +#endif + +// XIAO S3 Expansion board has 1.3 inch OLED Screen +#define USCREEN_SSD1306 + +#define I2C_SDA 5 +#define I2C_SCL 6 + +// XIAO S3 LORA module +#define USE_SX1262 + +#define LORA_MISO 8 +#define LORA_SCK 7 +#define LORA_MOSI 9 +#define LORA_CS 41 + +#define LORA_RESET 42 +#define LORA_DIO1 39 + +#define LORA_DIO2 38 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY 40 +#define SX126X_RESET LORA_RESET + +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#endif \ No newline at end of file