Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11a14bdda5 | ||
|
|
f25e3e893d | ||
|
|
134ec6dc54 | ||
|
|
a04b7c4a87 | ||
|
|
60303968bb | ||
|
|
d3c7f05baa | ||
|
|
1971e5ab13 | ||
|
|
8ccb2c918f | ||
|
|
6b49b7228a | ||
|
|
c194666885 | ||
|
|
d7cb5d7885 | ||
|
|
ffd144da83 | ||
|
|
1e8c9e7071 | ||
|
|
3c83e01d0e | ||
|
|
dc3947117e | ||
|
|
87d0850f95 | ||
|
|
5831952636 | ||
|
|
a6d61413c3 | ||
|
|
e393a5c410 | ||
|
|
8f2ecbdb4d | ||
|
|
6c28d11cee | ||
|
|
69f1b502cc | ||
|
|
3a498fbbe4 | ||
|
|
8f7dea0580 | ||
|
|
322f0262a8 | ||
|
|
00762393cf | ||
|
|
866c89f801 | ||
|
|
8c248927c8 | ||
|
|
188d895eb4 | ||
|
|
6f476f3475 | ||
|
|
50e1fe88e8 | ||
|
|
f9bedd8adc | ||
|
|
5a619c9031 | ||
|
|
8d3f9222ff | ||
|
|
f5335f22ea | ||
|
|
4a3f449555 | ||
|
|
608713470b | ||
|
|
f475be19c6 | ||
|
|
27cc76d5ed |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x2886", "0x1667"]
|
||||
],
|
||||
"usb_product": "HT-n5262",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "heltec_mesh_node_t1",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Heltec Mesh Node T1",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://heltec.org",
|
||||
"vendor": "Heltec"
|
||||
}
|
||||
@@ -186,12 +186,16 @@ lib_deps =
|
||||
https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/archive/refs/tags/v1.1.2.zip
|
||||
# renovate: datasource=github-tags depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/SparkFun_ICM-20948_ArduinoLibrary
|
||||
https://github.com/sparkfun/SparkFun_ICM-20948_ArduinoLibrary/archive/refs/tags/v1.3.2.zip
|
||||
# renovate: datasource=github-tags depName=TDK InvenSense ICM42670P packageName=tdk-invn-oss/motion.arduino.ICM42670P
|
||||
https://github.com/tdk-invn-oss/motion.arduino.ICM42670P/archive/refs/tags/1.0.8.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit LTR390 Library packageName=adafruit/Adafruit_LTR390
|
||||
https://github.com/adafruit/Adafruit_LTR390/archive/refs/tags/1.1.2.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit PCT2075 packageName=adafruit/Adafruit_PCT2075
|
||||
https://github.com/adafruit/Adafruit_PCT2075/archive/refs/tags/1.0.6.zip
|
||||
# renovate: datasource=github-tags depName=DFRobot_BMM150 packageName=dfrobot/DFRobot_BMM150
|
||||
https://github.com/DFRobot/DFRobot_BMM150/archive/refs/tags/V1.0.0.zip
|
||||
# renovate: datasource=github-tags depName=SparkFun MMC5983MA Magnetometer packageName=sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library
|
||||
https://github.com/sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library/archive/refs/tags/v1.1.4.zip
|
||||
# renovate: datasource=github-tags depName=Adafruit_TSL2561 packageName=adafruit/Adafruit_TSL2561
|
||||
https://github.com/adafruit/Adafruit_TSL2561/archive/refs/tags/1.1.3.zip
|
||||
# renovate: datasource=github-tags depName=BH1750_WE packageName=wollewald/BH1750_WE
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "Lock.h"
|
||||
#include "configuration.h"
|
||||
#include <cassert>
|
||||
#include <logging.h>
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
@@ -33,13 +34,33 @@ void Lock::unlock()
|
||||
}
|
||||
}
|
||||
#else
|
||||
Lock::Lock() {}
|
||||
Lock::Lock()
|
||||
{
|
||||
pthread_mutex_init(&mutex, NULL);
|
||||
}
|
||||
|
||||
Lock::~Lock() {}
|
||||
void Lock::lock()
|
||||
{
|
||||
if (locked) {
|
||||
LOG_INFO("Attempt to lock an already locked Lock!");
|
||||
}
|
||||
pthread_mutex_lock(&mutex);
|
||||
locked = true;
|
||||
|
||||
void Lock::lock() {}
|
||||
if (console)
|
||||
LOG_WARN("Lock");
|
||||
}
|
||||
|
||||
void Lock::unlock() {}
|
||||
void Lock::unlock()
|
||||
{
|
||||
pthread_mutex_unlock(&mutex);
|
||||
locked = false;
|
||||
}
|
||||
|
||||
Lock::~Lock()
|
||||
{
|
||||
pthread_mutex_destroy(&mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -30,6 +30,9 @@ class Lock
|
||||
private:
|
||||
#ifdef HAS_FREE_RTOS
|
||||
SemaphoreHandle_t handle;
|
||||
#else
|
||||
pthread_mutex_t mutex;
|
||||
bool locked = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -243,6 +243,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define QMI8658_ADDR 0x6B
|
||||
#define QMC5883L_ADDR 0x0D
|
||||
#define HMC5883L_ADDR 0x1E
|
||||
#define MMC5983MA_ADDR 0x30
|
||||
#define SHTC3_ADDR 0x70
|
||||
#define LPS22HB_ADDR 0x5C
|
||||
#define LPS22HB_ADDR_ALT 0x5D
|
||||
@@ -292,6 +293,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define DA217_ADDR 0x26
|
||||
#define BMI270_ADDR 0x68
|
||||
#define BMI270_ADDR_ALT 0x69
|
||||
#define ICM42607P_ADDR 0x68
|
||||
#define ICM42607P_ADDR_ALT 0x69
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// LED
|
||||
|
||||
@@ -37,8 +37,15 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150, BMI270};
|
||||
return firstOfOrNONE(10, types);
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX,
|
||||
ICM20948, QMA6100P, BMM150, BMI270, ICM42607P};
|
||||
return firstOfOrNONE(11, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstMagnetometer() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {MMC5983MA};
|
||||
return firstOfOrNONE(1, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAQI() const
|
||||
|
||||
@@ -41,6 +41,7 @@ class ScanI2C
|
||||
QMI8658,
|
||||
QMC5883L,
|
||||
HMC5883L,
|
||||
MMC5983MA,
|
||||
PMSA003I,
|
||||
QMA6100P,
|
||||
MPU6050,
|
||||
@@ -65,6 +66,7 @@ class ScanI2C
|
||||
FT6336U,
|
||||
STK8BAXX,
|
||||
ICM20948,
|
||||
ICM42607P,
|
||||
SCD4X,
|
||||
MAX30102,
|
||||
TPS65233,
|
||||
@@ -149,6 +151,8 @@ class ScanI2C
|
||||
|
||||
FoundDevice firstAccelerometer() const;
|
||||
|
||||
FoundDevice firstMagnetometer() const;
|
||||
|
||||
FoundDevice firstAQI() const;
|
||||
|
||||
FoundDevice firstRGBLED() const;
|
||||
|
||||
@@ -360,9 +360,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address);
|
||||
#ifdef HAS_NCP5623
|
||||
SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623", (uint8_t)addr.address);
|
||||
#endif
|
||||
#ifdef HAS_LP5562
|
||||
SCAN_SIMPLE_CASE(LP5562_ADDR, LP5562, "LP5562", (uint8_t)addr.address);
|
||||
#endif
|
||||
case XPOWERS_AXP192_AXP2101_ADDRESS:
|
||||
// Do we have the axp2101/192 or the TCA8418
|
||||
@@ -600,6 +597,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
#else
|
||||
SCAN_SIMPLE_CASE(PMSA003I_ADDR, PMSA003I, "PMSA003I", (uint8_t)addr.address)
|
||||
#endif
|
||||
case MMC5983MA_ADDR:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x2F), 1);
|
||||
if (registerValue == 0x30) {
|
||||
type = MMC5983MA;
|
||||
logFoundDevice("MMC5983MA", (uint8_t)addr.address);
|
||||
#ifdef HAS_LP5562
|
||||
} else {
|
||||
type = LP5562;
|
||||
logFoundDevice("LP5562", (uint8_t)addr.address);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case BMA423_ADDR: // this can also be LIS3DH_ADDR_ALT
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2);
|
||||
if (registerValue == 0x3300 || registerValue == 0x3333) { // RAK4631 WisBlock has LIS3DH register at 0x3333
|
||||
@@ -754,8 +763,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
}
|
||||
break;
|
||||
|
||||
case ICM20948_ADDR: // same as BMX160_ADDR, BMI270_ADDR_ALT, and SEN5X_ADDR
|
||||
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR, BMI270_ADDR
|
||||
case ICM20948_ADDR: // same as BMX160_ADDR, BMI270_ADDR_ALT, ICM42607P_ADDR_ALT, and SEN5X_ADDR
|
||||
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR, BMI270_ADDR, and ICM42607P_ADDR
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
|
||||
#ifdef HAS_ICM20948
|
||||
type = ICM20948;
|
||||
@@ -775,6 +784,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
logFoundDevice("BMX160", (uint8_t)addr.address);
|
||||
break;
|
||||
} else {
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x75), 1); // WHO_AM_I
|
||||
if (registerValue == 0x60) {
|
||||
type = ICM42607P;
|
||||
logFoundDevice("ICM-42607-P", (uint8_t)addr.address);
|
||||
break;
|
||||
}
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
|
||||
String prod = "";
|
||||
prod = readSEN5xProductName(i2cBus, addr.address);
|
||||
|
||||
+236
-456
@@ -505,15 +505,12 @@ bool GPS::setup()
|
||||
int msglen = 0;
|
||||
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
if (probeTries < GPS_PROBETRIES) {
|
||||
bootString = "Probing GPS...";
|
||||
gnssModel = probe(serialSpeeds[speedSelect]);
|
||||
if (gnssModel == GNSS_MODEL_UNKNOWN) {
|
||||
if (currentStep == 0 && ++speedSelect == array_count(serialSpeeds)) {
|
||||
speedSelect = 0;
|
||||
++probeTries;
|
||||
}
|
||||
} else {
|
||||
currentStep = 0;
|
||||
}
|
||||
}
|
||||
// Rare Serial Speeds
|
||||
@@ -525,8 +522,6 @@ bool GPS::setup()
|
||||
LOG_WARN("Give up on GPS probe and set to %d", GPS_BAUDRATE);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
currentStep = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -543,133 +538,86 @@ bool GPS::setup()
|
||||
* t-beam-s3-core uses the same L76K GNSS module as t-echo.
|
||||
* Unlike t-echo, L76K uses 9600 baud rate for communication by default.
|
||||
* */
|
||||
if (currentStep == 0) {
|
||||
// Initialize the L76K Chip, use GPS + GLONASS + BEIDOU
|
||||
_serial_gps->write("$PCAS04,7*1E\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 1) {
|
||||
// only ask for RMC and GGA
|
||||
_serial_gps->write("$PCAS03,1,0,0,0,1,0,0,0,0,0,,,0,0*02\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 2) {
|
||||
// Switch to Vehicle Mode, since SoftRF enables Aviation < 2g
|
||||
_serial_gps->write("$PCAS11,3*1E\r\n");
|
||||
currentDelay = 250;
|
||||
}
|
||||
|
||||
// Initialize the L76K Chip, use GPS + GLONASS + BEIDOU
|
||||
_serial_gps->write("$PCAS04,7*1E\r\n");
|
||||
delay(250);
|
||||
// only ask for RMC and GGA
|
||||
_serial_gps->write("$PCAS03,1,0,0,0,1,0,0,0,0,0,,,0,0*02\r\n");
|
||||
delay(250);
|
||||
// Switch to Vehicle Mode, since SoftRF enables Aviation < 2g
|
||||
_serial_gps->write("$PCAS11,3*1E\r\n");
|
||||
delay(250);
|
||||
} else if (gnssModel == GNSS_MODEL_MTK_L76B) {
|
||||
// Waveshare Pico-GPS hat uses the L76B with 9600 baud
|
||||
// Initialize the L76B Chip, use GPS + GLONASS
|
||||
// See note in L76_Series_GNSS_Protocol_Specification, chapter 3.29
|
||||
if (currentStep == 0) {
|
||||
_serial_gps->write("$PMTK353,1,1,0,0,0*2B\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 1000;
|
||||
return false;
|
||||
} else if (currentStep == 1) {
|
||||
// Above command will reset the GPS and takes longer before it will accept new commands
|
||||
// only ask for RMC and GGA (GNRMC and GNGGA)
|
||||
// See note in L76_Series_GNSS_Protocol_Specification, chapter 2.1
|
||||
_serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 2) {
|
||||
// Enable SBAS
|
||||
_serial_gps->write("$PMTK301,2*2E\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 3) {
|
||||
// Enable PPS for 2D/3D fix only
|
||||
_serial_gps->write("$PMTK285,3,100*3F\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 4) {
|
||||
// Switch to Fitness Mode, for running and walking purpose with low speed (<5 m/s)
|
||||
_serial_gps->write("$PMTK886,1*29\r\n");
|
||||
currentDelay = 250;
|
||||
}
|
||||
_serial_gps->write("$PMTK353,1,1,0,0,0*2B\r\n");
|
||||
// Above command will reset the GPS and takes longer before it will accept new commands
|
||||
delay(1000);
|
||||
// only ask for RMC and GGA (GNRMC and GNGGA)
|
||||
// See note in L76_Series_GNSS_Protocol_Specification, chapter 2.1
|
||||
_serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
|
||||
delay(250);
|
||||
// Enable SBAS
|
||||
_serial_gps->write("$PMTK301,2*2E\r\n");
|
||||
delay(250);
|
||||
// Enable PPS for 2D/3D fix only
|
||||
_serial_gps->write("$PMTK285,3,100*3F\r\n");
|
||||
delay(250);
|
||||
// Switch to Fitness Mode, for running and walking purpose with low speed (<5 m/s)
|
||||
_serial_gps->write("$PMTK886,1*29\r\n");
|
||||
delay(250);
|
||||
} else if (gnssModel == GNSS_MODEL_MTK_PA1010D) {
|
||||
// PA1010D is used in the Pimoroni GPS board.
|
||||
if (currentStep == 0) {
|
||||
// Enable all constellations.
|
||||
_serial_gps->write("$PMTK353,1,1,1,1,1*2A\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 1000;
|
||||
return false;
|
||||
} else if (currentStep == 1) {
|
||||
// Above command will reset the GPS and takes longer before it will accept new commands
|
||||
// Only ask for RMC and GGA (GNRMC and GNGGA)
|
||||
_serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 2) {
|
||||
// Enable SBAS / WAAS
|
||||
_serial_gps->write("$PMTK301,2*2E\r\n");
|
||||
currentDelay = 250;
|
||||
}
|
||||
|
||||
// Enable all constellations.
|
||||
_serial_gps->write("$PMTK353,1,1,1,1,1*2A\r\n");
|
||||
// Above command will reset the GPS and takes longer before it will accept new commands
|
||||
delay(1000);
|
||||
// Only ask for RMC and GGA (GNRMC and GNGGA)
|
||||
_serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
|
||||
delay(250);
|
||||
// Enable SBAS / WAAS
|
||||
_serial_gps->write("$PMTK301,2*2E\r\n");
|
||||
delay(250);
|
||||
} else if (gnssModel == GNSS_MODEL_MTK_PA1616S) {
|
||||
// PA1616S is used in some GPS breakout boards from Adafruit
|
||||
// PA1616S does not have GLONASS capability. PA1616D does, but is not implemented here.
|
||||
if (currentStep == 0) {
|
||||
_serial_gps->write("$PMTK353,1,0,0,0,0*2A\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 1000;
|
||||
return false;
|
||||
} else if (currentStep == 1) {
|
||||
// Above command will reset the GPS and takes longer before it will accept new commands
|
||||
// Only ask for RMC and GGA (GNRMC and GNGGA)
|
||||
_serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 2) {
|
||||
// Enable SBAS / WAAS
|
||||
_serial_gps->write("$PMTK301,2*2E\r\n");
|
||||
currentDelay = 250;
|
||||
}
|
||||
_serial_gps->write("$PMTK353,1,0,0,0,0*2A\r\n");
|
||||
// Above command will reset the GPS and takes longer before it will accept new commands
|
||||
delay(1000);
|
||||
// Only ask for RMC and GGA (GNRMC and GNGGA)
|
||||
_serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
|
||||
delay(250);
|
||||
// Enable SBAS / WAAS
|
||||
_serial_gps->write("$PMTK301,2*2E\r\n");
|
||||
delay(250);
|
||||
} else if (gnssModel == GNSS_MODEL_ATGM336H) {
|
||||
if (currentStep == 0) {
|
||||
// Set the initial configuration of the device - these _should_ work for most AT6558 devices
|
||||
msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x07, 250) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("ATGM336H: Could not set Config");
|
||||
}
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 1) {
|
||||
// Set the update frequency to 1Hz
|
||||
msglen = makeCASPacket(0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x04, 250) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("ATGM336H: Could not set Update Frequency");
|
||||
}
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 2) {
|
||||
// Set the NEMA output messages - Ask for only RMC and GGA
|
||||
// Set the initial configuration of the device - these _should_ work for most AT6558 devices
|
||||
msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x07, 250) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("ATGM336H: Could not set Config");
|
||||
}
|
||||
|
||||
// Set the update frequency to 1Hz
|
||||
msglen = makeCASPacket(0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x04, 250) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("ATGM336H: Could not set Update Frequency");
|
||||
}
|
||||
|
||||
// Set the NEMA output messages
|
||||
// Ask for only RMC and GGA
|
||||
uint8_t fields[] = {CAS_NEMA_RMC, CAS_NEMA_GGA};
|
||||
for (unsigned int i = 0; i < sizeof(fields); i++) {
|
||||
// Construct a CAS-CFG-MSG packet
|
||||
uint8_t cas_cfg_msg_packet[] = {0x4e, CAS_NEMA_RMC, 0x01, 0x00};
|
||||
uint8_t cas_cfg_msg_packet[] = {0x4e, fields[i], 0x01, 0x00};
|
||||
msglen = makeCASPacket(0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x01, 250) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("ATGM336H: Could not enable NMEA MSG: %d", CAS_NEMA_RMC);
|
||||
}
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 3) {
|
||||
uint8_t cas_cfg_msg_packet[] = {0x4e, CAS_NEMA_GGA, 0x01, 0x00};
|
||||
msglen = makeCASPacket(0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACKCas(0x06, 0x01, 250) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("ATGM336H: Could not enable NMEA MSG: %d", CAS_NEMA_GGA);
|
||||
LOG_WARN("ATGM336H: Could not enable NMEA MSG: %d", fields[i]);
|
||||
}
|
||||
}
|
||||
} else if (gnssModel == GNSS_MODEL_UC6580) {
|
||||
@@ -677,363 +625,195 @@ bool GPS::setup()
|
||||
// use GPS L1 & L5 + BDS B1I & B2a + GLONASS L1 + GALILEO E1 & E5a + SBAS + QZSS
|
||||
// This will reset the receiver, so wait a bit afterwards
|
||||
// The paranoid will wait for the OK*04 confirmation response after each command.
|
||||
if (currentStep == 0) {
|
||||
_serial_gps->write("$CFGSYS,h35155\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 1) {
|
||||
// Must be done after the CFGSYS command
|
||||
// Turn off GSV messages, we don't really care about which and where the sats are, maybe someday.
|
||||
_serial_gps->write("$CFGMSG,0,3,0\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 2) {
|
||||
// Turn off GSA messages, TinyGPS++ doesn't use this message.
|
||||
_serial_gps->write("$CFGMSG,0,2,0\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 3) {
|
||||
// Turn off NOTICE __TXT messages, these may provide Unicore some info but we don't care.
|
||||
_serial_gps->write("$CFGMSG,6,0,0\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 4) {
|
||||
_serial_gps->write("$CFGMSG,6,1,0\r\n");
|
||||
currentDelay = 250;
|
||||
}
|
||||
_serial_gps->write("$CFGSYS,h35155\r\n");
|
||||
delay(750);
|
||||
// Must be done after the CFGSYS command
|
||||
// Turn off GSV messages, we don't really care about which and where the sats are, maybe someday.
|
||||
_serial_gps->write("$CFGMSG,0,3,0\r\n");
|
||||
delay(250);
|
||||
// Turn off GSA messages, TinyGPS++ doesn't use this message.
|
||||
_serial_gps->write("$CFGMSG,0,2,0\r\n");
|
||||
delay(250);
|
||||
// Turn off NOTICE __TXT messages, these may provide Unicore some info but we don't care.
|
||||
_serial_gps->write("$CFGMSG,6,0,0\r\n");
|
||||
delay(250);
|
||||
_serial_gps->write("$CFGMSG,6,1,0\r\n");
|
||||
delay(250);
|
||||
} else if (IS_ONE_OF(gnssModel, GNSS_MODEL_AG3335, GNSS_MODEL_AG3352)) {
|
||||
if (currentStep == 0) {
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_IN ||
|
||||
config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_NP_865) {
|
||||
_serial_gps->write("$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC
|
||||
// GPS GLONASS GALILEO BDS QZSS NAVIC
|
||||
// 1 0 1 0 0 1
|
||||
} else {
|
||||
_serial_gps->write("$PAIR066,1,1,1,1,0,0*3A\r\n"); // Enable GPS+GLONASS+GALILEO+BDS
|
||||
// GPS GLONASS GALILEO BDS QZSS NAVIC
|
||||
// 1 1 1 1 0 0
|
||||
}
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 1) {
|
||||
// Configure NMEA (sentences will output once per fix)
|
||||
_serial_gps->write("$PAIR062,0,1*3F\r\n"); // GGA ON
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 2) {
|
||||
_serial_gps->write("$PAIR062,1,0*3F\r\n"); // GLL OFF
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 3) {
|
||||
_serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 4) {
|
||||
_serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 5) {
|
||||
_serial_gps->write("$PAIR062,4,1*3B\r\n"); // RMC ON
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 6) {
|
||||
_serial_gps->write("$PAIR062,5,0*3B\r\n"); // VTG OFF
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 7) {
|
||||
_serial_gps->write("$PAIR062,6,0*38\r\n"); // ZDA ON
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 8) {
|
||||
_serial_gps->write("$PAIR513*3D\r\n"); // save configuration
|
||||
|
||||
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_IN ||
|
||||
config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_NP_865) {
|
||||
_serial_gps->write("$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC
|
||||
// GPS GLONASS GALILEO BDS QZSS NAVIC
|
||||
// 1 0 1 0 0 1
|
||||
} else {
|
||||
_serial_gps->write("$PAIR066,1,1,1,1,0,0*3A\r\n"); // Enable GPS+GLONASS+GALILEO+BDS
|
||||
// GPS GLONASS GALILEO BDS QZSS NAVIC
|
||||
// 1 1 1 1 0 0
|
||||
}
|
||||
// Configure NMEA (sentences will output once per fix)
|
||||
_serial_gps->write("$PAIR062,0,1*3F\r\n"); // GGA ON
|
||||
_serial_gps->write("$PAIR062,1,0*3F\r\n"); // GLL OFF
|
||||
_serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF
|
||||
_serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF
|
||||
_serial_gps->write("$PAIR062,4,1*3B\r\n"); // RMC ON
|
||||
_serial_gps->write("$PAIR062,5,0*3B\r\n"); // VTG OFF
|
||||
_serial_gps->write("$PAIR062,6,0*38\r\n"); // ZDA ON
|
||||
|
||||
delay(250);
|
||||
_serial_gps->write("$PAIR513*3D\r\n"); // save configuration
|
||||
} else if (gnssModel == GNSS_MODEL_UBLOX6) {
|
||||
if (currentStep == 0) {
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "disable text info messages", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 1) {
|
||||
SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "enable interference resistance", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 2) {
|
||||
SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "configure NAVX5 settings", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 3) {
|
||||
// Turn off unwanted NMEA messages, set update rate
|
||||
SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "set GPS update rate", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 4) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "disable NMEA GLL", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 5) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "enable NMEA GSA", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 6) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "disable NMEA GSV", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 7) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "disable NMEA VTG", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 8) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "enable NMEA RMC", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 9) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "enable NMEA GGA", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 10) {
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_ECO, "enable powersave ECO mode for Neo-6", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 11) {
|
||||
SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 12) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_AID, "disable UBX-AID", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 13) {
|
||||
msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("Unable to save GNSS module config");
|
||||
} else {
|
||||
LOG_INFO("GNSS module config saved!");
|
||||
}
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "disable text info messages", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "enable interference resistance", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "configure NAVX5 settings", 500);
|
||||
|
||||
// Turn off unwanted NMEA messages, set update rate
|
||||
SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "set GPS update rate", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "disable NMEA GLL", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "enable NMEA GSA", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "disable NMEA GSV", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "disable NMEA VTG", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "enable NMEA RMC", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "enable NMEA GGA", 500);
|
||||
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_ECO, "enable powersave ECO mode for Neo-6", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_AID, "disable UBX-AID", 500);
|
||||
|
||||
msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("Unable to save GNSS module config");
|
||||
} else {
|
||||
LOG_INFO("GNSS module config saved!");
|
||||
}
|
||||
} else if (IS_ONE_OF(gnssModel, GNSS_MODEL_UBLOX7, GNSS_MODEL_UBLOX8, GNSS_MODEL_UBLOX9)) {
|
||||
if (currentStep == 0) {
|
||||
if (gnssModel == GNSS_MODEL_UBLOX7) {
|
||||
LOG_DEBUG("Set GPS+SBAS");
|
||||
msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_7), _message_GNSS_7);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
} else { // 8,9
|
||||
msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_8), _message_GNSS_8);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
}
|
||||
|
||||
if (getACK(0x06, 0x3e, 800) == GNSS_RESPONSE_NAK) {
|
||||
// It's not critical if the module doesn't acknowledge this configuration.
|
||||
LOG_DEBUG("reconfigure GNSS - defaults maintained. Is this module GPS-only?");
|
||||
} else {
|
||||
if (gnssModel == GNSS_MODEL_UBLOX7) {
|
||||
LOG_DEBUG("Set GPS+SBAS");
|
||||
msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_7), _message_GNSS_7);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
LOG_INFO("GPS+SBAS configured");
|
||||
} else { // 8,9
|
||||
msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_8), _message_GNSS_8);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
LOG_INFO("GPS+SBAS+GLONASS+Galileo configured");
|
||||
}
|
||||
if (getACK(0x06, 0x3e, 800) == GNSS_RESPONSE_NAK) {
|
||||
// It's not critical if the module doesn't acknowledge this configuration.
|
||||
LOG_DEBUG("reconfigure GNSS - defaults maintained. Is this module GPS-only?");
|
||||
} else {
|
||||
if (gnssModel == GNSS_MODEL_UBLOX7) {
|
||||
LOG_INFO("GPS+SBAS configured");
|
||||
} else { // 8,9
|
||||
LOG_INFO("GPS+SBAS+GLONASS+Galileo configured");
|
||||
}
|
||||
// Documentation say, we need wait at least 0.5s after reconfiguration of GNSS module, before sending next
|
||||
// commands for the M8 it tends to be more... 1 sec should be enough ;>)
|
||||
delay(1000);
|
||||
}
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 1) {
|
||||
// Disable Text Info messages //6,7,8,9
|
||||
// Documentation say, we need wait at least 0.5s after reconfiguration of GNSS module, before sending next
|
||||
// commands for the M8 it tends to be more... 1 sec should be enough ;>)
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
// Disable Text Info messages //6,7,8,9
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "disable text info messages", 500);
|
||||
|
||||
if (gnssModel == GNSS_MODEL_UBLOX8) { // 8
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "disable text info messages", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 2) {
|
||||
if (gnssModel == GNSS_MODEL_UBLOX8) { // 8
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x39, _message_JAM_8, "enable interference resistance", 500);
|
||||
} else { // 7,9
|
||||
SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "enable interference resistance", 500);
|
||||
}
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 3) {
|
||||
if (gnssModel == GNSS_MODEL_UBLOX8) { // 8
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5_8, "configure NAVX5_8 settings", 500);
|
||||
} else { // 7,9
|
||||
SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "configure NAVX5 settings", 500);
|
||||
}
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 4) {
|
||||
// Turn off unwanted NMEA messages, set update rate
|
||||
SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "set GPS update rate", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 5) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "disable NMEA GLL", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 6) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "enable NMEA GSA", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 7) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "disable NMEA GSV", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 8) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "disable NMEA VTG", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 9) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "enable NMEA RMC", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 10) {
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "enable NMEA GGA", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 11) {
|
||||
if (ublox_info.protocol_version >= 18) {
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x86, _message_PMS, "enable powersave for GPS", 500);
|
||||
} else {
|
||||
SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_PSM, "enable powersave mode for GPS", 500);
|
||||
}
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 12) {
|
||||
SEND_UBX_PACKET(0x06, 0x39, _message_JAM_8, "enable interference resistance", 500);
|
||||
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5_8, "configure NAVX5_8 settings", 500);
|
||||
} else { // 6,7,9
|
||||
SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "enable interference resistance", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "configure NAVX5 settings", 500);
|
||||
}
|
||||
// Turn off unwanted NMEA messages, set update rate
|
||||
SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "set GPS update rate", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "disable NMEA GLL", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "enable NMEA GSA", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "disable NMEA GSV", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "disable NMEA VTG", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "enable NMEA RMC", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "enable NMEA GGA", 500);
|
||||
|
||||
if (ublox_info.protocol_version >= 18) {
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x86, _message_PMS, "enable powersave for GPS", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
} else if (currentStep == 13) {
|
||||
|
||||
// For M8 we want to enable NMEA version 4.10 so we can see the additional sats.
|
||||
if (ublox_info.protocol_version >= 18 && gnssModel == GNSS_MODEL_UBLOX8) {
|
||||
if (gnssModel == GNSS_MODEL_UBLOX8) {
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x17, _message_NMEA, "enable NMEA 4.10", 500);
|
||||
currentStep++;
|
||||
return false;
|
||||
}
|
||||
msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("Unable to save GNSS module config");
|
||||
} else {
|
||||
LOG_INFO("GNSS module configuration saved!");
|
||||
}
|
||||
} else if (currentStep == 14) {
|
||||
msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("Unable to save GNSS module config");
|
||||
} else {
|
||||
LOG_INFO("GNSS module configuration saved!");
|
||||
}
|
||||
} else {
|
||||
SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_PSM, "enable powersave mode for GPS", 500);
|
||||
SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersave details for GPS", 500);
|
||||
}
|
||||
|
||||
msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("Unable to save GNSS module config");
|
||||
} else {
|
||||
LOG_INFO("GNSS module configuration saved!");
|
||||
}
|
||||
} else if (gnssModel == GNSS_MODEL_UBLOX10) {
|
||||
if (currentStep == 0) {
|
||||
LOG_INFO("Configuring M10 GPS step 0");
|
||||
delay(1000);
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_RAM, "disable NMEA messages in M10 RAM", 300);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 1) {
|
||||
LOG_INFO("Configuring M10 GPS step 1");
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_BBR, "disable NMEA messages in M10 BBR", 300);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 2) {
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_RAM, "disable Info messages for M10 GPS RAM", 300);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 3) {
|
||||
// Next disable Info txt messages in BBR layer
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_BBR, "disable Info messages for M10 GPS BBR", 300);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 4) {
|
||||
// Do M10 configuration for Power Management.
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_RAM, "enable powersave for M10 GPS RAM", 300);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 5) {
|
||||
// Next enable powersave in BBR layer
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_BBR, "enable powersave for M10 GPS BBR", 300);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 6) {
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_RAM, "enable jam detection M10 GPS RAM", 300);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 7) {
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_BBR, "enable jam detection M10 GPS BBR", 300);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 8) {
|
||||
// Here is where the init commands should go to do further M10 initialization.
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_RAM, "disable SBAS M10 GPS RAM", 300);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 9) {
|
||||
// will cause a receiver restart so wait a bit
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_BBR, "disable SBAS M10 GPS BBR", 300);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 10) {
|
||||
// Done with initialization, Now enable wanted NMEA messages in BBR layer so they will survive a periodic
|
||||
// sleep.
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_BBR, "enable messages for M10 GPS BBR", 300);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 11) {
|
||||
// Next enable wanted NMEA messages in RAM layer
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_RAM, "enable messages for M10 GPS RAM", 500);
|
||||
currentStep++;
|
||||
currentDelay = 750;
|
||||
return false;
|
||||
} else if (currentStep == 12) {
|
||||
// As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR.
|
||||
// BBR will survive a restart, and power off for a while, but modules with small backup
|
||||
// batteries or super caps will not retain the config for a long power off time.
|
||||
msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE_10), _message_SAVE_10);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("Unable to save GNSS module config");
|
||||
} else {
|
||||
LOG_INFO("GNSS module configuration saved!");
|
||||
}
|
||||
didSerialInit = true;
|
||||
delay(1000);
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_RAM, "disable NMEA messages in M10 RAM", 300);
|
||||
delay(750);
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_BBR, "disable NMEA messages in M10 BBR", 300);
|
||||
delay(750);
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_RAM, "disable Info messages for M10 GPS RAM", 300);
|
||||
delay(750);
|
||||
// Next disable Info txt messages in BBR layer
|
||||
clearBuffer();
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_BBR, "disable Info messages for M10 GPS BBR", 300);
|
||||
delay(750);
|
||||
// Do M10 configuration for Power Management.
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_RAM, "enable powersave for M10 GPS RAM", 300);
|
||||
delay(750);
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_BBR, "enable powersave for M10 GPS BBR", 300);
|
||||
delay(750);
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_RAM, "enable jam detection M10 GPS RAM", 300);
|
||||
delay(750);
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_BBR, "enable jam detection M10 GPS BBR", 300);
|
||||
delay(750);
|
||||
// Here is where the init commands should go to do further M10 initialization.
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_RAM, "disable SBAS M10 GPS RAM", 300);
|
||||
delay(750); // will cause a receiver restart so wait a bit
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_BBR, "disable SBAS M10 GPS BBR", 300);
|
||||
delay(750); // will cause a receiver restart so wait a bit
|
||||
|
||||
// Done with initialization, Now enable wanted NMEA messages in BBR layer so they will survive a periodic
|
||||
// sleep.
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_BBR, "enable messages for M10 GPS BBR", 300);
|
||||
delay(750);
|
||||
// Next enable wanted NMEA messages in RAM layer
|
||||
SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_RAM, "enable messages for M10 GPS RAM", 500);
|
||||
delay(750);
|
||||
|
||||
// As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR.
|
||||
// BBR will survive a restart, and power off for a while, but modules with small backup
|
||||
// batteries or super caps will not retain the config for a long power off time.
|
||||
msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE_10), _message_SAVE_10);
|
||||
_serial_gps->write(UBXscratch, msglen);
|
||||
if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
|
||||
LOG_WARN("Unable to save GNSS module config");
|
||||
} else {
|
||||
LOG_INFO("GNSS module configuration saved!");
|
||||
}
|
||||
} else if (gnssModel == GNSS_MODEL_CM121) {
|
||||
// only ask for RMC and GGA
|
||||
if (currentStep == 0) {
|
||||
// enable GGA
|
||||
_serial_gps->write("$CFGMSG,0,0,1,1*1B\r\n");
|
||||
currentStep++;
|
||||
currentDelay = 250;
|
||||
return false;
|
||||
} else if (currentStep == 1) {
|
||||
// enable RMC
|
||||
_serial_gps->write("$CFGMSG,0,4,1,1*1F\r\n");
|
||||
currentDelay = 250;
|
||||
}
|
||||
// enable GGA
|
||||
_serial_gps->write("$CFGMSG,0,0,1,1*1B\r\n");
|
||||
delay(250);
|
||||
// enable RMC
|
||||
_serial_gps->write("$CFGMSG,0,4,1,1*1F\r\n");
|
||||
delay(250);
|
||||
}
|
||||
didSerialInit = true;
|
||||
}
|
||||
|
||||
@@ -128,8 +128,6 @@ class GPS : private concurrency::OSThread
|
||||
// Let the GPS hardware save power between updates
|
||||
void down();
|
||||
|
||||
bool initFinished() const { return GPSInitFinished; };
|
||||
|
||||
private:
|
||||
GPS() : concurrency::OSThread("GPS") {}
|
||||
|
||||
|
||||
@@ -891,7 +891,7 @@ int32_t Screen::runOnce()
|
||||
// Show boot screen for first logo_timeout seconds, then switch to normal operation.
|
||||
// serialSinceMsec adjusts for additional serial wait time during nRF52 bootup
|
||||
static bool showingBootScreen = true;
|
||||
if (showingBootScreen && (!gps || gps->initFinished()) && (millis() > (logo_timeout + serialSinceMsec))) {
|
||||
if (showingBootScreen && (millis() > (logo_timeout + serialSinceMsec))) {
|
||||
LOG_INFO("Done with boot screen");
|
||||
stopBootScreen();
|
||||
showingBootScreen = false;
|
||||
|
||||
@@ -1458,7 +1458,8 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON);
|
||||
#elif defined(HACKADAY_COMMUNICATOR)
|
||||
tft->displayOn();
|
||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096)
|
||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096) && \
|
||||
!defined(HELTEC_MESH_NODE_T1)
|
||||
tft->wakeup();
|
||||
tft->powerSaveOff();
|
||||
#endif
|
||||
@@ -1469,7 +1470,7 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
#ifdef UNPHONE
|
||||
unphone.backlight(true); // using unPhone library
|
||||
#endif
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
|
||||
#elif !defined(M5STACK) && !defined(ST7789_CS) && \
|
||||
!defined(HACKADAY_COMMUNICATOR) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
|
||||
tft->setBrightness(172);
|
||||
@@ -1485,7 +1486,8 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON);
|
||||
#elif defined(HACKADAY_COMMUNICATOR)
|
||||
tft->displayOff();
|
||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096)
|
||||
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096) && \
|
||||
!defined(HELTEC_MESH_NODE_T1)
|
||||
tft->sleep();
|
||||
tft->powerSaveOn();
|
||||
#endif
|
||||
@@ -1496,7 +1498,7 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
#ifdef UNPHONE
|
||||
unphone.backlight(false); // using unPhone library
|
||||
#endif
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
|
||||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
|
||||
tft->setBrightness(0);
|
||||
#endif
|
||||
@@ -1511,7 +1513,7 @@ void TFTDisplay::sendCommand(uint8_t com)
|
||||
|
||||
void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
|
||||
{
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
|
||||
// todo
|
||||
#elif !defined(HACKADAY_COMMUNICATOR)
|
||||
tft->setBrightness(_brightness);
|
||||
@@ -1531,7 +1533,8 @@ bool TFTDisplay::hasTouch(void)
|
||||
{
|
||||
#ifdef RAK14014
|
||||
return true;
|
||||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096)
|
||||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && \
|
||||
!defined(HELTEC_MESH_NODE_T1)
|
||||
return tft->touch() != nullptr;
|
||||
#else
|
||||
return false;
|
||||
@@ -1550,7 +1553,8 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096)
|
||||
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && \
|
||||
!defined(HELTEC_MESH_NODE_T1)
|
||||
return tft->getTouch(x, y);
|
||||
#else
|
||||
return false;
|
||||
@@ -1567,7 +1571,7 @@ bool TFTDisplay::connect()
|
||||
{
|
||||
concurrency::LockGuard g(spiLock);
|
||||
LOG_INFO("Do TFT init");
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
|
||||
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
|
||||
tft = new TFT_eSPI;
|
||||
#elif defined(HACKADAY_COMMUNICATOR)
|
||||
bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, 38 /* SCK */, 21 /* MOSI */, GFX_NOT_DEFINED /* MISO */, HSPI /* spi_num */);
|
||||
|
||||
@@ -1333,6 +1333,11 @@ void menuHandler::positionBaseMenu()
|
||||
if (accelerometerThread) {
|
||||
accelerometerThread->calibrate(30);
|
||||
}
|
||||
#endif
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_MAGNETOMETER
|
||||
if (magnetometerThread) {
|
||||
magnetometerThread->calibrate(30);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case PositionAction::GPSSmartPosition:
|
||||
|
||||
@@ -1505,24 +1505,6 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
|
||||
display->drawString(x + getStringCenteredX(title) + 1, y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - 5, title);
|
||||
}
|
||||
display->setFont(FONT_SMALL);
|
||||
if (bootString != nullptr) {
|
||||
static uint8_t bootStringStep = 0;
|
||||
char stringCharacter = '.';
|
||||
uint32_t stringWidth = getStringCenteredX(bootString);
|
||||
if (bootStringStep == 0) {
|
||||
stringCharacter = '.';
|
||||
bootStringStep++;
|
||||
} else if (bootStringStep == 1) {
|
||||
stringCharacter = 'o';
|
||||
bootStringStep++;
|
||||
} else if (bootStringStep == 2) {
|
||||
stringCharacter = 'O';
|
||||
bootStringStep = 0;
|
||||
}
|
||||
char tmpBootString[40];
|
||||
snprintf(tmpBootString, sizeof(tmpBootString), "%s %c", bootString, stringCharacter);
|
||||
display->drawString(x + stringWidth, y + SCREEN_HEIGHT - 2 * FONT_HEIGHT_MEDIUM, tmpBootString);
|
||||
}
|
||||
// Draw region in upper left
|
||||
if (upperMsg) {
|
||||
display->drawString(x + 5, y + 5, upperMsg);
|
||||
|
||||
+23
-2
@@ -92,8 +92,6 @@ NRF54L15Bluetooth *nrf54l15Bluetooth = nullptr;
|
||||
#include <string>
|
||||
#endif
|
||||
|
||||
const char *bootString =
|
||||
nullptr; // Pointer to a string that will be drawn on the boot screen, if set (used for GPS probing status)
|
||||
#ifdef ARCH_ESP32
|
||||
#ifdef DEBUG_PARTITION_TABLE
|
||||
#include "esp_partition.h"
|
||||
@@ -138,6 +136,10 @@ void printPartitionTable()
|
||||
#include "motion/AccelerometerThread.h"
|
||||
AccelerometerThread *accelerometerThread = nullptr;
|
||||
#endif
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_MAGNETOMETER
|
||||
#include "motion/MagnetometerThread.h"
|
||||
MagnetometerThread *magnetometerThread = nullptr;
|
||||
#endif
|
||||
|
||||
#ifdef HAS_I2S
|
||||
#include "AudioThread.h"
|
||||
@@ -208,6 +210,8 @@ bool osk_found = false;
|
||||
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
|
||||
// The I2C address of the Accelerometer (if found)
|
||||
ScanI2C::DeviceAddress accelerometer_found = ScanI2C::ADDRESS_NONE;
|
||||
// The I2C address of the Magnetometer (if found)
|
||||
ScanI2C::DeviceAddress magnetometer_found = ScanI2C::ADDRESS_NONE;
|
||||
// The I2C address of the RGB LED (if found)
|
||||
ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ScanI2C::ADDRESS_NONE);
|
||||
/// The I2C address of our Air Quality Indicator (if found)
|
||||
@@ -433,6 +437,11 @@ void setup()
|
||||
digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE); // turn on the display power
|
||||
#endif
|
||||
|
||||
#if defined(PIN_SENSOR_EN)
|
||||
pinMode(PIN_SENSOR_EN, OUTPUT);
|
||||
digitalWrite(PIN_SENSOR_EN, PIN_SENSOR_EN_ACTIVE); // turn on sensor power
|
||||
#endif
|
||||
|
||||
#if defined(BIAS_T_ENABLE)
|
||||
pinMode(BIAS_T_ENABLE, OUTPUT);
|
||||
digitalWrite(BIAS_T_ENABLE, BIAS_T_VALUE); // turn on 5V for GPS Antenna
|
||||
@@ -673,6 +682,11 @@ void setup()
|
||||
accelerometer_found = acc_info.type != ScanI2C::DeviceType::NONE ? acc_info.address : accelerometer_found;
|
||||
LOG_DEBUG("acc_info = %i", acc_info.type);
|
||||
#endif
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_MAGNETOMETER
|
||||
auto mag_info = i2cScanner->firstMagnetometer();
|
||||
magnetometer_found = mag_info.type != ScanI2C::DeviceType::NONE ? mag_info.address : magnetometer_found;
|
||||
LOG_DEBUG("mag_info = %i", mag_info.type);
|
||||
#endif
|
||||
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226);
|
||||
@@ -685,6 +699,8 @@ void setup()
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MMC5983MA, meshtastic_TelemetrySensorType_MMC5983MA);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM42607P, meshtastic_TelemetrySensorType_ICM42607P);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
|
||||
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
|
||||
@@ -768,6 +784,11 @@ void setup()
|
||||
accelerometerThread = new AccelerometerThread(acc_info.type);
|
||||
}
|
||||
#endif
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_MAGNETOMETER
|
||||
if (mag_info.type != ScanI2C::DeviceType::NONE) {
|
||||
magnetometerThread = new MagnetometerThread(mag_info.type);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAS_NEOPIXEL) || defined(UNPHONE) || defined(RGBLED_RED)
|
||||
ambientLightingThread = new AmbientLightingThread(ScanI2C::DeviceType::NONE);
|
||||
|
||||
+5
-2
@@ -39,6 +39,7 @@ extern bool kb_found;
|
||||
extern bool osk_found;
|
||||
extern ScanI2C::DeviceAddress rtc_found;
|
||||
extern ScanI2C::DeviceAddress accelerometer_found;
|
||||
extern ScanI2C::DeviceAddress magnetometer_found;
|
||||
extern ScanI2C::FoundDevice rgb_found;
|
||||
extern ScanI2C::DeviceAddress aqi_found;
|
||||
|
||||
@@ -73,6 +74,10 @@ extern graphics::Screen *screen;
|
||||
#include "motion/AccelerometerThread.h"
|
||||
extern AccelerometerThread *accelerometerThread;
|
||||
#endif
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_MAGNETOMETER
|
||||
#include "motion/MagnetometerThread.h"
|
||||
extern MagnetometerThread *magnetometerThread;
|
||||
#endif
|
||||
|
||||
extern bool isVibrating;
|
||||
|
||||
@@ -105,5 +110,3 @@ void scannerToSensorsMap(const std::unique_ptr<ScanI2CTwoWire> &i2cScanner, Scan
|
||||
|
||||
// We default to 4MHz SPI, SPI mode 0
|
||||
extern SPISettings spiSettings;
|
||||
|
||||
extern const char *bootString;
|
||||
|
||||
+1
-1
@@ -132,7 +132,7 @@ inline bool shouldDropPacketForPreHop(const meshtastic_MeshPacket &p)
|
||||
if (isFromUs(&p)) {
|
||||
return false; // local-originated packets should never be dropped by pre-hop drop policy
|
||||
}
|
||||
return classifyHopStart(p) != HopStartStatus::VALID;
|
||||
return classifyHopStart(p) == HopStartStatus::INVALID;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +210,8 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
|
||||
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
|
||||
|
||||
uint8_t packetsInTxQueue() { return txQueue.getMaxLen() - txQueue.getFree(); }
|
||||
|
||||
/**
|
||||
* Update the noise floor measurement by sampling RSSI from a slow path.
|
||||
* This should not be called from radio interrupt or TX/RX critical paths.
|
||||
|
||||
@@ -0,0 +1,804 @@
|
||||
#include "DMShell.h"
|
||||
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
|
||||
#include "Channels.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "Throttle.h"
|
||||
#include "configuration.h"
|
||||
#include "mesh/generated/meshtastic/mesh.pb.h"
|
||||
#include "mesh/mesh-pb-constants.h"
|
||||
#include "pb_decode.h"
|
||||
#include "pb_encode.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <pty.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
DMShellModule *dmShellModule;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr uint16_t PTY_COLS_DEFAULT = 120;
|
||||
constexpr uint16_t PTY_ROWS_DEFAULT = 40;
|
||||
constexpr size_t MAX_MESSAGE_SIZE = 200;
|
||||
|
||||
// --- Half-duplex turn-taking ("talking stick") protocol, carried in RemoteShell.flags ---
|
||||
// On a 2-party LoRa link, Meshtastic's CSMA-CA breaks down when both ends transmit at once
|
||||
// (synchronized same-slot collisions that CAD can't prevent). These flags let exactly one
|
||||
// side transmit at a time, eliminating those collisions. The client is the master/idle-owner.
|
||||
constexpr uint32_t TURN_FLAG_GRANT = 0x01; // I am handing you the turn; you may transmit now
|
||||
constexpr uint32_t TURN_FLAG_MORE = 0x02; // I yielded under a budget but still have data queued
|
||||
constexpr uint32_t TURN_FLAG_RTS = 0x04; // I have output but no turn; please grant me one
|
||||
constexpr size_t TURN_BUDGET_FRAMES = 8; // max output frames per granted turn before yielding (bounds interrupt latency)
|
||||
constexpr uint32_t RTS_RETRY_MS = 250; // min interval between request-to-send frames
|
||||
// After being granted the turn we keep it for a short "linger" window, continuing to drain shell
|
||||
// output as it appears, instead of yielding the instant the PTY drains. This lets a command's
|
||||
// output (and the next prompt) ride the same turn as the keystroke that triggered it, avoiding a
|
||||
// full RTS->grant round-trip per command. The turn still ends promptly once the PTY is idle this
|
||||
// long, or once TURN_BUDGET_FRAMES is hit (so the client can interject, e.g. Ctrl-C).
|
||||
constexpr uint32_t TURN_LINGER_MS = 120;
|
||||
} // namespace
|
||||
|
||||
DMShellModule::DMShellModule()
|
||||
: SinglePortModule("DMShellModule", meshtastic_PortNum_REMOTE_SHELL_APP), concurrency::OSThread("DMShell", 100)
|
||||
{
|
||||
LOG_WARN("DMShell enabled on Portduino: remote shell access is dangerous and intended for trusted debugging only");
|
||||
}
|
||||
|
||||
ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
meshtastic_RemoteShell frame = meshtastic_RemoteShell_init_zero;
|
||||
if (!mp.pki_encrypted) {
|
||||
LOG_WARN("DMShell: ignoring packet without PKI from 0x%x", mp.from);
|
||||
return ProcessMessage::STOP;
|
||||
}
|
||||
|
||||
if (!parseFrame(mp, frame)) {
|
||||
LOG_WARN("DMShell: ignoring malformed frame");
|
||||
return ProcessMessage::STOP;
|
||||
}
|
||||
|
||||
if (frame.op == meshtastic_RemoteShell_OpCode_ACK) {
|
||||
if (session.active && frame.session_id == session.sessionId && getFrom(&mp) == session.peer) {
|
||||
LOG_WARN("DMShell: Received ack from 0x%x 0x%x", getFrom(&mp), session.peer);
|
||||
applyTurnFlags(frame);
|
||||
if (frame.last_rx_seq > 0) {
|
||||
resendFramesFrom(frame.last_rx_seq + 1);
|
||||
}
|
||||
// A standalone grant (client re-granting for MORE, replying to our RTS, or a heartbeat
|
||||
// poll) is our cue to flush any pending shell output during this turn.
|
||||
serviceTurn();
|
||||
}
|
||||
return ProcessMessage::CONTINUE;
|
||||
}
|
||||
|
||||
if (frame.op >= 64) {
|
||||
LOG_WARN("DMShell: ignoring frame with op code %d, seq %d", frame.op, frame.seq);
|
||||
return ProcessMessage::CONTINUE;
|
||||
}
|
||||
|
||||
if (!isAuthorizedPacket(mp)) {
|
||||
LOG_WARN("DMShell: unauthorized sender 0x%x, %u", mp.from, frame.op);
|
||||
myReply = allocErrorResponse(meshtastic_Routing_Error_NOT_AUTHORIZED, &mp);
|
||||
return ProcessMessage::STOP;
|
||||
}
|
||||
|
||||
if (frame.op == meshtastic_RemoteShell_OpCode_OPEN) {
|
||||
LOG_WARN("DMShell: received OPEN from 0x%x sessionId=0x%x", mp.from, frame.session_id);
|
||||
if (!openSession(mp, frame)) {
|
||||
sendError("open_failed", getFrom(&mp));
|
||||
}
|
||||
return ProcessMessage::STOP;
|
||||
}
|
||||
|
||||
if (!session.active || frame.session_id != session.sessionId || getFrom(&mp) != session.peer) {
|
||||
if (!session.active) {
|
||||
LOG_WARN("DMShell: no active session, rejecting op %d from 0x%x", frame.op, mp.from);
|
||||
} else {
|
||||
LOG_WARN("DMShell: session ID mismatch (got 0x%x expected 0x%x) or peer mismatch (got 0x%x expected 0x%x), rejecting "
|
||||
"op %d",
|
||||
frame.session_id, session.sessionId, mp.from, session.peer, frame.op);
|
||||
}
|
||||
sendError("invalid_session", getFrom(&mp));
|
||||
return ProcessMessage::STOP;
|
||||
}
|
||||
|
||||
// Honor channel-access flags before ordering checks: a GRANT transfers the turn regardless of
|
||||
// whether this frame's payload is in order.
|
||||
applyTurnFlags(frame);
|
||||
|
||||
if (!shouldProcessIncomingFrame(frame)) {
|
||||
// We won't process the payload (gap/duplicate), but we may now hold the turn, so flush
|
||||
// output and/or hand it back rather than stalling the link.
|
||||
serviceTurn();
|
||||
return ProcessMessage::STOP;
|
||||
}
|
||||
|
||||
session.lastActivityMs = millis();
|
||||
|
||||
switch (frame.op) {
|
||||
case meshtastic_RemoteShell_OpCode_INPUT:
|
||||
if (!writeSessionInput(frame)) {
|
||||
sendError("input_write_failed");
|
||||
} else if (!session.turnManaged) {
|
||||
// Legacy peer (no turn-taking): echo immediately as before. In managed mode the
|
||||
// serviceTurn() call at the end of handleReceived drains the echo and yields the turn.
|
||||
uint8_t outBuf[MAX_MESSAGE_SIZE];
|
||||
const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf));
|
||||
if (bytesRead > 0) {
|
||||
LOG_WARN("DMShell: read %zd bytes from PTY", bytesRead);
|
||||
meshtastic_RemoteShell frame = {
|
||||
.op = meshtastic_RemoteShell_OpCode_OUTPUT,
|
||||
.session_id = session.sessionId,
|
||||
.seq = session.nextTxSeq++,
|
||||
.ack_seq = session.lastAckedRxSeq,
|
||||
.cols = 0,
|
||||
.rows = 0,
|
||||
.flags = 0,
|
||||
};
|
||||
assert(bytesRead <= sizeof(frame.payload.bytes));
|
||||
memcpy(frame.payload.bytes, outBuf, bytesRead);
|
||||
frame.payload.size = bytesRead;
|
||||
sendFrameToPeer(session.peer, frame, true);
|
||||
session.lastActivityMs = millis();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case meshtastic_RemoteShell_OpCode_RESIZE:
|
||||
if (frame.rows > 0 && frame.cols > 0) {
|
||||
struct winsize ws = {};
|
||||
ws.ws_row = frame.rows;
|
||||
ws.ws_col = frame.cols;
|
||||
if (session.masterFd >= 0) {
|
||||
ioctl(session.masterFd, TIOCSWINSZ, &ws);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case meshtastic_RemoteShell_OpCode_PING: {
|
||||
uint32_t peerLastRxSeq = frame.ack_seq;
|
||||
if (frame.last_rx_seq > 0) {
|
||||
peerLastRxSeq = frame.last_rx_seq;
|
||||
}
|
||||
|
||||
const uint32_t nextMissingForPeer = peerLastRxSeq + 1;
|
||||
if (nextMissingForPeer > 0 && nextMissingForPeer < session.nextTxSeq) {
|
||||
resendFramesFrom(nextMissingForPeer);
|
||||
}
|
||||
|
||||
meshtastic_RemoteShell frame = {
|
||||
.op = meshtastic_RemoteShell_OpCode_PONG,
|
||||
.session_id = session.sessionId,
|
||||
.seq = session.nextTxSeq++,
|
||||
.ack_seq = session.lastAckedRxSeq,
|
||||
.cols = 0,
|
||||
.rows = 0,
|
||||
.flags = 0,
|
||||
.last_tx_seq = session.nextTxSeq > 0 ? session.nextTxSeq - 1 : 0,
|
||||
.last_rx_seq = session.lastAckedRxSeq,
|
||||
};
|
||||
frame.payload.size = 0;
|
||||
sendFrameToPeer(session.peer, frame, true);
|
||||
break;
|
||||
}
|
||||
case meshtastic_RemoteShell_OpCode_CLOSE:
|
||||
closeSession("peer_close", true);
|
||||
break;
|
||||
default:
|
||||
sendError("unsupported_op");
|
||||
break;
|
||||
}
|
||||
|
||||
// If the peer granted us the turn, flush pending shell output and hand the turn back.
|
||||
serviceTurn();
|
||||
|
||||
return ProcessMessage::STOP;
|
||||
}
|
||||
|
||||
int32_t DMShellModule::runOnce()
|
||||
{
|
||||
processPendingChildReap();
|
||||
|
||||
if (!session.active) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
reapChildIfExited();
|
||||
if (!session.active) {
|
||||
return 100;
|
||||
}
|
||||
|
||||
if (Throttle::isWithinTimespanMs(session.lastActivityMs, SESSION_IDLE_TIMEOUT_MS) == false) {
|
||||
closeSession("idle_timeout", true);
|
||||
return 100;
|
||||
}
|
||||
|
||||
if (RadioLibInterface::instance->packetsInTxQueue() > 1) {
|
||||
return 50;
|
||||
}
|
||||
|
||||
if (session.turnManaged) {
|
||||
if (session.hasToken) {
|
||||
// We hold the turn: flush output and hand it back.
|
||||
serviceTurn();
|
||||
} else if (ptyHasOutput() && !Throttle::isWithinTimespanMs(session.lastRtsMs, RTS_RETRY_MS)) {
|
||||
// Unsolicited shell output but no turn: ask the client to grant us one.
|
||||
sendRts();
|
||||
session.lastRtsMs = millis();
|
||||
}
|
||||
return 50;
|
||||
}
|
||||
|
||||
// Legacy free-send path (peer is not using turn-taking).
|
||||
uint8_t outBuf[MAX_MESSAGE_SIZE];
|
||||
while (session.masterFd >= 0) {
|
||||
const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf));
|
||||
if (bytesRead > 0) {
|
||||
LOG_WARN("DMShell: read %zd bytes from PTY", bytesRead);
|
||||
|
||||
meshtastic_RemoteShell frame = {
|
||||
.op = meshtastic_RemoteShell_OpCode_OUTPUT,
|
||||
.session_id = session.sessionId,
|
||||
.seq = session.nextTxSeq++,
|
||||
.ack_seq = session.lastAckedRxSeq,
|
||||
.cols = 0,
|
||||
.rows = 0,
|
||||
.flags = 0,
|
||||
};
|
||||
assert(bytesRead <= sizeof(frame.payload.bytes));
|
||||
memcpy(frame.payload.bytes, outBuf, bytesRead);
|
||||
frame.payload.size = bytesRead;
|
||||
sendFrameToPeer(session.peer, frame, true);
|
||||
|
||||
session.lastActivityMs = millis();
|
||||
// continue;
|
||||
// do we want to ack every data message, and only send the next on ack?
|
||||
// would require some retry logic. Maybe re-use the wantAck bit
|
||||
return 50;
|
||||
}
|
||||
|
||||
if (bytesRead == 0) {
|
||||
closeSession("pty_eof", true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_WARN("DMShell: PTY read error errno=%d", errno);
|
||||
closeSession("pty_read_error", true);
|
||||
break;
|
||||
}
|
||||
|
||||
return 100;
|
||||
}
|
||||
|
||||
bool DMShellModule::parseFrame(const meshtastic_MeshPacket &mp, meshtastic_RemoteShell &outFrame)
|
||||
{
|
||||
if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_RemoteShell_fields, &outFrame)) {
|
||||
LOG_INFO("Received a DMShell message");
|
||||
} else {
|
||||
LOG_ERROR("Error decoding DMShell message!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DMShellModule::isAuthorizedPacket(const meshtastic_MeshPacket &mp) const
|
||||
{
|
||||
if (mp.from == 0) {
|
||||
return !config.security.is_managed;
|
||||
}
|
||||
|
||||
const meshtastic_Channel *ch = &channels.getByIndex(mp.channel);
|
||||
if (strcasecmp(ch->settings.name, Channels::adminChannel) == 0) {
|
||||
return config.security.admin_channel_enabled;
|
||||
}
|
||||
|
||||
if (mp.pki_encrypted) {
|
||||
for (uint8_t i = 0; i < 3; ++i) {
|
||||
if (config.security.admin_key[i].size == 32 &&
|
||||
memcmp(mp.public_key.bytes, config.security.admin_key[i].bytes, 32) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const meshtastic_RemoteShell &frame)
|
||||
{
|
||||
if (session.active) {
|
||||
closeSession("preempted", false);
|
||||
}
|
||||
|
||||
int masterFd = -1;
|
||||
struct winsize ws = {};
|
||||
if (frame.rows > 0) {
|
||||
ws.ws_row = frame.rows;
|
||||
} else {
|
||||
ws.ws_row = PTY_ROWS_DEFAULT;
|
||||
}
|
||||
if (frame.cols > 0) {
|
||||
ws.ws_col = frame.cols;
|
||||
} else {
|
||||
ws.ws_col = PTY_COLS_DEFAULT;
|
||||
}
|
||||
const pid_t childPid = forkpty(&masterFd, nullptr, nullptr, &ws);
|
||||
if (childPid < 0) {
|
||||
LOG_ERROR("DMShell: forkpty failed errno=%d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (childPid == 0) {
|
||||
const char *shell = getenv("SHELL");
|
||||
if (!shell || !*shell) {
|
||||
shell = "/bin/sh";
|
||||
}
|
||||
execl(shell, shell, "-i", static_cast<char *>(nullptr));
|
||||
_exit(127);
|
||||
}
|
||||
|
||||
const int flags = fcntl(masterFd, F_GETFL, 0);
|
||||
if (flags >= 0) {
|
||||
fcntl(masterFd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
|
||||
session.active = true;
|
||||
session.sessionId = (frame.session_id != 0) ? frame.session_id : static_cast<uint32_t>(random(1, 0x7fffffff));
|
||||
session.peer = getFrom(&mp);
|
||||
session.channel = mp.channel;
|
||||
session.masterFd = masterFd;
|
||||
session.childPid = childPid;
|
||||
session.nextTxSeq = 1;
|
||||
session.lastAckedRxSeq = frame.seq;
|
||||
session.nextExpectedRxSeq = frame.seq + 1;
|
||||
session.highestSeenRxSeq = frame.seq;
|
||||
session.lastActivityMs = millis();
|
||||
session.turnManaged = true;
|
||||
session.hasToken = false;
|
||||
session.lastRtsMs = 0;
|
||||
|
||||
// Honor any GRANT the client put on OPEN (opts this session into turn-taking).
|
||||
applyTurnFlags(frame);
|
||||
|
||||
meshtastic_RemoteShell newFrame = {
|
||||
.op = meshtastic_RemoteShell_OpCode_OPEN_OK,
|
||||
.session_id = session.sessionId,
|
||||
.seq = session.nextTxSeq++,
|
||||
.ack_seq = frame.seq,
|
||||
.cols = ws.ws_col,
|
||||
.rows = ws.ws_row,
|
||||
.flags = session.turnManaged ? TURN_FLAG_GRANT : 0u,
|
||||
};
|
||||
newFrame.payload.size = 0;
|
||||
sendFrameToPeer(session.peer, newFrame, true);
|
||||
if (session.turnManaged) {
|
||||
// OPEN_OK handed the turn back to the client; it is now the idle-owner.
|
||||
session.hasToken = false;
|
||||
}
|
||||
|
||||
LOG_INFO("DMShell: opened session=0x%x peer=0x%x pid=%d", session.sessionId, session.peer, session.childPid);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DMShellModule::writeSessionInput(const meshtastic_RemoteShell &frame)
|
||||
{
|
||||
if (session.masterFd < 0) {
|
||||
return false;
|
||||
}
|
||||
if (frame.payload.size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const ssize_t bytesWritten = write(session.masterFd, frame.payload.bytes, frame.payload.size);
|
||||
return bytesWritten >= 0;
|
||||
}
|
||||
|
||||
void DMShellModule::closeSession(const char *reason, bool notifyPeer)
|
||||
{
|
||||
if (!session.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (notifyPeer) {
|
||||
const size_t reasonLen = strnlen(reason, 256);
|
||||
meshtastic_RemoteShell frame = {
|
||||
.op = meshtastic_RemoteShell_OpCode_CLOSED,
|
||||
.session_id = session.sessionId,
|
||||
.seq = session.nextTxSeq++,
|
||||
.ack_seq = session.lastAckedRxSeq,
|
||||
.cols = 0,
|
||||
.rows = 0,
|
||||
.flags = 0,
|
||||
};
|
||||
assert(reasonLen <= sizeof(frame.payload.bytes));
|
||||
memcpy(frame.payload.bytes, reason, reasonLen);
|
||||
frame.payload.size = reasonLen;
|
||||
sendFrameToPeer(session.peer, frame, true);
|
||||
}
|
||||
|
||||
if (session.masterFd >= 0) {
|
||||
close(session.masterFd);
|
||||
session.masterFd = -1;
|
||||
}
|
||||
|
||||
if (session.childPid > 0) {
|
||||
// Run this to avoid forgetting a child
|
||||
processPendingChildReap();
|
||||
|
||||
if (kill(session.childPid, SIGTERM) < 0 && errno != ESRCH) {
|
||||
LOG_WARN("DMShell: failed to send SIGTERM to pid=%d errno=%d", session.childPid, errno);
|
||||
}
|
||||
|
||||
pendingChildPid = session.childPid;
|
||||
session.childPid = -1;
|
||||
}
|
||||
|
||||
LOG_INFO("DMShell: closed session=0x%x reason=%s", session.sessionId, reason);
|
||||
session = DMShellSession{};
|
||||
}
|
||||
|
||||
void DMShellModule::reapChildIfExited()
|
||||
{
|
||||
if (!session.active || session.childPid <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
const pid_t result = waitpid(session.childPid, &status, WNOHANG);
|
||||
if (result == session.childPid) {
|
||||
closeSession("shell_exited", true);
|
||||
}
|
||||
}
|
||||
|
||||
void DMShellModule::processPendingChildReap()
|
||||
{
|
||||
if (pendingChildPid <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int status = 0;
|
||||
const pid_t result = waitpid(pendingChildPid, &status, WNOHANG);
|
||||
|
||||
if (result == pendingChildPid || (result < 0 && errno == ECHILD)) {
|
||||
pendingChildPid = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result < 0) {
|
||||
LOG_WARN("DMShell: waitpid failed for pid=%d errno=%d", pendingChildPid, errno);
|
||||
pendingChildPid = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingChildPid > 0) {
|
||||
if (kill(pendingChildPid, SIGKILL) < 0 && errno != ESRCH) {
|
||||
LOG_WARN("DMShell: failed to send SIGKILL to pid=%d errno=%d", pendingChildPid, errno);
|
||||
}
|
||||
pendingChildPid = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void DMShellModule::rememberSentFrame(meshtastic_RemoteShell frame)
|
||||
{
|
||||
if (frame.seq == 0 || frame.op == meshtastic_RemoteShell_OpCode_ACK) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &entry = session.txHistory[session.txHistoryNext];
|
||||
entry.valid = true;
|
||||
entry.op = frame.op;
|
||||
entry.sessionId = frame.session_id;
|
||||
entry.seq = frame.seq;
|
||||
entry.ackSeq = frame.ack_seq;
|
||||
entry.cols = frame.cols;
|
||||
entry.rows = frame.rows;
|
||||
entry.flags = frame.flags;
|
||||
entry.payloadLen = frame.payload.size;
|
||||
if (frame.payload.size > 0) {
|
||||
memcpy(entry.payload, frame.payload.bytes, frame.payload.size);
|
||||
}
|
||||
|
||||
session.txHistoryNext = (session.txHistoryNext + 1) % session.txHistory.size();
|
||||
}
|
||||
|
||||
void DMShellModule::resendFramesFrom(uint32_t startSeq)
|
||||
{
|
||||
if (startSeq == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
DMShellSession::SentFrame *match = nullptr;
|
||||
for (auto &entry : session.txHistory) {
|
||||
if (!entry.valid || entry.seq != startSeq) {
|
||||
continue;
|
||||
}
|
||||
match = &entry;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
LOG_WARN("DMShell: replay request for seq=%u not found in history", startSeq);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("DMShell: replaying frame seq=%u op=%d", match->seq, match->op);
|
||||
meshtastic_RemoteShell frame = {
|
||||
.op = match->op,
|
||||
.session_id = match->sessionId,
|
||||
.seq = match->seq,
|
||||
.ack_seq = match->ackSeq,
|
||||
.cols = match->cols,
|
||||
.rows = match->rows,
|
||||
.flags = match->flags,
|
||||
};
|
||||
assert(match->payloadLen <= sizeof(frame.payload.bytes));
|
||||
memcpy(frame.payload.bytes, match->payload, match->payloadLen);
|
||||
frame.payload.size = match->payloadLen;
|
||||
sendFrameToPeer(session.peer, frame, false);
|
||||
}
|
||||
|
||||
void DMShellModule::sendAck(uint32_t replayFromSeq)
|
||||
{
|
||||
if (replayFromSeq > 0) {
|
||||
LOG_WARN("DMShell: requesting replay from seq=%u", replayFromSeq);
|
||||
}
|
||||
meshtastic_RemoteShell frame = {
|
||||
.op = meshtastic_RemoteShell_OpCode_ACK,
|
||||
.session_id = session.sessionId,
|
||||
.seq = 0,
|
||||
.ack_seq = session.lastAckedRxSeq,
|
||||
.cols = 0,
|
||||
.rows = 0,
|
||||
.flags = 0,
|
||||
.last_rx_seq = replayFromSeq - 1,
|
||||
};
|
||||
frame.payload.size = 0;
|
||||
sendFrameToPeer(session.peer, frame, false);
|
||||
}
|
||||
|
||||
bool DMShellModule::shouldProcessIncomingFrame(const meshtastic_RemoteShell &frame)
|
||||
{
|
||||
if (frame.seq == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (frame.seq < session.nextExpectedRxSeq) {
|
||||
if (session.highestSeenRxSeq >= session.nextExpectedRxSeq) {
|
||||
sendAck(session.nextExpectedRxSeq);
|
||||
} else {
|
||||
sendAck();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (frame.seq > session.nextExpectedRxSeq) {
|
||||
if (frame.seq > session.highestSeenRxSeq) {
|
||||
session.highestSeenRxSeq = frame.seq;
|
||||
}
|
||||
sendAck(session.nextExpectedRxSeq);
|
||||
return false;
|
||||
}
|
||||
|
||||
session.lastAckedRxSeq = frame.seq;
|
||||
session.nextExpectedRxSeq = frame.seq + 1;
|
||||
if (frame.seq > session.highestSeenRxSeq) {
|
||||
session.highestSeenRxSeq = frame.seq;
|
||||
}
|
||||
if (session.highestSeenRxSeq >= session.nextExpectedRxSeq) {
|
||||
sendAck(session.nextExpectedRxSeq);
|
||||
} else {
|
||||
session.highestSeenRxSeq = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DMShellModule::sendFrameToPeer(NodeNum peer, meshtastic_RemoteShell frame, bool remember)
|
||||
{
|
||||
meshtastic_MeshPacket *packet = allocDataPacket();
|
||||
if (!packet) {
|
||||
return;
|
||||
}
|
||||
LOG_WARN("DMShell: building packet op=%u session=0x%x seq=%u payloadLen=%zu", frame.op, frame.session_id, frame.seq,
|
||||
frame.payload.size);
|
||||
const size_t encoded = pb_encode_to_bytes(packet->decoded.payload.bytes, sizeof(packet->decoded.payload.bytes),
|
||||
meshtastic_RemoteShell_fields, &frame);
|
||||
if (encoded == 0) {
|
||||
return;
|
||||
}
|
||||
packet->decoded.payload.size = encoded;
|
||||
|
||||
if (remember) {
|
||||
rememberSentFrame(frame);
|
||||
}
|
||||
|
||||
packet->to = peer;
|
||||
packet->hop_limit = 0;
|
||||
packet->hop_start = 0;
|
||||
packet->channel = 0;
|
||||
packet->want_ack = false;
|
||||
packet->pki_encrypted = true;
|
||||
packet->priority = meshtastic_MeshPacket_Priority_RELIABLE;
|
||||
service->sendToMesh(packet);
|
||||
}
|
||||
|
||||
void DMShellModule::applyTurnFlags(const meshtastic_RemoteShell &frame)
|
||||
{
|
||||
if (frame.flags & (TURN_FLAG_GRANT | TURN_FLAG_MORE | TURN_FLAG_RTS)) {
|
||||
session.turnManaged = true; // peer speaks turn-taking; enable gating for this session
|
||||
}
|
||||
if (frame.flags & TURN_FLAG_GRANT) {
|
||||
if (!session.hasToken) {
|
||||
// Fresh turn: start the linger window and reset the per-turn budget.
|
||||
session.turnDeadlineMs = millis() + TURN_LINGER_MS;
|
||||
session.turnFramesSent = 0;
|
||||
}
|
||||
session.hasToken = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool DMShellModule::ptyHasOutput()
|
||||
{
|
||||
if (session.masterFd < 0) {
|
||||
return false;
|
||||
}
|
||||
struct pollfd pfd = {};
|
||||
pfd.fd = session.masterFd;
|
||||
pfd.events = POLLIN;
|
||||
return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN);
|
||||
}
|
||||
|
||||
void DMShellModule::sendOutputFrame(const uint8_t *data, size_t len, uint32_t extraFlags)
|
||||
{
|
||||
meshtastic_RemoteShell frame = {
|
||||
.op = meshtastic_RemoteShell_OpCode_OUTPUT,
|
||||
.session_id = session.sessionId,
|
||||
.seq = session.nextTxSeq++,
|
||||
.ack_seq = session.lastAckedRxSeq,
|
||||
.cols = 0,
|
||||
.rows = 0,
|
||||
.flags = extraFlags,
|
||||
};
|
||||
assert(len <= sizeof(frame.payload.bytes));
|
||||
memcpy(frame.payload.bytes, data, len);
|
||||
frame.payload.size = len;
|
||||
sendFrameToPeer(session.peer, frame, true);
|
||||
}
|
||||
|
||||
void DMShellModule::sendTurnGrant(bool more)
|
||||
{
|
||||
meshtastic_RemoteShell frame = {
|
||||
.op = meshtastic_RemoteShell_OpCode_ACK,
|
||||
.session_id = session.sessionId,
|
||||
.seq = 0,
|
||||
.ack_seq = session.lastAckedRxSeq,
|
||||
.cols = 0,
|
||||
.rows = 0,
|
||||
.flags = TURN_FLAG_GRANT | (more ? TURN_FLAG_MORE : 0u),
|
||||
.last_rx_seq = 0,
|
||||
};
|
||||
frame.payload.size = 0;
|
||||
sendFrameToPeer(session.peer, frame, false);
|
||||
}
|
||||
|
||||
void DMShellModule::sendRts()
|
||||
{
|
||||
meshtastic_RemoteShell frame = {
|
||||
.op = meshtastic_RemoteShell_OpCode_ACK,
|
||||
.session_id = session.sessionId,
|
||||
.seq = 0,
|
||||
.ack_seq = session.lastAckedRxSeq,
|
||||
.cols = 0,
|
||||
.rows = 0,
|
||||
.flags = TURN_FLAG_RTS,
|
||||
.last_rx_seq = 0,
|
||||
};
|
||||
frame.payload.size = 0;
|
||||
sendFrameToPeer(session.peer, frame, false);
|
||||
}
|
||||
|
||||
// Called (every tick) while we hold the turn. Drains available shell output and sends it
|
||||
// immediately, then decides whether to keep the turn (linger, to catch output that is about to
|
||||
// appear) or hand it back. The turn is yielded once the per-turn budget is hit (so the client can
|
||||
// interject, e.g. Ctrl-C) or once the PTY has been idle past the linger window. Output frames go
|
||||
// out as soon as they are read (no extra delay); the grant is a trailing ACK.
|
||||
void DMShellModule::serviceTurn()
|
||||
{
|
||||
if (!session.active || !session.turnManaged || !session.hasToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t buf[MAX_MESSAGE_SIZE];
|
||||
bool eof = false;
|
||||
bool readError = false;
|
||||
|
||||
// Drain whatever is available right now, up to the remaining per-turn budget.
|
||||
while (session.turnFramesSent < TURN_BUDGET_FRAMES && session.masterFd >= 0) {
|
||||
const ssize_t n = read(session.masterFd, buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
sendOutputFrame(buf, (size_t)n, 0u);
|
||||
session.turnFramesSent++;
|
||||
session.lastActivityMs = millis();
|
||||
session.turnDeadlineMs = millis() + TURN_LINGER_MS; // extend the linger while output flows
|
||||
} else if (n == 0) {
|
||||
eof = true;
|
||||
break;
|
||||
} else {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
readError = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (eof || readError) {
|
||||
session.hasToken = false;
|
||||
sendTurnGrant(false);
|
||||
if (eof) {
|
||||
closeSession("pty_eof", true);
|
||||
} else {
|
||||
LOG_WARN("DMShell: PTY read error errno=%d", errno);
|
||||
closeSession("pty_read_error", true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const bool morePending = ptyHasOutput();
|
||||
|
||||
if (session.turnFramesSent >= TURN_BUDGET_FRAMES) {
|
||||
// Hit the per-turn budget: yield so the client gets a chance to interject (e.g. Ctrl-C).
|
||||
session.hasToken = false;
|
||||
sendTurnGrant(morePending);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!morePending && (int32_t)(millis() - session.turnDeadlineMs) >= 0) {
|
||||
// PTY has been idle past the linger window: hand the turn back.
|
||||
session.hasToken = false;
|
||||
sendTurnGrant(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise keep holding the turn: there is more to drain next pass, or we are lingering for
|
||||
// output that may be about to appear. runOnce re-enters serviceTurn on the next tick.
|
||||
}
|
||||
|
||||
void DMShellModule::sendError(const char *message, NodeNum peer)
|
||||
{
|
||||
const size_t len = strnlen(message, MAX_MESSAGE_SIZE);
|
||||
meshtastic_RemoteShell frame = {
|
||||
.op = meshtastic_RemoteShell_OpCode_ERROR,
|
||||
.session_id = session.sessionId,
|
||||
.seq = session.nextTxSeq++,
|
||||
.ack_seq = session.lastAckedRxSeq,
|
||||
.cols = 0,
|
||||
.rows = 0,
|
||||
.flags = 0,
|
||||
};
|
||||
if (message && len > 0) {
|
||||
assert(len <= sizeof(frame.payload.bytes));
|
||||
memcpy(frame.payload.bytes, message, len);
|
||||
frame.payload.size = len;
|
||||
}
|
||||
if (peer == 0) {
|
||||
peer = session.peer;
|
||||
}
|
||||
sendFrameToPeer(peer, frame, true);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include "MeshModule.h"
|
||||
#include "Router.h"
|
||||
#include "SinglePortModule.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "configuration.h"
|
||||
#include "mesh/generated/meshtastic/mesh.pb.h"
|
||||
#include <Arduino.h>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
|
||||
#if defined(ARCH_PORTDUINO)
|
||||
|
||||
struct DMShellSession {
|
||||
bool active = false;
|
||||
uint32_t sessionId = 0;
|
||||
NodeNum peer = 0;
|
||||
uint8_t channel = 0;
|
||||
int masterFd = -1;
|
||||
int childPid = -1;
|
||||
uint32_t nextTxSeq = 1;
|
||||
uint32_t lastAckedRxSeq = 0;
|
||||
uint32_t nextExpectedRxSeq = 1;
|
||||
uint32_t highestSeenRxSeq = 0;
|
||||
uint32_t lastActivityMs = 0;
|
||||
// --- Half-duplex turn-taking ("talking stick") state ---
|
||||
bool turnManaged = false; // becomes true once the peer uses turn-taking flags; enables gating
|
||||
bool hasToken = false; // do we currently hold the right to transmit?
|
||||
uint32_t lastRtsMs = 0; // last time we asked the client for a turn (rate-limit)
|
||||
uint32_t turnDeadlineMs = 0; // while holding the turn, keep it until this time (extended on output)
|
||||
uint32_t turnFramesSent = 0; // output frames sent in the current held turn (vs TURN_BUDGET_FRAMES)
|
||||
struct SentFrame {
|
||||
bool valid = false;
|
||||
meshtastic_RemoteShell_OpCode op = meshtastic_RemoteShell_OpCode_ERROR;
|
||||
uint32_t sessionId = 0;
|
||||
uint32_t seq = 0;
|
||||
uint32_t ackSeq = 0;
|
||||
uint32_t cols = 0;
|
||||
uint32_t rows = 0;
|
||||
uint32_t flags = 0;
|
||||
uint8_t payload[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0};
|
||||
size_t payloadLen = 0;
|
||||
};
|
||||
std::array<SentFrame, 50> txHistory = {};
|
||||
size_t txHistoryNext = 0;
|
||||
};
|
||||
|
||||
class DMShellModule : private concurrency::OSThread, public SinglePortModule
|
||||
{
|
||||
|
||||
public:
|
||||
DMShellModule();
|
||||
|
||||
protected:
|
||||
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
static constexpr uint32_t SESSION_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
|
||||
|
||||
DMShellSession session;
|
||||
pid_t pendingChildPid = -1;
|
||||
|
||||
bool parseFrame(const meshtastic_MeshPacket &mp, meshtastic_RemoteShell &outFrame);
|
||||
bool isAuthorizedPacket(const meshtastic_MeshPacket &mp) const;
|
||||
bool openSession(const meshtastic_MeshPacket &mp, const meshtastic_RemoteShell &frame);
|
||||
bool shouldProcessIncomingFrame(const meshtastic_RemoteShell &frame);
|
||||
bool writeSessionInput(const meshtastic_RemoteShell &frame);
|
||||
void closeSession(const char *reason, bool notifyPeer);
|
||||
void reapChildIfExited();
|
||||
void processPendingChildReap();
|
||||
|
||||
void rememberSentFrame(meshtastic_RemoteShell frame);
|
||||
void resendFramesFrom(uint32_t startSeq);
|
||||
void sendAck(uint32_t replayFromSeq = 0);
|
||||
void sendFrameToPeer(NodeNum peer, meshtastic_RemoteShell frame, bool remember = true);
|
||||
void sendError(const char *message, NodeNum peer = 0);
|
||||
|
||||
// --- Turn-taking helpers ---
|
||||
void applyTurnFlags(const meshtastic_RemoteShell &frame);
|
||||
bool ptyHasOutput();
|
||||
void serviceTurn();
|
||||
void sendOutputFrame(const uint8_t *data, size_t len, uint32_t extraFlags);
|
||||
void sendTurnGrant(bool more);
|
||||
void sendRts();
|
||||
};
|
||||
|
||||
extern DMShellModule *dmShellModule;
|
||||
|
||||
#endif
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "modules/WaypointModule.h"
|
||||
#endif
|
||||
#if ARCH_PORTDUINO
|
||||
#include "modules/DMShell.h"
|
||||
#include "modules/Telemetry/HostMetrics.h"
|
||||
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
|
||||
#include "modules/StoreForwardModule.h"
|
||||
@@ -195,6 +196,7 @@ void setupModules()
|
||||
#endif
|
||||
#if ARCH_PORTDUINO
|
||||
new HostMetricsModule();
|
||||
dmShellModule = new DMShellModule();
|
||||
#endif
|
||||
#if HAS_TELEMETRY
|
||||
new DeviceTelemetryModule();
|
||||
|
||||
@@ -332,8 +332,6 @@ meshtastic_MeshPacket *PositionModule::allocAtakPli()
|
||||
takPacket.geo_src = meshtastic_GeoPointSource_GeoPointSource_GPS;
|
||||
takPacket.alt_src = meshtastic_GeoPointSource_GeoPointSource_GPS;
|
||||
}
|
||||
takPacket.which_payload_variant = meshtastic_TAKPacketV2_pli_tag;
|
||||
takPacket.payload_variant.pli = true;
|
||||
|
||||
// Callsign - stored as plain string (no compression, apps handle that)
|
||||
strncpy(takPacket.callsign, owner.long_name, sizeof(takPacket.callsign) - 1);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#endif
|
||||
#include "BMM150Sensor.h"
|
||||
#include "BMX160Sensor.h"
|
||||
#include "ICM42607PSensor.h"
|
||||
#include "ICM20948Sensor.h"
|
||||
#include "LIS3DHSensor.h"
|
||||
#include "LSM6DS3Sensor.h"
|
||||
@@ -111,6 +112,9 @@ class AccelerometerThread : public concurrency::OSThread
|
||||
case ScanI2C::DeviceType::ICM20948:
|
||||
sensor = new ICM20948Sensor(device);
|
||||
break;
|
||||
case ScanI2C::DeviceType::ICM42607P:
|
||||
sensor = new ICM42607PSensor(device);
|
||||
break;
|
||||
case ScanI2C::DeviceType::BMM150:
|
||||
sensor = new BMM150Sensor(device);
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#include "ICM42607PSensor.h"
|
||||
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && __has_include(<ICM42670P.h>)
|
||||
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
#include <ICM42670P.h>
|
||||
|
||||
static constexpr uint16_t ICM42607P_ACCEL_ODR_HZ = 50;
|
||||
static constexpr uint16_t ICM42607P_ACCEL_FSR_G = 2;
|
||||
static constexpr float ICM42607P_COUNTS_PER_G = 32768.0f / ICM42607P_ACCEL_FSR_G;
|
||||
|
||||
#ifdef ICM_42607P_INT_PIN
|
||||
volatile static bool ICM42607P_IRQ = false;
|
||||
|
||||
void ICM42607PSetInterrupt()
|
||||
{
|
||||
ICM42607P_IRQ = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
ICM42607PSensor::ICM42607PSensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice)
|
||||
{
|
||||
wire = ScanI2CTwoWire::fetchI2CBus(foundDevice.address);
|
||||
}
|
||||
|
||||
ICM42607PSensor::~ICM42607PSensor() = default;
|
||||
|
||||
bool ICM42607PSensor::init()
|
||||
{
|
||||
bool addressLsb = deviceAddress() == ICM42607P_ADDR_ALT;
|
||||
|
||||
LOG_DEBUG("ICM-42607-P begin on addr 0x%02X (port=%d)", deviceAddress(), devicePort());
|
||||
sensor.reset();
|
||||
auto newSensor = std::make_unique<ICM42670>(*wire, addressLsb);
|
||||
|
||||
int status = newSensor->begin();
|
||||
// ICM42670P library returns -3 for ICM42607P because WHO_AM_I differs; the register map is compatible.
|
||||
if (status != 0 && status != -3) {
|
||||
LOG_DEBUG("ICM-42607-P init error %d", status);
|
||||
return false;
|
||||
}
|
||||
|
||||
status = newSensor->startAccel(ICM42607P_ACCEL_ODR_HZ, ICM42607P_ACCEL_FSR_G);
|
||||
if (status != 0) {
|
||||
LOG_DEBUG("ICM-42607-P accel start error %d", status);
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ICM_42607P_INT_PIN
|
||||
ICM42607P_IRQ = false;
|
||||
status = newSensor->startWakeOnMotion(ICM_42607P_INT_PIN, ICM42607PSetInterrupt);
|
||||
if (status != 0) {
|
||||
LOG_DEBUG("ICM-42607-P wake-on-motion start error %d", status);
|
||||
return false;
|
||||
}
|
||||
LOG_DEBUG("ICM-42607-P wake-on-motion interrupt ok pin=%d", ICM_42607P_INT_PIN);
|
||||
#endif
|
||||
|
||||
sensor = std::move(newSensor);
|
||||
LOG_DEBUG("ICM-42607-P init ok");
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t ICM42607PSensor::runOnce()
|
||||
{
|
||||
#ifdef ICM_42607P_INT_PIN
|
||||
if (ICM42607P_IRQ) {
|
||||
ICM42607P_IRQ = false;
|
||||
LOG_DEBUG("ICM-42607-P motion interrupt");
|
||||
wakeScreen();
|
||||
}
|
||||
return MOTION_SENSOR_CHECK_INTERVAL_MS;
|
||||
#else
|
||||
int16_t x = 0;
|
||||
int16_t y = 0;
|
||||
int16_t z = 0;
|
||||
inv_imu_sensor_event_t event = {};
|
||||
|
||||
if (sensor == nullptr || sensor->getDataFromRegisters(event) != 0) {
|
||||
return MOTION_SENSOR_CHECK_INTERVAL_MS;
|
||||
}
|
||||
|
||||
// getDataFromRegisters() fills accel[] but does not set sensor_mask in this library version.
|
||||
if (event.accel[0] == 0 && event.accel[1] == 0 && event.accel[2] == 0) {
|
||||
return MOTION_SENSOR_CHECK_INTERVAL_MS;
|
||||
}
|
||||
|
||||
x = event.accel[0];
|
||||
y = event.accel[1];
|
||||
z = event.accel[2];
|
||||
// LOG_DEBUG("ICM-42607-P accel read x=%.3fg y=%.3fg z=%.3fg", (float)x / ICM42607P_COUNTS_PER_G,
|
||||
// (float)y / ICM42607P_COUNTS_PER_G, (float)z / ICM42607P_COUNTS_PER_G);
|
||||
|
||||
return MOTION_SENSOR_CHECK_INTERVAL_MS;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#ifndef _ICM42607P_SENSOR_H_
|
||||
#define _ICM42607P_SENSOR_H_
|
||||
|
||||
#include "MotionSensor.h"
|
||||
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && __has_include(<ICM42670P.h>)
|
||||
|
||||
#include <memory>
|
||||
|
||||
class ICM42670;
|
||||
|
||||
class ICM42607PSensor : public MotionSensor
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<ICM42670> sensor;
|
||||
TwoWire *wire = nullptr;
|
||||
|
||||
public:
|
||||
explicit ICM42607PSensor(ScanI2C::FoundDevice foundDevice);
|
||||
~ICM42607PSensor() override;
|
||||
virtual bool init() override;
|
||||
virtual int32_t runOnce() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "MMC5983MASensor.h"
|
||||
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && __has_include(<SparkFun_MMC5983MA_Arduino_Library.h>)
|
||||
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
extern graphics::Screen *screen;
|
||||
#endif
|
||||
|
||||
static constexpr float MMC5983MA_ZERO_FIELD = 131072.0f;
|
||||
static constexpr float MMC5983MA_COUNTS_PER_GAUSS = 16384.0f;
|
||||
static constexpr uint16_t MMC5983MA_CONTINUOUS_FREQUENCY_HZ = 10;
|
||||
static constexpr float MMC5983MA_HEADING_OFFSET_DEG = 180.0f;
|
||||
|
||||
MMC5983MASensor::MMC5983MASensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {}
|
||||
|
||||
bool MMC5983MASensor::init()
|
||||
{
|
||||
LOG_DEBUG("MMC5983MA begin on addr 0x%02X (port=%d)", device.address.address, device.address.port);
|
||||
TwoWire *wire = ScanI2CTwoWire::fetchI2CBus(device.address);
|
||||
|
||||
if (!sensor.begin(*wire)) {
|
||||
LOG_DEBUG("MMC5983MA init error");
|
||||
return false;
|
||||
}
|
||||
|
||||
sensor.softReset();
|
||||
sensor.setFilterBandwidth(100);
|
||||
sensor.performSetOperation();
|
||||
sensor.enableAutomaticSetReset();
|
||||
continuousMode = sensor.setContinuousModeFrequency(MMC5983MA_CONTINUOUS_FREQUENCY_HZ);
|
||||
continuousMode &= sensor.enableContinuousMode();
|
||||
|
||||
if (!continuousMode) {
|
||||
LOG_DEBUG("MMC5983MA continuous mode failed, using single-shot reads");
|
||||
sensor.disableContinuousMode();
|
||||
}
|
||||
|
||||
loadMagnetometerCalibration(compassCalibrationFileName, highestX, lowestX, highestY, lowestY, highestZ, lowestZ);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMC5983MASensor::readMagnetometer(float &xGauss, float &yGauss, float &zGauss)
|
||||
{
|
||||
uint32_t rawX = 0;
|
||||
uint32_t rawY = 0;
|
||||
uint32_t rawZ = 0;
|
||||
|
||||
if (!(continuousMode ? sensor.readFieldsXYZ(&rawX, &rawY, &rawZ) : sensor.getMeasurementXYZ(&rawX, &rawY, &rawZ))) {
|
||||
LOG_DEBUG("MMC5983MA read failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
xGauss = ((float)rawX - MMC5983MA_ZERO_FIELD) / MMC5983MA_COUNTS_PER_GAUSS;
|
||||
yGauss = ((float)rawY - MMC5983MA_ZERO_FIELD) / MMC5983MA_COUNTS_PER_GAUSS;
|
||||
zGauss = ((float)rawZ - MMC5983MA_ZERO_FIELD) / MMC5983MA_COUNTS_PER_GAUSS;
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t MMC5983MASensor::runOnce()
|
||||
{
|
||||
float magX = 0, magY = 0, magZ = 0;
|
||||
if (!readMagnetometer(magX, magY, magZ)) {
|
||||
return MOTION_SENSOR_CHECK_INTERVAL_MS;
|
||||
}
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
if (doCalibration) {
|
||||
beginCalibrationDisplay(showingScreen);
|
||||
updateCalibrationExtrema(magX, magY, magZ, highestX, lowestX, highestY, lowestY, highestZ, lowestZ);
|
||||
finishCalibrationIfExpired(showingScreen, compassCalibrationFileName, highestX, lowestX, highestY, lowestY, highestZ,
|
||||
lowestZ);
|
||||
}
|
||||
#endif
|
||||
|
||||
magX -= (highestX + lowestX) / 2;
|
||||
magY -= (highestY + lowestY) / 2;
|
||||
magZ -= (highestZ + lowestZ) / 2;
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN
|
||||
float heading = atan2f(magY, magX) * RAD_TO_DEG + MMC5983MA_HEADING_OFFSET_DEG;
|
||||
if (heading < 0.0f) {
|
||||
heading += 360.0f;
|
||||
} else if (heading >= 360.0f) {
|
||||
heading -= 360.0f;
|
||||
}
|
||||
|
||||
heading = applyCompassOrientation(heading);
|
||||
if (screen) {
|
||||
screen->setHeading(heading);
|
||||
}
|
||||
#endif
|
||||
|
||||
return MOTION_SENSOR_CHECK_INTERVAL_MS;
|
||||
}
|
||||
|
||||
void MMC5983MASensor::calibrate(uint16_t forSeconds)
|
||||
{
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
float xGauss = 0.0f;
|
||||
float yGauss = 0.0f;
|
||||
float zGauss = 0.0f;
|
||||
|
||||
LOG_DEBUG("MMC5983MA calibration started for %is", forSeconds);
|
||||
if (readMagnetometer(xGauss, yGauss, zGauss)) {
|
||||
seedCalibrationExtrema(xGauss, yGauss, zGauss, highestX, lowestX, highestY, lowestY, highestZ, lowestZ);
|
||||
} else {
|
||||
seedCalibrationExtrema(0.0f, 0.0f, 0.0f, highestX, lowestX, highestY, lowestY, highestZ, lowestZ);
|
||||
}
|
||||
startCalibrationWindow(forSeconds);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#ifndef _MMC5983MA_SENSOR_H_
|
||||
#define _MMC5983MA_SENSOR_H_
|
||||
|
||||
#include "MotionSensor.h"
|
||||
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && __has_include(<SparkFun_MMC5983MA_Arduino_Library.h>)
|
||||
|
||||
#include <SparkFun_MMC5983MA_Arduino_Library.h>
|
||||
|
||||
class MMC5983MASensor : public MotionSensor
|
||||
{
|
||||
private:
|
||||
SFE_MMC5983MA sensor;
|
||||
bool continuousMode = false;
|
||||
bool showingScreen = false;
|
||||
static constexpr const char *compassCalibrationFileName = "/prefs/compass_mmc5983ma.dat";
|
||||
float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
|
||||
|
||||
bool readMagnetometer(float &xGauss, float &yGauss, float &zGauss);
|
||||
|
||||
public:
|
||||
explicit MMC5983MASensor(ScanI2C::FoundDevice foundDevice);
|
||||
virtual bool init() override;
|
||||
virtual int32_t runOnce() override;
|
||||
virtual void calibrate(uint16_t forSeconds) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
#ifndef _MAGNETOMETER_THREAD_H_
|
||||
#define _MAGNETOMETER_THREAD_H_
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_MAGNETOMETER
|
||||
|
||||
#include "../concurrency/OSThread.h"
|
||||
#include "MMC5983MASensor.h"
|
||||
#include "MotionSensor.h"
|
||||
|
||||
extern ScanI2C::DeviceAddress magnetometer_found;
|
||||
|
||||
class MagnetometerThread : public concurrency::OSThread
|
||||
{
|
||||
private:
|
||||
MotionSensor *sensor = nullptr;
|
||||
ScanI2C::FoundDevice device;
|
||||
bool isInitialised = false;
|
||||
|
||||
public:
|
||||
explicit MagnetometerThread(ScanI2C::FoundDevice foundDevice) : OSThread("Magnetometer")
|
||||
{
|
||||
device = foundDevice;
|
||||
init();
|
||||
}
|
||||
|
||||
explicit MagnetometerThread(ScanI2C::DeviceType type) : MagnetometerThread(ScanI2C::FoundDevice{type, magnetometer_found}) {}
|
||||
|
||||
void start()
|
||||
{
|
||||
init();
|
||||
setIntervalFromNow(0);
|
||||
};
|
||||
|
||||
void calibrate(uint16_t forSeconds)
|
||||
{
|
||||
if (sensor) {
|
||||
sensor->calibrate(forSeconds);
|
||||
setIntervalFromNow(0);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
int32_t runOnce() override
|
||||
{
|
||||
canSleep = true;
|
||||
|
||||
if (isInitialised) {
|
||||
return sensor->runOnce();
|
||||
}
|
||||
|
||||
return MOTION_SENSOR_CHECK_INTERVAL_MS;
|
||||
}
|
||||
|
||||
private:
|
||||
void init()
|
||||
{
|
||||
if (isInitialised) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (device.address.port == ScanI2C::I2CPort::NO_I2C || device.address.address == 0 || device.type == ScanI2C::NONE) {
|
||||
LOG_DEBUG("MagnetometerThread Disable due to no sensors found");
|
||||
disable();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (device.type) {
|
||||
case ScanI2C::DeviceType::MMC5983MA:
|
||||
sensor = new MMC5983MASensor(device);
|
||||
break;
|
||||
default:
|
||||
disable();
|
||||
return;
|
||||
}
|
||||
|
||||
isInitialised = sensor->init();
|
||||
if (!isInitialised) {
|
||||
clean();
|
||||
}
|
||||
LOG_DEBUG("MagnetometerThread::init %s", isInitialised ? "ok" : "failed");
|
||||
}
|
||||
|
||||
MagnetometerThread(const MagnetometerThread &other) : OSThread::OSThread("Magnetometer") { this->copy(other); }
|
||||
|
||||
virtual ~MagnetometerThread() { clean(); }
|
||||
|
||||
MagnetometerThread &operator=(const MagnetometerThread &other)
|
||||
{
|
||||
this->copy(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void copy(const MagnetometerThread &other)
|
||||
{
|
||||
if (this != &other) {
|
||||
clean();
|
||||
this->device = ScanI2C::FoundDevice(other.device.type,
|
||||
ScanI2C::DeviceAddress(other.device.address.port, other.device.address.address));
|
||||
}
|
||||
}
|
||||
|
||||
void clean()
|
||||
{
|
||||
isInitialised = false;
|
||||
delete sensor;
|
||||
sensor = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -187,11 +187,9 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
||||
LOG_INFO("BLE onConfigStart");
|
||||
|
||||
// Prefer high throughput during config/setup, at the cost of high power consumption (for a few seconds)
|
||||
if (bleServer && isConnected()) {
|
||||
uint16_t conn_handle = nimbleBluetoothConnHandle.load();
|
||||
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
requestHighThroughputConnection(conn_handle);
|
||||
}
|
||||
uint16_t conn_handle = nimbleBluetoothConnHandle.load();
|
||||
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
requestHighThroughputConnection(conn_handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,11 +198,9 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
||||
LOG_INFO("BLE onConfigComplete");
|
||||
|
||||
// Switch to lower power consumption BLE connection params for steady-state use after config/setup is complete
|
||||
if (bleServer && isConnected()) {
|
||||
uint16_t conn_handle = nimbleBluetoothConnHandle.load();
|
||||
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
requestLowerPowerConnection(conn_handle);
|
||||
}
|
||||
uint16_t conn_handle = nimbleBluetoothConnHandle.load();
|
||||
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
requestLowerPowerConnection(conn_handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,7 +333,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
||||
}
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() override { return bleServer && bleServer->getConnectedCount() > 0; }
|
||||
virtual bool checkIsConnected() override { return nimbleBluetoothConnHandle.load() != BLE_HS_CONN_HANDLE_NONE; }
|
||||
|
||||
void requestHighThroughputConnection(uint16_t conn_handle)
|
||||
{
|
||||
@@ -732,32 +728,19 @@ bool NimbleBluetooth::isActive()
|
||||
|
||||
bool NimbleBluetooth::isConnected()
|
||||
{
|
||||
return bleServer && bleServer->getConnectedCount() > 0;
|
||||
return nimbleBluetoothConnHandle.load() != BLE_HS_CONN_HANDLE_NONE;
|
||||
}
|
||||
|
||||
int NimbleBluetooth::getRssi()
|
||||
{
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
|
||||
if (!bleServer || !isConnected()) {
|
||||
uint16_t conn_handle = nimbleBluetoothConnHandle.load();
|
||||
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
return 0; // No active BLE connection
|
||||
}
|
||||
|
||||
uint16_t connHandle = nimbleBluetoothConnHandle.load();
|
||||
|
||||
if (connHandle == BLE_HS_CONN_HANDLE_NONE) {
|
||||
const auto peers = bleServer->getPeerDevices(true);
|
||||
if (!peers.empty()) {
|
||||
connHandle = peers.begin()->first;
|
||||
nimbleBluetoothConnHandle = connHandle;
|
||||
}
|
||||
}
|
||||
|
||||
if (connHandle == BLE_HS_CONN_HANDLE_NONE) {
|
||||
return 0; // Connection handle not available yet
|
||||
}
|
||||
|
||||
int8_t rssi = 0;
|
||||
const int rc = ble_gap_conn_rssi(connHandle, &rssi);
|
||||
const int rc = ble_gap_conn_rssi(conn_handle, &rssi);
|
||||
|
||||
if (rc == 0) {
|
||||
return rssi;
|
||||
@@ -879,7 +862,7 @@ void NimbleBluetooth::setupService()
|
||||
/// Given a level between 0-100, update the BLE attribute
|
||||
void updateBatteryLevel(uint8_t level)
|
||||
{
|
||||
if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) {
|
||||
if ((config.bluetooth.enabled == true) && nimbleBluetooth->isConnected()) {
|
||||
BatteryCharacteristic->setValue(&level, 1);
|
||||
BatteryCharacteristic->notify();
|
||||
}
|
||||
@@ -894,7 +877,7 @@ void NimbleBluetooth::clearBonds()
|
||||
|
||||
void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length)
|
||||
{
|
||||
if (!bleServer || !isConnected() || length > 512) {
|
||||
if (!isConnected() || length > 512) {
|
||||
return;
|
||||
}
|
||||
logRadioCharacteristic->setValue(logMessage, length);
|
||||
|
||||
@@ -117,6 +117,8 @@
|
||||
#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW
|
||||
#elif defined(HELTEC_T114)
|
||||
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_NODE_T114
|
||||
#elif defined(HELTEC_MESH_NODE_T1)
|
||||
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_NODE_T1
|
||||
#elif defined(MESHLINK)
|
||||
#define HW_VENDOR meshtastic_HardwareModel_MESHLINK
|
||||
#elif defined(SEEED_XIAO_NRF52840_KIT)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#include "../test_helpers.h"
|
||||
#include "mesh/mesh-pb-constants.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct BytesDecodeState {
|
||||
uint8_t *buffer;
|
||||
size_t capacity;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
struct BytesEncodeState {
|
||||
const uint8_t *buffer;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
bool decodeBytesField(pb_istream_t *stream, const pb_field_iter_t *field, void **arg)
|
||||
{
|
||||
(void)field;
|
||||
auto *state = static_cast<BytesDecodeState *>(*arg);
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t fieldLen = stream->bytes_left;
|
||||
if (fieldLen > state->capacity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!pb_read(stream, state->buffer, fieldLen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state->length = fieldLen;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool encodeBytesField(pb_ostream_t *stream, const pb_field_iter_t *field, void *const *arg)
|
||||
{
|
||||
auto *state = static_cast<const BytesEncodeState *>(*arg);
|
||||
if (!state || !state->buffer || state->length == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!pb_encode_tag_for_field(stream, field)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pb_encode_string(stream, state->buffer, state->length);
|
||||
}
|
||||
|
||||
void assert_dmshell_roundtrip(meshtastic_RemoteShell_OpCode op, uint32_t sessionId, uint32_t seq, const uint8_t *payload,
|
||||
size_t payloadLen, uint32_t cols = 0, uint32_t rows = 0)
|
||||
{
|
||||
meshtastic_RemoteShell tx = meshtastic_RemoteShell_init_zero;
|
||||
tx.op = op;
|
||||
tx.session_id = sessionId;
|
||||
tx.seq = seq;
|
||||
tx.cols = cols;
|
||||
tx.rows = rows;
|
||||
|
||||
uint8_t encoded[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0};
|
||||
size_t encodedLen = pb_encode_to_bytes(encoded, sizeof(encoded), meshtastic_RemoteShell_fields, &tx);
|
||||
TEST_ASSERT_GREATER_THAN_UINT32(0, encodedLen);
|
||||
|
||||
meshtastic_RemoteShell rx = meshtastic_RemoteShell_init_zero;
|
||||
|
||||
TEST_ASSERT_TRUE(pb_decode_from_bytes(encoded, encodedLen, meshtastic_RemoteShell_fields, &rx));
|
||||
TEST_ASSERT_EQUAL(op, rx.op);
|
||||
TEST_ASSERT_EQUAL_UINT32(sessionId, rx.session_id);
|
||||
TEST_ASSERT_EQUAL_UINT32(seq, rx.seq);
|
||||
TEST_ASSERT_EQUAL_UINT32(cols, rx.cols);
|
||||
TEST_ASSERT_EQUAL_UINT32(rows, rx.rows);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void test_dmshell_open_roundtrip()
|
||||
{
|
||||
assert_dmshell_roundtrip(meshtastic_RemoteShell_OpCode_OPEN, 0x101, 1, nullptr, 0, 120, 40);
|
||||
}
|
||||
|
||||
void test_dmshell_input_roundtrip()
|
||||
{
|
||||
const uint8_t payload[] = {'l', 's', '\n'};
|
||||
assert_dmshell_roundtrip(meshtastic_RemoteShell_OpCode_INPUT, 0x202, 2, payload, sizeof(payload));
|
||||
}
|
||||
|
||||
void test_dmshell_resize_roundtrip()
|
||||
{
|
||||
assert_dmshell_roundtrip(meshtastic_RemoteShell_OpCode_RESIZE, 0x303, 3, nullptr, 0, 180, 55);
|
||||
}
|
||||
|
||||
void test_dmshell_close_roundtrip()
|
||||
{
|
||||
const uint8_t reason[] = {'b', 'y', 'e'};
|
||||
assert_dmshell_roundtrip(meshtastic_RemoteShell_OpCode_CLOSE, 0x404, 4, reason, sizeof(reason));
|
||||
}
|
||||
@@ -19,6 +19,10 @@ void test_telemetry_environment_metrics_complete_coverage();
|
||||
void test_telemetry_environment_metrics_unset_fields();
|
||||
void test_encrypted_packet_serialization();
|
||||
void test_empty_encrypted_packet();
|
||||
void test_dmshell_open_roundtrip();
|
||||
void test_dmshell_input_roundtrip();
|
||||
void test_dmshell_resize_roundtrip();
|
||||
void test_dmshell_close_roundtrip();
|
||||
|
||||
void setup()
|
||||
{
|
||||
@@ -52,6 +56,12 @@ void setup()
|
||||
RUN_TEST(test_encrypted_packet_serialization);
|
||||
RUN_TEST(test_empty_encrypted_packet);
|
||||
|
||||
// DMShell protobuf transport tests
|
||||
RUN_TEST(test_dmshell_open_roundtrip);
|
||||
RUN_TEST(test_dmshell_input_roundtrip);
|
||||
RUN_TEST(test_dmshell_resize_roundtrip);
|
||||
RUN_TEST(test_dmshell_close_roundtrip);
|
||||
|
||||
UNITY_END();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
; Heltec Mesh Node T1 nRF52840/SX1262 device
|
||||
[env:heltec-mesh-node-t1]
|
||||
custom_meshtastic_hw_model = 133
|
||||
custom_meshtastic_hw_model_slug = HELTEC_MESH_NODE_T1
|
||||
custom_meshtastic_architecture = nrf52840
|
||||
custom_meshtastic_actively_supported = true
|
||||
custom_meshtastic_support_level = 1
|
||||
custom_meshtastic_display_name = Heltec Mesh Node T1
|
||||
custom_meshtastic_images = heltec-mesh-node-t1.svg
|
||||
custom_meshtastic_tags = Heltec
|
||||
|
||||
extends = nrf52840_base
|
||||
board = heltec_mesh_node_t1
|
||||
board_level = pr
|
||||
debug_tool = jlink
|
||||
|
||||
build_flags = ${nrf52840_base.build_flags}
|
||||
-Ivariants/nrf52840/heltec_mesh_node_t1
|
||||
-DHELTEC_MESH_NODE_T1
|
||||
-DUSE_TFTDISPLAY
|
||||
-DUSER_SETUP_LOADED
|
||||
-DST7735_DRIVER
|
||||
-DST7735_REDTAB160x80
|
||||
-D TFT_SPI_PORT=SPI1
|
||||
-D TFT_CS=ST7735_CS
|
||||
-D TFT_DC=ST7735_RS
|
||||
-D TFT_RST=ST7735_RESET
|
||||
-D TFT_BL=ST7735_BL
|
||||
-D TFT_BACKLIGHT_ON=LOW
|
||||
|
||||
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_node_t1>
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
lewisxhe/PCF8563_Library@^1.0.1
|
||||
bodmer/TFT_eSPI@2.5.43
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
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 "nrf.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] = {
|
||||
// P0 - pins 0 and 1 are hardwired for xtal and should never be enabled
|
||||
0xff, 0xff, 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_BUZZER_VOLTAGE_MULTIPLIER_1, OUTPUT);
|
||||
pinMode(PIN_BUZZER_VOLTAGE_MULTIPLIER_2, OUTPUT);
|
||||
digitalWrite(PIN_BUZZER_VOLTAGE_MULTIPLIER_1, HIGH);
|
||||
digitalWrite(PIN_BUZZER_VOLTAGE_MULTIPLIER_2, HIGH);
|
||||
}
|
||||
|
||||
void variant_shutdown()
|
||||
{
|
||||
nrf_gpio_cfg_default(ST7735_CS);
|
||||
nrf_gpio_cfg_default(ST7735_RS);
|
||||
nrf_gpio_cfg_default(ST7735_SDA);
|
||||
nrf_gpio_cfg_default(ST7735_SCK);
|
||||
nrf_gpio_cfg_default(ST7735_RESET);
|
||||
nrf_gpio_cfg_default(ST7735_BL);
|
||||
nrf_gpio_cfg_default(VTFT_CTRL);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_WIRE_SDA);
|
||||
nrf_gpio_cfg_default(PIN_WIRE_SCL);
|
||||
|
||||
nrf_gpio_cfg_default(SX126X_CS);
|
||||
nrf_gpio_cfg_default(SX126X_DIO1);
|
||||
nrf_gpio_cfg_default(SX126X_BUSY);
|
||||
nrf_gpio_cfg_default(SX126X_RESET);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_SPI_MISO);
|
||||
nrf_gpio_cfg_default(PIN_SPI_MOSI);
|
||||
nrf_gpio_cfg_default(PIN_SPI_SCK);
|
||||
|
||||
// nrf_gpio_cfg_default(PIN_SPI1_MISO);// ST7735 doesn't support MISO, so we don't configure it at all
|
||||
nrf_gpio_cfg_default(PIN_SPI1_MOSI);
|
||||
nrf_gpio_cfg_default(PIN_SPI1_SCK);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_GPS_RESET);
|
||||
nrf_gpio_cfg_default(PIN_GPS_EN);
|
||||
nrf_gpio_cfg_default(PIN_GPS_PPS);
|
||||
nrf_gpio_cfg_default(GPS_TX_PIN);
|
||||
nrf_gpio_cfg_default(GPS_RX_PIN);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_BUZZER_VOLTAGE_MULTIPLIER_1);
|
||||
nrf_gpio_cfg_default(PIN_BUZZER_VOLTAGE_MULTIPLIER_2);
|
||||
|
||||
pinMode(PIN_BUZZER, OUTPUT);
|
||||
digitalWrite(PIN_BUZZER, LOW);
|
||||
|
||||
pinMode(PIN_SENSOR_EN, OUTPUT);
|
||||
digitalWrite(PIN_SENSOR_EN, !PIN_SENSOR_EN_ACTIVE); // Turn off sensor power
|
||||
|
||||
pinMode(PIN_LED1, OUTPUT);
|
||||
digitalWrite(PIN_LED1, HIGH);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
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_HELTEC_MESH_NODE_T1_
|
||||
#define _VARIANT_HELTEC_MESH_NODE_T1_
|
||||
/** 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
|
||||
|
||||
#define ST7735_CS (0 + 12)
|
||||
#define ST7735_RS (0 + 22) // DC
|
||||
#define ST7735_SDA (0 + 24)
|
||||
#define ST7735_SCK (32 + 0)
|
||||
#define ST7735_RESET (0 + 20)
|
||||
#define ST7735_MISO -1
|
||||
#define ST7735_BUSY -1
|
||||
#define ST7735_BL (0 + 15)
|
||||
#define VTFT_CTRL (0 + 13) // Active HIGH, powers the ST7735 display
|
||||
#define SPI_FREQUENCY 80000000
|
||||
#define SPI_READ_FREQUENCY 16000000
|
||||
#define SCREEN_ROTATE
|
||||
#define TFT_HEIGHT 160
|
||||
#define TFT_WIDTH 80
|
||||
#define TFT_OFFSET_X 24
|
||||
#define TFT_OFFSET_Y 0
|
||||
#define TFT_INVERT false
|
||||
#define SCREEN_TRANSITION_FRAMERATE 3 // fps
|
||||
#define DISPLAY_FORCE_SMALL_FONTS
|
||||
|
||||
// Number of pins defined in PinDescription array
|
||||
#define PINS_COUNT (48)
|
||||
#define NUM_DIGITAL_PINS (48)
|
||||
#define NUM_ANALOG_INPUTS (1)
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
// LEDs
|
||||
#define PIN_LED1 (0 + 16)
|
||||
#define LED_BLUE PIN_LED1 // fake for bluefruit library
|
||||
#define LED_GREEN PIN_LED1
|
||||
#define LED_STATE_ON 0 // State when LED is lit
|
||||
|
||||
/*
|
||||
* Buttons
|
||||
*/
|
||||
#define PIN_BUTTON1 (32 + 10)
|
||||
#define PIN_BUTTON2 (0 + 14)
|
||||
|
||||
/*
|
||||
No longer populated on PCB
|
||||
*/
|
||||
#define PIN_SERIAL2_RX (-1)
|
||||
#define PIN_SERIAL2_TX (-1)
|
||||
|
||||
/*
|
||||
* I2C
|
||||
*/
|
||||
|
||||
#define WIRE_INTERFACES_COUNT 1
|
||||
|
||||
// I2C bus 1
|
||||
#define PIN_WIRE_SDA (32 + 3)
|
||||
#define PIN_WIRE_SCL (0 + 10)
|
||||
|
||||
#define PIN_SENSOR_EN (32 + 6) // Power control pin for sensors
|
||||
#define PIN_SENSOR_EN_ACTIVE LOW // Power control active state
|
||||
// #define ICM_42607P_INT_PIN (32 + 1) // ICM42607P INT1, Arduino pin 33 / nRF P1.01
|
||||
// #define ICM_42607P_INT2_PIN (32 + 7) // ICM42607P INT2, Arduino pin 39 / nRF P1.07
|
||||
|
||||
/*
|
||||
* Lora radio
|
||||
*/
|
||||
|
||||
#define USE_SX1262
|
||||
#define SX126X_CS (32 + 11) // FIXME - we really should define LORA_CS instead
|
||||
#define LORA_CS SX126X_CS
|
||||
#define SX126X_DIO1 (0 + 31)
|
||||
#define SX126X_BUSY (0 + 29)
|
||||
#define SX126X_RESET (0 + 2)
|
||||
#define SX126X_DIO2_AS_RF_SWITCH
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||
|
||||
/*
|
||||
* SPI Interfaces
|
||||
*/
|
||||
#define SPI_INTERFACES_COUNT 2
|
||||
|
||||
// For LORA, spi 0
|
||||
#define PIN_SPI_MISO (0 + 3)
|
||||
#define PIN_SPI_MOSI (32 + 14)
|
||||
#define PIN_SPI_SCK (32 + 13)
|
||||
|
||||
#define PIN_SPI1_MISO ST7735_MISO
|
||||
#define PIN_SPI1_MOSI ST7735_SDA
|
||||
#define PIN_SPI1_SCK ST7735_SCK
|
||||
|
||||
/*
|
||||
* GPS pins
|
||||
*/
|
||||
#define GPS_UC6580
|
||||
#define GPS_BAUDRATE 115200
|
||||
#define PIN_GPS_RESET (0 + 26)
|
||||
#define GPS_RESET_MODE LOW
|
||||
#define PIN_GPS_EN (0 + 4)
|
||||
#define GPS_EN_ACTIVE LOW
|
||||
#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing
|
||||
#define PIN_GPS_PPS (32 + 9) // Pulse per second input from the GPS
|
||||
#define GPS_TX_PIN (0 + 7)
|
||||
#define GPS_RX_PIN (0 + 8)
|
||||
|
||||
#define GPS_THREAD_INTERVAL 50
|
||||
|
||||
#define PIN_SERIAL1_RX GPS_RX_PIN
|
||||
#define PIN_SERIAL1_TX GPS_TX_PIN
|
||||
|
||||
#define PIN_BUZZER (0 + 9)
|
||||
#define PIN_BUZZER_VOLTAGE_MULTIPLIER_1 (32 + 2)
|
||||
#define PIN_BUZZER_VOLTAGE_MULTIPLIER_2 (32 + 5)
|
||||
|
||||
#define ADC_CTRL 11
|
||||
#define ADC_CTRL_ENABLED HIGH
|
||||
#define BATTERY_PIN 5
|
||||
#define ADC_RESOLUTION 14
|
||||
|
||||
#define BATTERY_SENSE_RESOLUTION_BITS 12
|
||||
#define BATTERY_SENSE_RESOLUTION 4096.0
|
||||
#undef AREF_VOLTAGE
|
||||
#define AREF_VOLTAGE 3.0
|
||||
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
|
||||
#define ADC_MULTIPLIER (4.916F)
|
||||
|
||||
// nrf52840 AIN3 = Pin 5
|
||||
// commented out due to power leakage of 2.9mA in shutdown state see reported issue #8801
|
||||
#define BATTERY_LPCOMP_INPUT NRF_LPCOMP_INPUT_3
|
||||
|
||||
// We have AIN3 with a VBAT divider so AIN3 = VBAT * (100/490)
|
||||
// We have the device going deep sleep under 3.1V, which is AIN3 = 0.63V
|
||||
// So we can wake up when VBAT>=VDD is restored to 3.3V, where AIN3 = 0.67V
|
||||
// Ratio 0.67/3.3 = 0.20, so we can pick a bit higher, 2/8 VDD, which means
|
||||
// VBAT=4.04V
|
||||
#define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_2_8
|
||||
|
||||
#define HAS_RTC 0
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Arduino objects - C++ only
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user