Merge branch 'develop' into pioarduino

This commit is contained in:
Austin
2026-01-20 19:03:57 -05:00
committed by GitHub
92 changed files with 2044 additions and 484 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ ENV PIP_ROOT_USER_ACTION=ignore
RUN apt-get update && apt-get install --no-install-recommends -y \
cmake git zip libgpiod-dev libbluetooth-dev libi2c-dev \
libunistring-dev libmicrohttpd-dev libgnutls28-dev libgcrypt20-dev \
libusb-1.0-0-dev libssl-dev pkg-config && \
libusb-1.0-0-dev libssl-dev pkg-config libsqlite3-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* && \
pip install --no-cache-dir -U \
platformio==6.1.16 \
+7 -7
View File
@@ -9,14 +9,14 @@ plugins:
lint:
enabled:
- checkov@3.2.497
- renovate@42.78.2
- prettier@3.7.4
- trufflehog@3.92.4
- yamllint@1.37.1
- bandit@1.9.2
- renovate@42.84.2
- prettier@3.8.0
- trufflehog@3.92.5
- yamllint@1.38.0
- bandit@1.9.3
- trivy@0.68.2
- taplo@0.10.0
- ruff@0.14.11
- ruff@0.14.13
- isort@7.0.0
- markdownlint@0.47.0
- oxipng@10.0.0
@@ -26,7 +26,7 @@ lint:
- hadolint@2.14.0
- shfmt@3.6.0
- shellcheck@0.11.0
- black@25.12.0
- black@26.1.0
- git-diff-check
- gitleaks@8.30.0
- clang-format@16.0.3
+1 -1
View File
@@ -14,7 +14,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
curl wget g++ zip git ca-certificates pkg-config \
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev \
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware
+1 -1
View File
@@ -11,7 +11,7 @@ RUN apk --no-cache add \
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
libgpiod-dev yaml-cpp-dev bluez-dev \
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
libx11-dev libinput-dev libxkbcommon-dev \
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev \
&& rm -rf /var/cache/apk/* \
&& pip install --no-cache-dir -U platformio \
&& mkdir /tmp/firmware
+38
View File
@@ -0,0 +1,38 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"partitions": "default.csv",
"memory_type": "qio_qspi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi"],
"debug": {
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "CDEBYTE_EoRa-Hub",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://www.cdebyte.com/products/EoRa-HUB-900TB",
"vendor": "CDEBYTE"
}
+53
View File
@@ -0,0 +1,53 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_ELECROW_M4 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x4405"],
["0x239A", "0x0029"],
["0x239A", "0x002A"]
],
"usb_product": "elecrow_thinknode_m4",
"mcu": "nrf52840",
"variant": "ELECROW-ThinkNode-M4",
"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": "ELECROW ThinkNode m4",
"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://www.elecrow.com/thinknode-m4-power-bank-lora-device-with-meshtastic-lora-tracker-function-powered-by-nrf52840.html",
"vendor": "ELECROW"
}
+2 -1
View File
@@ -25,7 +25,8 @@ Build-Depends: debhelper-compat (= 13),
liborcania-dev,
libx11-dev,
libinput-dev,
libxkbcommon-x11-dev
libxkbcommon-x11-dev,
libsqlite3-dev
Standards-Version: 4.6.2
Homepage: https://github.com/meshtastic/firmware
Rules-Requires-Root: no
+1
View File
@@ -39,6 +39,7 @@ BuildRequires: pkgconfig(bluez)
BuildRequires: pkgconfig(libusb-1.0)
BuildRequires: libi2c-devel
BuildRequires: pkgconfig(libuv)
BuildRequires: pkgconfig(sqlite3)
# Web components:
BuildRequires: pkgconfig(openssl)
BuildRequires: pkgconfig(liborcania)
+29 -4
View File
@@ -119,7 +119,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/12f8cddc1e2908e1988da21e3500c695668e8d92.zip
https://github.com/meshtastic/device-ui/archive/3480b731d28b10d73414cf0dd7975bff745de8cf.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
@@ -129,7 +129,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit Unified Sensor packageName=adafruit/library/Adafruit Unified Sensor
adafruit/Adafruit Unified Sensor@1.1.15
# renovate: datasource=custom.pio depName=Adafruit BMP280 packageName=adafruit/library/Adafruit BMP280 Library
adafruit/Adafruit BMP280 Library@2.6.8
adafruit/Adafruit BMP280 Library@3.0.0
# renovate: datasource=custom.pio depName=Adafruit BMP085 packageName=adafruit/library/Adafruit BMP085 Library
adafruit/Adafruit BMP085 Library@1.2.4
# renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library
@@ -142,8 +142,6 @@ lib_deps =
adafruit/Adafruit INA260 Library@1.5.3
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
adafruit/Adafruit INA219@1.2.3
# renovate: datasource=custom.pio depName=Adafruit PM25 AQI Sensor packageName=adafruit/library/Adafruit PM25 AQI Sensor
adafruit/Adafruit PM25 AQI Sensor@2.0.0
# renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050
adafruit/Adafruit MPU6050@2.2.6
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
@@ -214,3 +212,30 @@ lib_deps =
sensirion/Sensirion Core@0.7.2
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
sensirion/Sensirion I2C SCD4x@1.1.0
; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets)
[environmental_extra_no_bsec]
lib_deps =
# renovate: datasource=custom.pio depName=Adafruit BMP3XX packageName=adafruit/library/Adafruit BMP3XX Library
adafruit/Adafruit BMP3XX Library@2.1.6
# renovate: datasource=custom.pio depName=Adafruit MAX1704X packageName=adafruit/library/Adafruit MAX1704X
adafruit/Adafruit MAX1704X@1.0.3
# renovate: datasource=custom.pio depName=Adafruit SHTC3 packageName=adafruit/library/Adafruit SHTC3 Library
adafruit/Adafruit SHTC3 Library@1.0.2
# renovate: datasource=custom.pio depName=Adafruit LPS2X packageName=adafruit/library/Adafruit LPS2X
adafruit/Adafruit LPS2X@2.0.6
# renovate: datasource=custom.pio depName=Adafruit SHT31 packageName=adafruit/library/Adafruit SHT31 Library
adafruit/Adafruit SHT31 Library@2.2.2
# renovate: datasource=custom.pio depName=Adafruit VEML7700 packageName=adafruit/library/Adafruit VEML7700 Library
adafruit/Adafruit VEML7700 Library@2.1.6
# renovate: datasource=custom.pio depName=Adafruit SHT4x packageName=adafruit/library/Adafruit SHT4x Library
adafruit/Adafruit SHT4x Library@1.0.5
# renovate: datasource=custom.pio depName=SparkFun Qwiic Scale NAU7802 packageName=sparkfun/library/SparkFun Qwiic Scale NAU7802 Arduino Library
sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@1.0.6
# renovate: datasource=custom.pio depName=ClosedCube OPT3001 packageName=closedcube/library/ClosedCube OPT3001
closedcube/ClosedCube OPT3001@1.1.2
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
sensirion/Sensirion Core@0.7.2
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
sensirion/Sensirion I2C SCD4x@1.1.0
+137 -1
View File
@@ -530,7 +530,9 @@ class AnalogBatteryLevel : public HasBatteryLevel
return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
}
#endif
#ifdef EXT_CHRG_DETECT
#if defined(ELECROW_ThinkNode_M6)
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value || isVbusIn();
#elif EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#elif defined(BATTERY_CHARGING_INV)
return !digitalRead(BATTERY_CHARGING_INV);
@@ -737,6 +739,8 @@ bool Power::setup()
found = true;
} else if (lipoChargerInit()) {
found = true;
} else if (serialBatteryInit()) {
found = true;
} else if (meshSolarInit()) {
found = true;
} else if (analogInit()) {
@@ -1613,3 +1617,135 @@ bool Power::meshSolarInit()
return false;
}
#endif
#ifdef HAS_SERIAL_BATTERY_LEVEL
#include <SoftwareSerial.h>
/**
* SerialBatteryLevel class for pulling battery information from a secondary MCU over serial.
*/
class SerialBatteryLevel : public HasBatteryLevel
{
public:
/**
* Init the I2C meshSolar battery level sensor
*/
bool runOnce()
{
BatterySerial.begin(4800);
return true;
}
/**
* Battery state of charge, from 0 to 100 or -1 for unknown
*/
virtual int getBatteryPercent() override { return v_percent; }
/**
* The raw voltage of the battery in millivolts, or NAN if unknown
*/
virtual uint16_t getBattVoltage() override { return voltage * 1000; }
/**
* return true if there is a battery installed in this unit
*/
virtual bool isBatteryConnect() override
{
// definitely need to gobble up more bytes at once
if (BatterySerial.available() > 5) {
// LOG_WARN("SerialBatteryLevel: %u bytes available", BatterySerial.available());
while (BatterySerial.available() > 11) {
BatterySerial.read(); // flush old data
}
// LOG_WARN("SerialBatteryLevel: %u bytes now available", BatterySerial.available());
int tries = 0;
while (BatterySerial.read() != 0xFE) {
tries++; // wait for start byte
if (tries > 10) {
LOG_WARN("SerialBatteryLevel: no start byte found");
return 1;
}
}
Data[1] = BatterySerial.read();
Data[2] = BatterySerial.read();
Data[3] = BatterySerial.read();
Data[4] = BatterySerial.read();
Data[5] = BatterySerial.read();
if (Data[5] != 0xFD) {
LOG_WARN("SerialBatteryLevel: invalid end byte %02x", Data[5]);
return true;
}
v_percent = Data[1];
voltage = Data[2] + (((float)Data[3]) / 100) + (((float)Data[4]) / 10000);
voltage *= 2;
// LOG_WARN("SerialBatteryLevel: received data %u, %f, %02x", v_percent, voltage, Data[5]);
return true;
}
// This function runs first, so use it to grab the latest data from the secondary MCU
return true;
}
/**
* return true if there is an external power source detected
*/
virtual bool isVbusIn() override
{
#if defined(EXT_CHRG_DETECT)
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#endif
return false;
}
virtual bool isCharging() override
{
#ifdef EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#endif
// by default, we check the battery voltage only
return isVbusIn();
}
private:
SoftwareSerial BatterySerial = SoftwareSerial(SERIAL_BATTERY_RX, SERIAL_BATTERY_TX);
uint8_t Data[6] = {0};
int v_percent = 0;
float voltage = 0.0;
};
SerialBatteryLevel serialBatteryLevel;
/**
* Init the serial battery level sensor
*/
bool Power::serialBatteryInit()
{
#ifdef EXT_PWR_DETECT
pinMode(EXT_PWR_DETECT, INPUT);
#endif
#ifdef EXT_CHRG_DETECT
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
#endif
bool result = serialBatteryLevel.runOnce();
LOG_DEBUG("Power::serialBatteryInit serial battery sensor is %s", result ? "ready" : "not ready yet");
if (!result)
return false;
batteryLevel = &serialBatteryLevel;
return true;
}
#else
/**
* If this device has no serial battery level sensor, don't try to use it.
*/
bool Power::serialBatteryInit()
{
return false;
}
#endif
+2 -10
View File
@@ -54,7 +54,7 @@ size_t SafeFile::write(const uint8_t *buffer, size_t size)
}
/**
* Atomically close the file (deleting any old versions) and readback the contents to confirm the hash matches
* Atomically close the file (overwriting any old version) and readback the contents to confirm the hash matches
*
* @return false for failure
*/
@@ -73,15 +73,7 @@ bool SafeFile::close()
if (!testReadback())
return false;
{ // Scope for lock
concurrency::LockGuard g(spiLock);
// brief window of risk here ;-)
if (fullAtomic && FSCom.exists(filename.c_str()) && !FSCom.remove(filename.c_str())) {
LOG_ERROR("Can't remove old pref file");
return false;
}
}
// Rename or overwrite (atomic operation)
String filenameTmp = filename;
filenameTmp += ".tmp";
if (!renameFile(filenameTmp.c_str(), filename.c_str())) {
+7 -17
View File
@@ -172,11 +172,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// -----------------------------------------------------------------------------
// OLED & Input
// -----------------------------------------------------------------------------
#define SSD1306_ADDRESS_L 0x3C // Addr = 0
#define SSD1306_ADDRESS_H 0x3D // Addr = 1
#if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK)
#define SSD1306_ADDRESS 0x3D
#define SSD1306_ADDRESS SSD1306_ADDRESS_H
#define USE_SH1106
#else
#define SSD1306_ADDRESS 0x3C
#endif
#define ST7567_ADDRESS 0x3F
@@ -205,7 +206,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define INA_ADDR_WAVESHARE_UPS 0x43
#define INA3221_ADDR 0x42
#define MAX1704X_ADDR 0x36
#define QMC6310_ADDR 0x1C
#define QMC6310U_ADDR 0x1C
#define QMI8658_ADDR 0x6B
#define QMC5883L_ADDR 0x0D
#define HMC5883L_ADDR 0x1E
@@ -214,7 +215,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define LPS22HB_ADDR_ALT 0x5D
#define SHT31_4x_ADDR 0x44
#define SHT31_4x_ADDR_ALT 0x45
#define PMSA0031_ADDR 0x12
#define PMSA003I_ADDR 0x12
#define QMA6100P_ADDR 0x12
#define AHT10_ADDR 0x38
#define RCWL9620_ADDR 0x57
@@ -444,18 +445,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif
#endif
// BME680 BSEC2 support detection
#if !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
#if defined(RAK_4631) || defined(TBEAM_V10)
#define MESHTASTIC_BME680_BSEC2_SUPPORTED 1
#define MESHTASTIC_BME680_HEADER <bsec2.h>
#else
#define MESHTASTIC_BME680_BSEC2_SUPPORTED 0
#define MESHTASTIC_BME680_HEADER <Adafruit_BME680.h>
#endif // defined(RAK_4631)
#endif // !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
// -----------------------------------------------------------------------------
// Global switches to turn off features for a minimized build
// -----------------------------------------------------------------------------
@@ -480,6 +469,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MESHTASTIC_EXCLUDE_AUDIO 1
#define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1
#define MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR 1
#define MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR 1
#define MESHTASTIC_EXCLUDE_HEALTH_TELEMETRY 1
#define MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION 1
#define MESHTASTIC_EXCLUDE_PAXCOUNTER 1
+1 -1
View File
@@ -43,7 +43,7 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
ScanI2C::FoundDevice ScanI2C::firstAQI() const
{
ScanI2C::DeviceType types[] = {PMSA0031, SCD4X};
ScanI2C::DeviceType types[] = {PMSA003I, SCD4X};
return firstOfOrNONE(2, types);
}
+3 -2
View File
@@ -35,11 +35,12 @@ class ScanI2C
SHT4X,
SHTC3,
LPS22HB,
QMC6310,
QMC6310U,
QMC6310N,
QMI8658,
QMC5883L,
HMC5883L,
PMSA0031,
PMSA003I,
QMA6100P,
MPU6050,
LIS3DH,
+12 -7
View File
@@ -63,6 +63,10 @@ ScanI2C::DeviceType ScanI2CTwoWire::probeOLED(ScanI2C::DeviceAddress addr) const
if (i2cBus->available()) {
r = i2cBus->read();
}
if (r == 0x80) {
LOG_INFO("QMC6310N found at address 0x%02X", addr.address);
return ScanI2C::DeviceType::QMC6310N;
}
r &= 0x0f;
if (r == 0x08 || r == 0x00) {
@@ -106,7 +110,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
if (i2cBus->available())
i2cBus->read();
}
LOG_DEBUG("Register value: 0x%x", value);
LOG_DEBUG("Register value from 0x%x: 0x%x", registerLocation.i2cAddress.address, value);
return value;
}
@@ -175,7 +179,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = NONE;
if (err == 0) {
switch (addr.address) {
case SSD1306_ADDRESS:
case SSD1306_ADDRESS_H:
case SSD1306_ADDRESS_L:
type = probeOLED(addr);
break;
@@ -382,11 +387,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2);
if (registerValue == 0x5449) {
if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
type = OPT3001;
logFoundDevice("OPT3001", (uint8_t)addr.address);
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2) != 0) { // unique SHT4x serial number
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 6) !=
0) { // unique SHT4x serial number (6 bytes inc. CRC)
type = SHT4X;
logFoundDevice("SHT4X", (uint8_t)addr.address);
} else {
@@ -412,7 +417,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case LPS22HB_ADDR_ALT:
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB", (uint8_t)addr.address)
SCAN_SIMPLE_CASE(QMC6310_ADDR, QMC6310, "QMC6310", (uint8_t)addr.address)
SCAN_SIMPLE_CASE(QMC6310U_ADDR, QMC6310U, "QMC6310U", (uint8_t)addr.address)
case QMI8658_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0A), 1); // get ID
@@ -442,7 +447,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#ifdef HAS_QMA6100P
SCAN_SIMPLE_CASE(QMA6100P_ADDR, QMA6100P, "QMA6100P", (uint8_t)addr.address)
#else
SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031", (uint8_t)addr.address)
SCAN_SIMPLE_CASE(PMSA003I_ADDR, PMSA003I, "PMSA003I", (uint8_t)addr.address)
#endif
case BMA423_ADDR: // this can also be LIS3DH_ADDR_ALT
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2);
+41
View File
@@ -0,0 +1,41 @@
#ifdef CAN_RECLOCK_I2C
#include "ScanI2CTwoWire.h"
uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus)
{
uint32_t currentClock;
/* See https://github.com/arduino/Arduino/issues/11457
Currently, only ESP32 can getClock()
While all cores can setClock()
https://github.com/sandeepmistry/arduino-nRF5/blob/master/libraries/Wire/Wire.h#L50
https://github.com/earlephilhower/arduino-pico/blob/master/libraries/Wire/src/Wire.h#L60
https://github.com/stm32duino/Arduino_Core_STM32/blob/main/libraries/Wire/src/Wire.h#L103
For cases when I2C speed is different to the ones defined by sensors (see defines in sensor classes)
we need to reclock I2C and set it back to the previous desired speed.
Only for cases where we can know OR predefine the speed, we can do this.
*/
#ifdef ARCH_ESP32
currentClock = i2cBus->getClock();
#elif defined(ARCH_NRF52)
// TODO add getClock function or return a predefined clock speed per variant?
return 0;
#elif defined(ARCH_RP2040)
// TODO add getClock function or return a predefined clock speed per variant
return 0;
#elif defined(ARCH_STM32WL)
// TODO add getClock function or return a predefined clock speed per variant
return 0;
#else
return 0;
#endif
if (currentClock != desiredClock) {
LOG_DEBUG("Changing I2C clock to %u", desiredClock);
i2cBus->setClock(desiredClock);
}
return currentClock;
}
#endif
+5 -8
View File
@@ -896,14 +896,11 @@ void GPS::writePinEN(bool on)
void GPS::writePinStandby(bool standby)
{
#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones
// Determine the new value for the pin
// Normally: active HIGH for awake
#ifdef PIN_GPS_STANDBY_INVERTED
bool val = standby;
#else
bool val = !standby;
#endif
bool val;
if (standby)
val = GPS_STANDBY_ACTIVE;
else
val = !GPS_STANDBY_ACTIVE;
// Write and log
pinMode(PIN_GPS_STANDBY, OUTPUT);
+5
View File
@@ -16,6 +16,11 @@
#define GPS_EN_ACTIVE 1
#endif
// Allow defining the polarity of the STANDBY output. default is LOW for standby
#ifndef GPS_STANDBY_ACTIVE
#define GPS_STANDBY_ACTIVE LOW
#endif
static constexpr uint32_t GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS = 10 * 1000UL;
static constexpr uint32_t GPS_FIX_HOLD_MAX_MS = 20000;
+10 -1
View File
@@ -9,6 +9,15 @@
#include "GxEPD2Multi.h"
#endif
// Limit how often we push a full E-Ink refresh. T-Deck Pro needs faster updates for typing.
#ifndef EINK_FORCE_DISPLAY_THROTTLE_MS
#if defined(T_DECK_PRO)
#define EINK_FORCE_DISPLAY_THROTTLE_MS 200
#else
#define EINK_FORCE_DISPLAY_THROTTLE_MS 1000
#endif
#endif
/**
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
*
@@ -42,7 +51,7 @@ class EInkDisplay : public OLEDDisplay
*
* @return true if we did draw the screen
*/
virtual bool forceDisplay(uint32_t msecLimit = 1000);
virtual bool forceDisplay(uint32_t msecLimit = EINK_FORCE_DISPLAY_THROTTLE_MS);
/**
* Run any code needed to complete an update, after the physical refresh has completed.
+1 -1
View File
@@ -825,7 +825,7 @@ int32_t Screen::runOnce()
#endif
}
#endif
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0 && !suppressRebootBanner) {
showSimpleBanner("Rebooting...", 0);
}
+1 -1
View File
@@ -438,7 +438,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
if (currentResolution == ScreenResolution::UltraLow) {
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num);
} else {
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz (%d)", freqStr, config.lora.channel_num);
}
}
size_t len = strlen(frequencyslot);
+294 -42
View File
@@ -59,17 +59,18 @@ BannerOverlayOptions createStaticBannerOptions(const char *message, const MenuOp
} // namespace
menuHandler::screenMenus menuHandler::menuQueue = menu_none;
uint32_t menuHandler::pickedNodeNum = 0;
bool test_enabled = false;
uint8_t test_count = 0;
void menuHandler::loraMenu()
{
static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "LoRa Region"};
enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, lora_picker = 3 };
static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "Frequency Slot", "LoRa Region"};
enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, frequency_slot = 3, lora_picker = 4 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "LoRa Actions";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4;
bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
// No action
@@ -77,6 +78,8 @@ void menuHandler::loraMenu()
menuHandler::menuQueue = menuHandler::device_role_picker;
} else if (selected == radio_preset_picker) {
menuHandler::menuQueue = menuHandler::radio_preset_picker;
} else if (selected == frequency_slot) {
menuHandler::menuQueue = menuHandler::frequency_slot;
} else if (selected == lora_picker) {
menuHandler::menuQueue = menuHandler::lora_picker;
}
@@ -247,6 +250,113 @@ void menuHandler::DeviceRolePicker()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::FrequencySlotPicker()
{
enum ReplyOptions : int { Back = -1 };
constexpr int MAX_CHANNEL_OPTIONS = 202;
static const char *optionsArray[MAX_CHANNEL_OPTIONS];
static int optionsEnumArray[MAX_CHANNEL_OPTIONS];
static char channelText[MAX_CHANNEL_OPTIONS - 1][12];
int options = 0;
optionsArray[options] = "Back";
optionsEnumArray[options++] = Back;
optionsArray[options] = "Slot 0 (Auto)";
optionsEnumArray[options++] = 0;
// Calculate number of channels (copied from RadioInterface::applyModemConfig())
meshtastic_Config_LoRaConfig &loraConfig = config.lora;
double bw = loraConfig.bandwidth;
if (loraConfig.use_preset) {
switch (loraConfig.modem_preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
bw = (myRegion->wideLora) ? 1625.0 : 500;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
bw = (myRegion->wideLora) ? 812.5 : 250;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
bw = (myRegion->wideLora) ? 812.5 : 250;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
bw = (myRegion->wideLora) ? 812.5 : 250;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
bw = (myRegion->wideLora) ? 812.5 : 250;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
bw = (myRegion->wideLora) ? 1625.0 : 500;
break;
default:
bw = (myRegion->wideLora) ? 812.5 : 250;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
bw = (myRegion->wideLora) ? 406.25 : 125;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
bw = (myRegion->wideLora) ? 406.25 : 125;
break;
}
} else {
bw = loraConfig.bandwidth;
if (bw == 31) // This parameter is not an integer
bw = 31.25;
if (bw == 62) // Fix for 62.5Khz bandwidth
bw = 62.5;
if (bw == 200)
bw = 203.125;
if (bw == 400)
bw = 406.25;
if (bw == 800)
bw = 812.5;
if (bw == 1600)
bw = 1625.0;
}
uint32_t numChannels = 0;
if (myRegion) {
numChannels = (uint32_t)floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000.0)));
} else {
LOG_WARN("Region not set, cannot calculate number of channels");
return;
}
if (numChannels > (uint32_t)(MAX_CHANNEL_OPTIONS - 2))
numChannels = (uint32_t)(MAX_CHANNEL_OPTIONS - 2);
for (uint32_t ch = 1; ch <= numChannels; ch++) {
snprintf(channelText[ch - 1], sizeof(channelText[ch - 1]), "Slot %lu", (unsigned long)ch);
optionsArray[options] = channelText[ch - 1];
optionsEnumArray[options++] = (int)ch;
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Frequency Slot";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
// Start highlight on current channel if possible, otherwise on "1"
int initial = (int)config.lora.channel_num + 1;
if (initial < 2 || initial > (int)numChannels + 1)
initial = 1;
bannerOptions.InitialSelected = initial;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
menuHandler::menuQueue = menuHandler::lora_Menu;
screen->runNow();
return;
}
config.lora.channel_num = selected;
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::RadioPresetPicker()
{
static const RadioPresetOption presetOptions[] = {
@@ -277,6 +387,8 @@ void menuHandler::RadioPresetPicker()
}
config.lora.modem_preset = option.value;
config.lora.channel_num = 0; // Reset to default channel for the preset
config.lora.override_frequency = 0; // Clear any custom frequency
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
});
@@ -1213,20 +1325,13 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu()
{
enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, NodeNameLength, enumEnd };
enum optionsNumbers { Back, NodePicker, TraceRoute, Verify, Reset, NodeNameLength, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
optionsArray[options] = "Add Favorite";
optionsEnumArray[options++] = Favorite;
optionsArray[options] = "Trace Route";
optionsEnumArray[options++] = TraceRoute;
if (currentResolution != ScreenResolution::UltraLow) {
optionsArray[options] = "Key Verification";
optionsEnumArray[options++] = Verify;
}
optionsArray[options] = "Node Actions / Settings";
optionsEnumArray[options++] = NodePicker;
if (currentResolution != ScreenResolution::UltraLow) {
optionsArray[options] = "Show Long/Short Name";
@@ -1241,18 +1346,12 @@ void menuHandler::nodeListMenu()
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Favorite) {
menuQueue = add_favorite;
screen->runNow();
} else if (selected == Verify) {
menuQueue = key_verification_init;
if (selected == NodePicker) {
menuQueue = NodePicker_menu;
screen->runNow();
} else if (selected == Reset) {
menuQueue = reset_node_db_menu;
screen->runNow();
} else if (selected == TraceRoute) {
menuQueue = trace_route_menu;
screen->runNow();
} else if (selected == NodeNameLength) {
menuHandler::menuQueue = menuHandler::node_name_length_menu;
screen->runNow();
@@ -1261,6 +1360,159 @@ void menuHandler::nodeListMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::NodePicker()
{
const char *NODE_PICKER_TITLE;
if (currentResolution == ScreenResolution::UltraLow) {
NODE_PICKER_TITLE = "Pick Node";
} else {
NODE_PICKER_TITLE = "Pick A Node";
}
screen->showNodePicker(NODE_PICKER_TITLE, 30000, [](uint32_t nodenum) -> void {
LOG_INFO("Nodenum: %u", nodenum);
// Store the selection so the Manage Node menu knows which node to operate on
menuHandler::pickedNodeNum = nodenum;
// Keep UI favorite context in sync (used elsewhere for some node-based actions)
graphics::UIRenderer::currentFavoriteNodeNum = nodenum;
menuQueue = Manage_Node_menu;
screen->runNow();
});
}
void menuHandler::ManageNodeMenu()
{
// If we don't have a node selected yet, go fast exit
auto node = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
if (!node) {
return;
}
enum optionsNumbers { Back, Favorite, Mute, TraceRoute, KeyVerification, Ignore, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
if (node->is_favorite) {
optionsArray[options] = "Unfavorite";
} else {
optionsArray[options] = "Favorite";
}
optionsEnumArray[options++] = Favorite;
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
if (isMuted) {
optionsArray[options] = "Unmute Notifications";
} else {
optionsArray[options] = "Mute Notifications";
}
optionsEnumArray[options++] = Mute;
optionsArray[options] = "Trace Route";
optionsEnumArray[options++] = TraceRoute;
optionsArray[options] = "Key Verification";
optionsEnumArray[options++] = KeyVerification;
if (node->is_ignored) {
optionsArray[options] = "Unignore Node";
} else {
optionsArray[options] = "Ignore Node";
}
optionsEnumArray[options++] = Ignore;
BannerOverlayOptions bannerOptions;
std::string title = "";
if (node->has_user && node->user.long_name && node->user.long_name[0]) {
title += sanitizeString(node->user.long_name).substr(0, 15);
} else {
char buf[20];
snprintf(buf, sizeof(buf), "%08X", (unsigned int)node->num);
title += buf;
}
bannerOptions.message = title.c_str();
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
menuQueue = node_base_menu;
screen->runNow();
return;
}
if (selected == Favorite) {
auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
if (!n) {
return;
}
if (n->is_favorite) {
LOG_INFO("Removing node %08X from favorites", menuHandler::pickedNodeNum);
nodeDB->set_favorite(false, menuHandler::pickedNodeNum);
} else {
LOG_INFO("Adding node %08X to favorites", menuHandler::pickedNodeNum);
nodeDB->set_favorite(true, menuHandler::pickedNodeNum);
}
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
return;
}
if (selected == Mute) {
auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
if (!n) {
return;
}
if (n->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) {
n->bitfield &= ~NODEINFO_BITFIELD_IS_MUTED_MASK;
LOG_INFO("Unmuted node %08X", menuHandler::pickedNodeNum);
} else {
n->bitfield |= NODEINFO_BITFIELD_IS_MUTED_MASK;
LOG_INFO("Muted node %08X", menuHandler::pickedNodeNum);
}
nodeDB->notifyObservers(true);
nodeDB->saveToDisk();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
return;
}
if (selected == TraceRoute) {
LOG_INFO("Starting traceroute to %08X", menuHandler::pickedNodeNum);
if (traceRouteModule) {
traceRouteModule->startTraceRoute(menuHandler::pickedNodeNum);
}
return;
}
if (selected == KeyVerification) {
LOG_INFO("Initiating key verification with %08X", menuHandler::pickedNodeNum);
if (keyVerificationModule) {
keyVerificationModule->sendInitialRequest(menuHandler::pickedNodeNum);
}
return;
}
if (selected == Ignore) {
auto n = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
if (!n) {
return;
}
if (n->is_ignored) {
n->is_ignored = false;
LOG_INFO("Unignoring node %08X", menuHandler::pickedNodeNum);
} else {
n->is_ignored = true;
LOG_INFO("Ignoring node %08X", menuHandler::pickedNodeNum);
}
nodeDB->notifyObservers(true);
nodeDB->saveToDisk();
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
return;
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::nodeNameLengthMenu()
{
static const NodeNameOption nodeNameOptions[] = {
@@ -1289,6 +1541,7 @@ void menuHandler::nodeNameLengthMenu()
}
config.display.use_long_node_name = option.value;
saveUIConfig();
LOG_INFO("Setting names to %s", option.value ? "long" : "short");
});
@@ -1958,21 +2211,6 @@ void menuHandler::shutdownMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::addFavoriteMenu()
{
const char *NODE_PICKER_TITLE;
if (currentResolution == ScreenResolution::UltraLow) {
NODE_PICKER_TITLE = "Node Favorite";
} else {
NODE_PICKER_TITLE = "Node To Favorite";
}
screen->showNodePicker(NODE_PICKER_TITLE, 30000, [](uint32_t nodenum) -> void {
LOG_WARN("Nodenum: %u", nodenum);
nodeDB->set_favorite(true, nodenum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
});
}
void menuHandler::removeFavoriteMenu()
{
@@ -2247,7 +2485,8 @@ void menuHandler::FrameToggles_menu()
lora,
clock,
show_favorites,
show_telemetry,
show_env_telemetry,
show_aq_telemetry,
show_power,
enumEnd
};
@@ -2292,8 +2531,11 @@ void menuHandler::FrameToggles_menu()
optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
optionsEnumArray[options++] = show_favorites;
optionsArray[options] = moduleConfig.telemetry.environment_screen_enabled ? "Hide Telemetry" : "Show Telemetry";
optionsEnumArray[options++] = show_telemetry;
optionsArray[options] = moduleConfig.telemetry.environment_screen_enabled ? "Hide Env. Telemetry" : "Show Env. Telemetry";
optionsEnumArray[options++] = show_env_telemetry;
optionsArray[options] = moduleConfig.telemetry.air_quality_screen_enabled ? "Hide AQ Telemetry" : "Show AQ Telemetry";
optionsEnumArray[options++] = show_aq_telemetry;
optionsArray[options] = moduleConfig.telemetry.power_screen_enabled ? "Hide Power" : "Show Power";
optionsEnumArray[options++] = show_power;
@@ -2356,10 +2598,14 @@ void menuHandler::FrameToggles_menu()
screen->toggleFrameVisibility("show_favorites");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == show_telemetry) {
} else if (selected == show_env_telemetry) {
moduleConfig.telemetry.environment_screen_enabled = !moduleConfig.telemetry.environment_screen_enabled;
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == show_aq_telemetry) {
moduleConfig.telemetry.air_quality_screen_enabled = !moduleConfig.telemetry.air_quality_screen_enabled;
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == show_power) {
moduleConfig.telemetry.power_screen_enabled = !moduleConfig.telemetry.power_screen_enabled;
menuHandler::menuQueue = menuHandler::FrameToggles;
@@ -2416,6 +2662,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case radio_preset_picker:
RadioPresetPicker();
break;
case frequency_slot:
FrequencySlotPicker();
break;
case no_timeout_lora_picker:
LoraRegionPicker(0);
break;
@@ -2484,8 +2733,11 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case shutdown_menu:
shutdownMenu();
break;
case add_favorite:
addFavoriteMenu();
case NodePicker_menu:
NodePicker();
break;
case Manage_Node_menu:
ManageNodeMenu();
break;
case remove_favorite:
removeFavoriteMenu();
+8 -1
View File
@@ -13,6 +13,7 @@ class menuHandler
lora_picker,
device_role_picker,
radio_preset_picker,
frequency_slot,
no_timeout_lora_picker,
TZ_picker,
twelve_hour_picker,
@@ -33,7 +34,8 @@ class menuHandler
brightness_picker,
reboot_menu,
shutdown_menu,
add_favorite,
NodePicker_menu,
Manage_Node_menu,
remove_favorite,
test_menu,
number_test,
@@ -55,12 +57,14 @@ class menuHandler
DisplayUnits
};
static screenMenus menuQueue;
static uint32_t pickedNodeNum; // node selected by NodePicker for ManageNodeMenu
static void OnboardMessage();
static void LoraRegionPicker(uint32_t duration = 30000);
static void loraMenu();
static void DeviceRolePicker();
static void RadioPresetPicker();
static void FrequencySlotPicker();
static void handleMenuSwitch(OLEDDisplay *display);
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
static void clockMenu();
@@ -90,6 +94,8 @@ class menuHandler
static void BrightnessPickerMenu();
static void rebootMenu();
static void shutdownMenu();
static void NodePicker();
static void ManageNodeMenu();
static void addFavoriteMenu();
static void removeFavoriteMenu();
static void traceRouteMenu();
@@ -149,6 +155,7 @@ using GPSToggleOption = MenuOption<meshtastic_Config_PositionConfig_GpsMode>;
using GPSFormatOption = MenuOption<meshtastic_DeviceUIConfig_GpsCoordinateFormat>;
using NodeNameOption = MenuOption<bool>;
using PositionMenuOption = MenuOption<int>;
using ManageNodeOption = MenuOption<int>;
using ClockFaceOption = MenuOption<bool>;
} // namespace graphics
+34
View File
@@ -176,6 +176,7 @@ int calculateMaxScroll(int totalEntries, int visibleRows)
void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd)
{
x = (currentResolution == ScreenResolution::High) ? x - 2 : (currentResolution == ScreenResolution::Low) ? x - 1 : x;
for (int y = yStart; y <= yEnd; y += 2) {
display->setPixel(x, y);
}
@@ -205,9 +206,11 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
{
bool isLeftCol = (x < SCREEN_WIDTH / 2);
int nameMaxWidth = columnWidth - 25;
int timeOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
char timeStr[10];
uint32_t seconds = sinceLastSeen(node);
@@ -234,6 +237,13 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
int rightEdge = x + columnWidth - timeOffset;
if (timeStr[strlen(timeStr) - 1] == 'm') // Fix the fact that our fonts don't line up well all the time
@@ -253,6 +263,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
int barsXOffset = columnWidth - barsOffset;
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -265,6 +276,13 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
// Draw signal strength bars
int bars = (node->snr > 5) ? 4 : (node->snr > 0) ? 3 : (node->snr > -5) ? 2 : (node->snr > -10) ? 1 : 0;
@@ -298,6 +316,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
char distStr[10] = "";
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
@@ -358,6 +377,13 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
if (strlen(distStr) > 0) {
int offset = (currentResolution == ScreenResolution::High)
@@ -392,6 +418,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
const char *nodeName = getSafeNodeName(display, node, columnWidth);
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -403,6 +430,13 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
}
}
if (node->is_ignored || isMuted) {
if (currentResolution == ScreenResolution::High) {
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
} else {
display->drawLine(x + 4, y + 6, (isLeftCol ? 0 : x - 3) + nameMaxWidth - 4, y + 6);
}
}
}
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float myHeading,
+2
View File
@@ -93,6 +93,8 @@ int32_t RotaryEncoderInterruptBase::runOnce()
if (!pressDetected) {
this->action = ROTARY_ACTION_NONE;
} else if (now - pressStartTime < LONG_PRESS_DURATION) {
return (20); // keep checking for long/short until time expires
}
return INT32_MAX;
+49 -5
View File
@@ -105,6 +105,43 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
#include <string>
#endif
#ifdef ARCH_ESP32
#ifdef DEBUG_PARTITION_TABLE
#include "esp_partition.h"
void printPartitionTable()
{
printf("\n--- Partition Table ---\n");
// Print Column Headers
printf("| %-16s | %-4s | %-7s | %-10s | %-10s |\n", "Label", "Type", "Subtype", "Offset", "Size");
printf("|------------------|------|---------|------------|------------|\n");
// Create an iterator to find ALL partitions (Type ANY, Subtype ANY)
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
// Loop through the iterator
if (it != NULL) {
do {
const esp_partition_t *part = esp_partition_get(it);
// Print details: Label, Type (Hex), Subtype (Hex), Offset (Hex), Size (Hex)
printf("| %-16s | 0x%02x | 0x%02x | 0x%08x | 0x%08x |\n", part->label, part->type, part->subtype, part->address,
part->size);
// Move to next partition
it = esp_partition_next(it);
} while (it != NULL);
// Release the iterator memory
esp_partition_iterator_release(it);
} else {
printf("No partitions found.\n");
}
printf("-----------------------\n");
}
#endif // DEBUG_PARTITION_TABLE
#endif // ARCH_ESP32
#if HAS_BUTTON || defined(ARCH_PORTDUINO)
#include "input/ButtonThread.h"
@@ -574,6 +611,7 @@ void setup()
Wire.setSCL(I2C_SCL);
Wire.begin();
#elif defined(I2C_SDA) && !defined(ARCH_RP2040)
LOG_INFO("Starting Bus with (SDA) %d and (SCL) %d: ", I2C_SDA, I2C_SCL);
Wire.begin(I2C_SDA, I2C_SCL);
#elif defined(ARCH_PORTDUINO)
if (portduino_config.i2cdev != "") {
@@ -647,7 +685,11 @@ void setup()
sensor_detected = true;
#endif
}
#ifdef ARCH_ESP32
#ifdef DEBUG_PARTITION_TABLE
printPartitionTable();
#endif
#endif // ARCH_ESP32
#ifdef ARCH_ESP32
// Don't init display if we don't have one or we are waking headless due to a timer event
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) {
@@ -758,11 +800,12 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310U, meshtastic_TelemetrySensorType_QMC6310);
// TODO: Types need to be added meshtastic_TelemetrySensorType_QMC6310N
// scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC6310N, meshtastic_TelemetrySensorType_QMC6310N);
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::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
@@ -1541,8 +1584,9 @@ void setup()
}
#endif
uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client)
uint32_t rebootAtMsec; // If not zero we will reboot at this time (used to reboot shortly after the update completes)
uint32_t shutdownAtMsec; // If not zero we will shutdown at this time (used to shutdown from python or mobile client)
bool suppressRebootBanner; // If true, suppress "Rebooting..." overlay (used for OTA handoff)
// If a thread does something that might need for it to be rescheduled ASAP it can set this flag
// This will suppress the current delay and instead try to run ASAP.
+1
View File
@@ -81,6 +81,7 @@ extern uint32_t timeLastPowered;
extern uint32_t rebootAtMsec;
extern uint32_t shutdownAtMsec;
extern bool suppressRebootBanner;
extern uint32_t serialSinceMsec;
+21 -4
View File
@@ -53,7 +53,7 @@
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
#include <WiFiOTA.h>
#include <MeshtasticOTA.h>
#endif
NodeDB *nodeDB = nullptr;
@@ -756,8 +756,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.display.compass_orientation = COMPASS_ORIENTATION;
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
if (WiFiOTA::isUpdated()) {
WiFiOTA::recoverConfig(&config.network);
if (MeshtasticOTA::isUpdated()) {
MeshtasticOTA::recoverConfig(&config.network);
}
#endif
@@ -823,7 +823,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.nag_timeout = 2;
#endif
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \
defined(ELECROW_ThinkNode_M6)
defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M6)
// Default to PIN_LED2 for external notification output (LED color depends on device variant)
moduleConfig.external_notification.enabled = true;
moduleConfig.external_notification.output = PIN_LED2;
@@ -1264,6 +1264,23 @@ void NodeDB::loadFromDisk()
if ((state != LoadFileResult::LOAD_SUCCESS) || (devicestate.version < DEVICESTATE_MIN_VER)) {
LOG_WARN("Devicestate %d is old or invalid, discard", devicestate.version);
installDefaultDeviceState();
// Attempt recovery of owner fields from our own NodeDB entry if available.
meshtastic_NodeInfoLite *us = getMeshNode(getNodeNum());
if (us && us->has_user) {
LOG_WARN("Restoring owner fields (long_name/short_name/is_licensed/is_unmessagable) from NodeDB for our node 0x%08x",
us->num);
memcpy(owner.long_name, us->user.long_name, sizeof(owner.long_name));
owner.long_name[sizeof(owner.long_name) - 1] = '\0';
memcpy(owner.short_name, us->user.short_name, sizeof(owner.short_name));
owner.short_name[sizeof(owner.short_name) - 1] = '\0';
owner.is_licensed = us->user.is_licensed;
owner.has_is_unmessagable = us->user.has_is_unmessagable;
owner.is_unmessagable = us->user.is_unmessagable;
// Save the recovered owner to device state on disk
saveToDisk(SEGMENT_DEVICESTATE);
}
} else {
LOG_INFO("Loaded saved devicestate version %d", devicestate.version);
}
+5 -3
View File
@@ -77,7 +77,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType {
/* TODO: REPLACE */
meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG = 11,
/* TODO: REPLACE */
meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12
meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12,
/* TODO: REPLACE */
meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13
} meshtastic_AdminMessage_ModuleConfigType;
typedef enum _meshtastic_AdminMessage_BackupLocation {
@@ -323,8 +325,8 @@ extern "C" {
#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1))
#define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG
#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG
#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG+1))
#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG
#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG+1))
#define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH
#define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD
+1 -1
View File
@@ -66,7 +66,7 @@ typedef enum _meshtastic_Config_DeviceConfig_Role {
but should not be given priority over other routers in order to avoid unnecessaraily
consuming hops. */
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE = 11,
/* Description: Treats packets from or to favorited nodes as ROUTER, and all other packets as CLIENT.
/* Description: Treats packets from or to favorited nodes as ROUTER_LATE, and all other packets as CLIENT.
Technical Details: Used for stronger attic/roof nodes to distribute messages more widely
from weaker, indoor, or less-well-positioned nodes. Recommended for users with multiple nodes
where one CLIENT_BASE acts as a more powerful base station, such as an attic/roof node. */
@@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
/* Maximum encoded size of messages (where known) */
/* meshtastic_NodeDatabase_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
#define meshtastic_BackupPreferences_size 2279
#define meshtastic_BackupPreferences_size 2362
#define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1737
#define meshtastic_NodeInfoLite_size 196
+11 -5
View File
@@ -87,6 +87,9 @@ typedef struct _meshtastic_LocalModuleConfig {
/* Paxcounter Config */
bool has_paxcounter;
meshtastic_ModuleConfig_PaxcounterConfig paxcounter;
/* StatusMessage Config */
bool has_statusmessage;
meshtastic_ModuleConfig_StatusMessageConfig statusmessage;
} meshtastic_LocalModuleConfig;
@@ -96,9 +99,9 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default}
#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default}
#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default}
#define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero}
#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero}
#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_LocalConfig_device_tag 1
@@ -124,6 +127,7 @@ extern "C" {
#define meshtastic_LocalModuleConfig_ambient_lighting_tag 12
#define meshtastic_LocalModuleConfig_detection_sensor_tag 13
#define meshtastic_LocalModuleConfig_paxcounter_tag 14
#define meshtastic_LocalModuleConfig_statusmessage_tag 15
/* Struct field encoding specification for nanopb */
#define meshtastic_LocalConfig_FIELDLIST(X, a) \
@@ -161,7 +165,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, remote_hardware, 10) \
X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \
X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \
X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \
X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14)
X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \
X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15)
#define meshtastic_LocalModuleConfig_CALLBACK NULL
#define meshtastic_LocalModuleConfig_DEFAULT NULL
#define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig
@@ -177,6 +182,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14)
#define meshtastic_LocalModuleConfig_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig
#define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig
#define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig
#define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig
extern const pb_msgdesc_t meshtastic_LocalConfig_msg;
extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
@@ -186,9 +192,9 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
#define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size
#define meshtastic_LocalConfig_size 749
#define meshtastic_LocalModuleConfig_size 675
#define meshtastic_LocalModuleConfig_size 758
#ifdef __cplusplus
} /* extern "C" */
@@ -30,6 +30,9 @@ PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2)
PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO)
PB_BIND(meshtastic_StatusMessage, meshtastic_StatusMessage, AUTO)
PB_BIND(meshtastic_MqttClientProxyMessage, meshtastic_MqttClientProxyMessage, 2)
+19
View File
@@ -298,6 +298,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_MESHSTICK_1262 = 121,
/* LilyGo T-Beam 1W */
meshtastic_HardwareModel_TBEAM_1_WATT = 122,
/* LilyGo T5 S3 ePaper Pro (V1 and V2) */
meshtastic_HardwareModel_T5_S3_EPAPER_PRO = 123,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@@ -856,6 +858,11 @@ typedef struct _meshtastic_Waypoint {
uint32_t icon;
} meshtastic_Waypoint;
/* Message for node status */
typedef struct _meshtastic_StatusMessage {
char status[80];
} meshtastic_StatusMessage;
typedef PB_BYTES_ARRAY_T(435) meshtastic_MqttClientProxyMessage_data_t;
/* This message will be proxied over the PhoneAPI for the client to deliver to the MQTT server */
typedef struct _meshtastic_MqttClientProxyMessage {
@@ -1400,6 +1407,7 @@ extern "C" {
#define meshtastic_MeshPacket_priority_ENUMTYPE meshtastic_MeshPacket_Priority
#define meshtastic_MeshPacket_delayed_ENUMTYPE meshtastic_MeshPacket_Delayed
#define meshtastic_MeshPacket_transport_mechanism_ENUMTYPE meshtastic_MeshPacket_TransportMechanism
@@ -1442,6 +1450,7 @@ extern "C" {
#define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}}
#define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0}
#define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0}
#define meshtastic_StatusMessage_init_default {""}
#define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0}
#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN}
#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
@@ -1474,6 +1483,7 @@ extern "C" {
#define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}}
#define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0}
#define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0}
#define meshtastic_StatusMessage_init_zero {""}
#define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0}
#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN}
#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
@@ -1569,6 +1579,7 @@ extern "C" {
#define meshtastic_Waypoint_name_tag 6
#define meshtastic_Waypoint_description_tag 7
#define meshtastic_Waypoint_icon_tag 8
#define meshtastic_StatusMessage_status_tag 1
#define meshtastic_MqttClientProxyMessage_topic_tag 1
#define meshtastic_MqttClientProxyMessage_data_tag 2
#define meshtastic_MqttClientProxyMessage_text_tag 3
@@ -1804,6 +1815,11 @@ X(a, STATIC, SINGULAR, FIXED32, icon, 8)
#define meshtastic_Waypoint_CALLBACK NULL
#define meshtastic_Waypoint_DEFAULT NULL
#define meshtastic_StatusMessage_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, STRING, status, 1)
#define meshtastic_StatusMessage_CALLBACK NULL
#define meshtastic_StatusMessage_DEFAULT NULL
#define meshtastic_MqttClientProxyMessage_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, STRING, topic, 1) \
X(a, STATIC, ONEOF, BYTES, (payload_variant,data,payload_variant.data), 2) \
@@ -2070,6 +2086,7 @@ extern const pb_msgdesc_t meshtastic_Data_msg;
extern const pb_msgdesc_t meshtastic_KeyVerification_msg;
extern const pb_msgdesc_t meshtastic_StoreForwardPlusPlus_msg;
extern const pb_msgdesc_t meshtastic_Waypoint_msg;
extern const pb_msgdesc_t meshtastic_StatusMessage_msg;
extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg;
extern const pb_msgdesc_t meshtastic_MeshPacket_msg;
extern const pb_msgdesc_t meshtastic_NodeInfo_msg;
@@ -2104,6 +2121,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg
#define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg
#define meshtastic_Waypoint_fields &meshtastic_Waypoint_msg
#define meshtastic_StatusMessage_fields &meshtastic_StatusMessage_msg
#define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg
#define meshtastic_MeshPacket_fields &meshtastic_MeshPacket_msg
#define meshtastic_NodeInfo_fields &meshtastic_NodeInfo_msg
@@ -2159,6 +2177,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_QueueStatus_size 23
#define meshtastic_RouteDiscovery_size 256
#define meshtastic_Routing_size 259
#define meshtastic_StatusMessage_size 81
#define meshtastic_StoreForwardPlusPlus_size 377
#define meshtastic_ToRadio_size 504
#define meshtastic_User_size 115
@@ -51,6 +51,9 @@ PB_BIND(meshtastic_ModuleConfig_CannedMessageConfig, meshtastic_ModuleConfig_Can
PB_BIND(meshtastic_ModuleConfig_AmbientLightingConfig, meshtastic_ModuleConfig_AmbientLightingConfig, AUTO)
PB_BIND(meshtastic_ModuleConfig_StatusMessageConfig, meshtastic_ModuleConfig_StatusMessageConfig, AUTO)
PB_BIND(meshtastic_RemoteHardwarePin, meshtastic_RemoteHardwarePin, AUTO)
@@ -409,6 +409,12 @@ typedef struct _meshtastic_ModuleConfig_AmbientLightingConfig {
uint8_t blue;
} meshtastic_ModuleConfig_AmbientLightingConfig;
/* StatusMessage config - Allows setting a status message for a node to periodically rebroadcast */
typedef struct _meshtastic_ModuleConfig_StatusMessageConfig {
/* The actual status string */
char node_status[80];
} meshtastic_ModuleConfig_StatusMessageConfig;
/* A GPIO pin definition for remote hardware module */
typedef struct _meshtastic_RemoteHardwarePin {
/* GPIO Pin number (must match Arduino) */
@@ -460,6 +466,8 @@ typedef struct _meshtastic_ModuleConfig {
meshtastic_ModuleConfig_DetectionSensorConfig detection_sensor;
/* TODO: REPLACE */
meshtastic_ModuleConfig_PaxcounterConfig paxcounter;
/* TODO: REPLACE */
meshtastic_ModuleConfig_StatusMessageConfig statusmessage;
} payload_variant;
} meshtastic_ModuleConfig;
@@ -515,6 +523,7 @@ extern "C" {
#define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_event_press_ENUMTYPE meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar
#define meshtastic_RemoteHardwarePin_type_ENUMTYPE meshtastic_RemoteHardwarePinType
@@ -534,6 +543,7 @@ extern "C" {
#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StatusMessageConfig_init_default {""}
#define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
#define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}}
#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero}
@@ -550,6 +560,7 @@ extern "C" {
#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StatusMessageConfig_init_zero {""}
#define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN}
/* Field tags (for use in manual encoding/decoding) */
@@ -653,6 +664,7 @@ extern "C" {
#define meshtastic_ModuleConfig_AmbientLightingConfig_red_tag 3
#define meshtastic_ModuleConfig_AmbientLightingConfig_green_tag 4
#define meshtastic_ModuleConfig_AmbientLightingConfig_blue_tag 5
#define meshtastic_ModuleConfig_StatusMessageConfig_node_status_tag 1
#define meshtastic_RemoteHardwarePin_gpio_pin_tag 1
#define meshtastic_RemoteHardwarePin_name_tag 2
#define meshtastic_RemoteHardwarePin_type_tag 3
@@ -672,6 +684,7 @@ extern "C" {
#define meshtastic_ModuleConfig_ambient_lighting_tag 11
#define meshtastic_ModuleConfig_detection_sensor_tag 12
#define meshtastic_ModuleConfig_paxcounter_tag 13
#define meshtastic_ModuleConfig_statusmessage_tag 14
/* Struct field encoding specification for nanopb */
#define meshtastic_ModuleConfig_FIELDLIST(X, a) \
@@ -687,7 +700,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,remote_hardware,payload_vari
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,neighbor_info,payload_variant.neighbor_info), 10) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_variant.ambient_lighting), 11) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13)
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14)
#define meshtastic_ModuleConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_DEFAULT NULL
#define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig
@@ -703,6 +717,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.p
#define meshtastic_ModuleConfig_payload_variant_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig
#define meshtastic_ModuleConfig_payload_variant_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig
#define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig
#define meshtastic_ModuleConfig_payload_variant_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig
#define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
@@ -865,6 +880,11 @@ X(a, STATIC, SINGULAR, UINT32, blue, 5)
#define meshtastic_ModuleConfig_AmbientLightingConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_AmbientLightingConfig_DEFAULT NULL
#define meshtastic_ModuleConfig_StatusMessageConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, STRING, node_status, 1)
#define meshtastic_ModuleConfig_StatusMessageConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_StatusMessageConfig_DEFAULT NULL
#define meshtastic_RemoteHardwarePin_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, gpio_pin, 1) \
X(a, STATIC, SINGULAR, STRING, name, 2) \
@@ -887,6 +907,7 @@ extern const pb_msgdesc_t meshtastic_ModuleConfig_RangeTestConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_TelemetryConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_CannedMessageConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_AmbientLightingConfig_msg;
extern const pb_msgdesc_t meshtastic_ModuleConfig_StatusMessageConfig_msg;
extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
@@ -905,6 +926,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_TelemetryConfig_fields &meshtastic_ModuleConfig_TelemetryConfig_msg
#define meshtastic_ModuleConfig_CannedMessageConfig_fields &meshtastic_ModuleConfig_CannedMessageConfig_msg
#define meshtastic_ModuleConfig_AmbientLightingConfig_fields &meshtastic_ModuleConfig_AmbientLightingConfig_msg
#define meshtastic_ModuleConfig_StatusMessageConfig_fields &meshtastic_ModuleConfig_StatusMessageConfig_msg
#define meshtastic_RemoteHardwarePin_fields &meshtastic_RemoteHardwarePin_msg
/* Maximum encoded size of messages (where known) */
@@ -921,6 +943,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_RangeTestConfig_size 12
#define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
#define meshtastic_ModuleConfig_SerialConfig_size 28
#define meshtastic_ModuleConfig_StatusMessageConfig_size 81
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24
#define meshtastic_ModuleConfig_TelemetryConfig_size 50
#define meshtastic_ModuleConfig_size 227
@@ -91,6 +91,11 @@ typedef enum _meshtastic_PortNum {
This module is specifically for Native Linux nodes, and provides a Git-style
chain of messages. */
meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP = 35,
/* Node Status module
ENCODING: protobuf
This module allows setting an extra string of status for a node.
Broadcasts on change and on a timer, possibly once a day. */
meshtastic_PortNum_NODE_STATUS_APP = 36,
/* Provides a hardware serial interface to send and receive from the Meshtastic network.
Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic
network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network.
+8 -4
View File
@@ -361,6 +361,8 @@ typedef struct _meshtastic_LocalStats {
uint32_t heap_free_bytes;
/* Number of packets that were dropped because the transmit queue was full. */
uint16_t num_tx_dropped;
/* Noise floor value measured in dBm */
int32_t noise_floor;
} meshtastic_LocalStats;
/* Health telemetry metrics */
@@ -458,7 +460,7 @@ extern "C" {
#define meshtastic_EnvironmentMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_PowerMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_default {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_LocalStats_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_default {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_default {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
@@ -467,7 +469,7 @@ extern "C" {
#define meshtastic_EnvironmentMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_PowerMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_AirQualityMetrics_init_zero {false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0, false, 0}
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_LocalStats_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_HealthMetrics_init_zero {false, 0, false, 0, false, 0}
#define meshtastic_HostMetrics_init_zero {0, 0, 0, false, 0, false, 0, 0, 0, 0, false, ""}
#define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}}
@@ -556,6 +558,7 @@ extern "C" {
#define meshtastic_LocalStats_heap_total_bytes_tag 12
#define meshtastic_LocalStats_heap_free_bytes_tag 13
#define meshtastic_LocalStats_num_tx_dropped_tag 14
#define meshtastic_LocalStats_noise_floor_tag 15
#define meshtastic_HealthMetrics_heart_bpm_tag 1
#define meshtastic_HealthMetrics_spO2_tag 2
#define meshtastic_HealthMetrics_temperature_tag 3
@@ -678,7 +681,8 @@ X(a, STATIC, SINGULAR, UINT32, num_tx_relay, 10) \
X(a, STATIC, SINGULAR, UINT32, num_tx_relay_canceled, 11) \
X(a, STATIC, SINGULAR, UINT32, heap_total_bytes, 12) \
X(a, STATIC, SINGULAR, UINT32, heap_free_bytes, 13) \
X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14)
X(a, STATIC, SINGULAR, UINT32, num_tx_dropped, 14) \
X(a, STATIC, SINGULAR, INT32, noise_floor, 15)
#define meshtastic_LocalStats_CALLBACK NULL
#define meshtastic_LocalStats_DEFAULT NULL
@@ -755,7 +759,7 @@ extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
#define meshtastic_EnvironmentMetrics_size 113
#define meshtastic_HealthMetrics_size 11
#define meshtastic_HostMetrics_size 264
#define meshtastic_LocalStats_size 76
#define meshtastic_LocalStats_size 87
#define meshtastic_Nau7802Config_size 16
#define meshtastic_PowerMetrics_size 81
#define meshtastic_Telemetry_size 272
+2 -2
View File
@@ -173,7 +173,7 @@ void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
if (req->getMethod() == "OPTIONS") {
res->setStatusCode(204); // Success with no content
// res->print(""); @todo remove
res->print("");
return;
}
@@ -223,7 +223,7 @@ void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
if (req->getMethod() == "OPTIONS") {
res->setStatusCode(204); // Success with no content
// res->print(""); @todo remove
res->print("");
return;
}
+71 -21
View File
@@ -9,11 +9,8 @@
#include "meshUtils.h"
#include <FSCommon.h>
#include <ctype.h> // for better whitespace handling
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_BLUETOOTH
#include "BleOta.h"
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
#include "WiFiOTA.h"
#include "MeshtasticOTA.h"
#endif
#include "Router.h"
#include "configuration.h"
@@ -236,26 +233,51 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
reboot(r->reboot_seconds);
break;
}
case meshtastic_AdminMessage_reboot_ota_seconds_tag: {
int32_t s = r->reboot_ota_seconds;
case meshtastic_AdminMessage_ota_request_tag: {
#if defined(ARCH_ESP32)
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
if (!BleOta::getOtaAppVersion().isEmpty()) {
LOG_INFO("OTA Requested");
if (r->ota_request.ota_hash.size != 32) {
suppressRebootBanner = true;
sendWarningAndLog("Cannot start OTA: Invalid `ota_hash` provided.");
break;
}
meshtastic_OTAMode mode = r->ota_request.reboot_ota_mode;
const char *mode_name = (mode == METHOD_OTA_BLE ? "BLE" : "WiFi");
// Check that we have an OTA partition
const esp_partition_t *part = MeshtasticOTA::getAppPartition();
if (part == NULL) {
suppressRebootBanner = true;
sendWarningAndLog("Cannot start OTA: Cannot find OTA Loader partition.");
break;
}
static esp_app_desc_t app_desc;
if (!MeshtasticOTA::getAppDesc(part, &app_desc)) {
suppressRebootBanner = true;
sendWarningAndLog("Cannot start OTA: Device does have a valid OTA Loader.");
break;
}
if (!MeshtasticOTA::checkOTACapability(&app_desc, mode)) {
suppressRebootBanner = true;
sendWarningAndLog("OTA Loader does not support %s", mode_name);
break;
}
if (MeshtasticOTA::trySwitchToOTA()) {
suppressRebootBanner = true;
if (screen)
screen->startFirmwareUpdateScreen();
BleOta::switchToOtaApp();
LOG_INFO("Rebooting to BLE OTA");
MeshtasticOTA::saveConfig(&config.network, mode, r->ota_request.ota_hash.bytes);
sendWarningAndLog("Rebooting to %s OTA", mode_name);
} else {
sendWarningAndLog("Unable to switch to the OTA partition.");
}
#endif
#if !MESHTASTIC_EXCLUDE_WIFI
if (WiFiOTA::trySwitchToOTA()) {
if (screen)
screen->startFirmwareUpdateScreen();
WiFiOTA::saveConfig(&config.network);
LOG_INFO("Rebooting to WiFi OTA");
}
#endif
#endif
int s = 1; // Reboot in 1 second, hard coded
LOG_INFO("Reboot in %d seconds", s);
rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000);
break;
@@ -1474,15 +1496,43 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent
#endif
}
void AdminModule::sendWarning(const char *message)
void AdminModule::sendWarning(const char *format, ...)
{
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
if (!cn)
return;
cn->level = meshtastic_LogRecord_Level_WARNING;
cn->time = getValidTime(RTCQualityFromNet);
strncpy(cn->message, message, sizeof(cn->message));
va_list args;
va_start(args, format);
// Format the arguments directly into the notification object
vsnprintf(cn->message, sizeof(cn->message), format, args);
va_end(args);
service->sendClientNotification(cn);
}
void AdminModule::sendWarningAndLog(const char *format, ...)
{
// We need a temporary buffer to hold the formatted text so we can log it
// Using 250 bytes as a safe upper limit for typical text notifications
char buf[250];
va_list args;
va_start(args, format);
vsnprintf(buf, sizeof(buf), format, args);
va_end(args);
LOG_WARN(buf);
// 2. Call sendWarning
// SECURITY NOTE: We pass "%s", buf instead of just 'buf'.
// If 'buf' contained a % symbol (e.g. "Battery 50%"), passing it directly
// would crash sendWarning. "%s" treats it purely as text.
sendWarning("%s", buf);
}
void disableBluetooth()
{
#if HAS_BLUETOOTH
+6 -3
View File
@@ -1,7 +1,9 @@
#include <sys/types.h>
#pragma once
#ifdef ESP_PLATFORM
#include <esp_ota_ops.h>
#endif
#include "ProtobufModule.h"
#include <sys/types.h>
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#endif
@@ -71,7 +73,8 @@ class AdminModule : public ProtobufModule<meshtastic_AdminMessage>, public Obser
bool messageIsResponse(const meshtastic_AdminMessage *r);
bool messageIsRequest(const meshtastic_AdminMessage *r);
void sendWarning(const char *message);
void sendWarning(const char *format, ...) __attribute__((format(printf, 2, 3)));
void sendWarningAndLog(const char *format, ...) __attribute__((format(printf, 2, 3)));
};
static constexpr const char *licensedModeMessage =
+10 -10
View File
@@ -460,12 +460,15 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
bool mutedNode = false;
if (sender) {
mutedNode = (sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK);
}
meshtastic_Channel ch = channels.getByIndex(mp.channel ? mp.channel : channels.getPrimaryIndex());
// If we receive a broadcast message, apply channel mute setting
// If we receive a direct message and the receipent is us, apply DM mute setting
// Else we just handle it as not muted.
const bool directToUs = !isBroadcast(mp.to) && isToUs(&mp);
bool is_muted = directToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0))
: (ch.settings.has_module_settings && ch.settings.module_settings.is_muted);
if (moduleConfig.external_notification.alert_bell) {
if (containsBell) {
LOG_INFO("externalNotificationModule - Notification Bell");
@@ -516,8 +519,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
if (moduleConfig.external_notification.alert_message && !mutedNode &&
(!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
if (moduleConfig.external_notification.alert_message && !is_muted) {
LOG_INFO("externalNotificationModule - Notification Module");
isNagging = true;
setExternalState(0, true);
@@ -528,8 +530,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
if (moduleConfig.external_notification.alert_message_vibra && !mutedNode &&
(!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
if (moduleConfig.external_notification.alert_message_vibra && !is_muted) {
LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
isNagging = true;
setExternalState(1, true);
@@ -540,8 +541,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
}
}
if (moduleConfig.external_notification.alert_message_buzzer && !mutedNode &&
(!ch.settings.has_module_settings || !ch.settings.module_settings.is_muted)) {
if (moduleConfig.external_notification.alert_message_buzzer && !is_muted) {
LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DIRECT_MSG_ONLY ||
(!isBroadcast(mp.to) && isToUs(&mp))) {
+3 -3
View File
@@ -252,9 +252,9 @@ void setupModules()
(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
new EnvironmentTelemetryModule();
}
#if __has_include("Adafruit_PM25AQI.h")
if (moduleConfig.has_telemetry && moduleConfig.telemetry.air_quality_enabled &&
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first > 0) {
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
if (moduleConfig.has_telemetry &&
(moduleConfig.telemetry.air_quality_enabled || moduleConfig.telemetry.air_quality_screen_enabled)) {
new AirQualityTelemetryModule();
}
#endif
+12 -6
View File
@@ -64,8 +64,9 @@ SerialModule *serialModule;
SerialModuleRadio *serialModuleRadio;
#if defined(TTGO_T_ECHO) || defined(TTGO_T_ECHO_PLUS) || defined(CANARYONE) || defined(MESHLINK) || \
defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || \
defined(ELECROW_ThinkNode_M3) || defined(MUZI_BASE)
defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M4) || defined(ELECROW_ThinkNode_M5) || \
defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) || defined(ELECROW_ThinkNode_M3) || defined(MUZI_BASE)
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial")
{
api_type = TYPE_SERIAL;
@@ -205,8 +206,9 @@ int32_t SerialModule::runOnce()
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
}
#elif !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && \
!defined(MUZI_BASE)
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \
!defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
#ifdef ARCH_RP2040
Serial2.setFIFOSize(RX_BUFFER);
@@ -263,7 +265,8 @@ int32_t SerialModule::runOnce()
}
#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(MESHLINK) && \
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
!defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M4) && \
!defined(ELECROW_ThinkNode_M5) && !defined(MUZI_BASE)
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
processWXSerial();
@@ -539,7 +542,10 @@ void SerialModule::processWXSerial()
{
#if !defined(TTGO_T_ECHO) && !defined(TTGO_T_ECHO_PLUS) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && \
!defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \
!defined(ELECROW_ThinkNode_M3) && !defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) && !defined(MUZI_BASE)
!defined(ELECROW_ThinkNode_M3) && \
!defined(ELECROW_ThinkNode_M4) && \
!defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL) && !defined(MUZI_BASE)
static unsigned int lastAveraged = 0;
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
static double dir_sum_sin = 0;
+40 -3
View File
@@ -13,6 +13,8 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule")
{
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
powerStatusObserver.observe(&powerStatus->onNewStatus);
if (inputBroker)
inputObserver.observe(inputBroker);
}
int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
@@ -48,9 +50,10 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
break;
}
case meshtastic::BluetoothStatus::ConnectionState::CONNECTED: {
ble_state = connected;
PAIRING_LED_starttime = millis();
break;
if (ble_state != connected) {
ble_state = connected;
PAIRING_LED_starttime = millis();
}
}
}
@@ -60,6 +63,12 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
return 0;
};
int StatusLEDModule::handleInputEvent(const InputEvent *event)
{
lastUserbuttonTime = millis();
return 0;
}
int32_t StatusLEDModule::runOnce()
{
my_interval = 1000;
@@ -103,6 +112,21 @@ int32_t StatusLEDModule::runOnce()
PAIRING_LED_state = LED_STATE_ON;
}
bool chargeIndicatorLED1 = LED_STATE_OFF;
bool chargeIndicatorLED2 = LED_STATE_OFF;
bool chargeIndicatorLED3 = LED_STATE_OFF;
bool chargeIndicatorLED4 = LED_STATE_OFF;
if (lastUserbuttonTime + 10 * 1000 > millis() || CHARGE_LED_state == LED_STATE_ON) {
// should this be off at very low percentages?
chargeIndicatorLED1 = LED_STATE_ON;
if (powerStatus && powerStatus->getBatteryChargePercent() >= 25)
chargeIndicatorLED2 = LED_STATE_ON;
if (powerStatus && powerStatus->getBatteryChargePercent() >= 50)
chargeIndicatorLED3 = LED_STATE_ON;
if (powerStatus && powerStatus->getBatteryChargePercent() >= 75)
chargeIndicatorLED4 = LED_STATE_ON;
}
#ifdef LED_CHARGE
digitalWrite(LED_CHARGE, CHARGE_LED_state);
#endif
@@ -111,5 +135,18 @@ int32_t StatusLEDModule::runOnce()
digitalWrite(LED_PAIRING, PAIRING_LED_state);
#endif
#ifdef Battery_LED_1
digitalWrite(Battery_LED_1, chargeIndicatorLED1);
#endif
#ifdef Battery_LED_2
digitalWrite(Battery_LED_2, chargeIndicatorLED2);
#endif
#ifdef Battery_LED_3
digitalWrite(Battery_LED_3, chargeIndicatorLED3);
#endif
#ifdef Battery_LED_4
digitalWrite(Battery_LED_4, chargeIndicatorLED4);
#endif
return (my_interval);
}
+6
View File
@@ -5,6 +5,7 @@
#include "PowerStatus.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#include "input/InputBroker.h"
#include <Arduino.h>
#include <functional>
@@ -17,6 +18,8 @@ class StatusLEDModule : private concurrency::OSThread
int handleStatusUpdate(const meshtastic::Status *);
int handleInputEvent(const InputEvent *arg);
protected:
unsigned int my_interval = 1000; // interval in millisconds
virtual int32_t runOnce() override;
@@ -25,12 +28,15 @@ class StatusLEDModule : private concurrency::OSThread
CallbackObserver<StatusLEDModule, const meshtastic::Status *>(this, &StatusLEDModule::handleStatusUpdate);
CallbackObserver<StatusLEDModule, const meshtastic::Status *> powerStatusObserver =
CallbackObserver<StatusLEDModule, const meshtastic::Status *>(this, &StatusLEDModule::handleStatusUpdate);
CallbackObserver<StatusLEDModule, const InputEvent *> inputObserver =
CallbackObserver<StatusLEDModule, const InputEvent *>(this, &StatusLEDModule::handleInputEvent);
private:
bool CHARGE_LED_state = LED_STATE_OFF;
bool PAIRING_LED_state = LED_STATE_OFF;
uint32_t PAIRING_LED_starttime = 0;
uint32_t lastUserbuttonTime = 0;
uint32_t POWER_LED_starttime = 0;
bool doing_fast_blink = false;
+224 -105
View File
@@ -1,6 +1,6 @@
#include "configuration.h"
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include("Adafruit_PM25AQI.h")
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "AirQualityTelemetry.h"
@@ -10,27 +10,54 @@
#include "PowerFSM.h"
#include "RTC.h"
#include "Router.h"
#include "detect/ScanI2CTwoWire.h"
#include "Sensor/AddI2CSensorTemplate.h"
#include "UnitConversions.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/images.h"
#include "main.h"
#include "sleep.h"
#include <Throttle.h>
#ifndef PMSA003I_WARMUP_MS
// from the PMSA003I datasheet:
// "Stable data should be got at least 30 seconds after the sensor wakeup
// from the sleep mode because of the fan’s performance."
#define PMSA003I_WARMUP_MS 30000
#endif
// Sensors
#include "Sensor/PMSA003ISensor.h"
int32_t AirQualityTelemetryModule::runOnce()
void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
{
if (!moduleConfig.telemetry.air_quality_enabled && !AIR_QUALITY_TELEMETRY_MODULE_ENABLE) {
return;
}
LOG_INFO("Air Quality Telemetry adding I2C devices...");
/*
Uncomment the preferences below if you want to use the module
without having to configure it from the PythonAPI or WebUI.
Note: this was previously on runOnce, which didnt take effect
as other modules already had already been initialized (screen)
*/
// moduleConfig.telemetry.air_quality_enabled = 1;
// moduleConfig.telemetry.air_quality_screen_enabled = 1;
// moduleConfig.telemetry.air_quality_interval = 15;
if (!(moduleConfig.telemetry.air_quality_enabled)) {
// order by priority of metrics/values (low top, high bottom)
addSensor<PMSA003ISensor>(i2cScanner, ScanI2C::DeviceType::PMSA003I);
}
int32_t AirQualityTelemetryModule::runOnce()
{
if (sleepOnNextExecution == true) {
sleepOnNextExecution = false;
uint32_t nightyNightMs = Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs);
LOG_DEBUG("Sleeping for %ims, then awaking to send metrics again.", nightyNightMs);
doDeepSleep(nightyNightMs, true, false);
}
uint32_t result = UINT32_MAX;
if (!(moduleConfig.telemetry.air_quality_enabled || moduleConfig.telemetry.air_quality_screen_enabled ||
AIR_QUALITY_TELEMETRY_MODULE_ENABLE)) {
// If this module is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it
return disable();
}
@@ -42,82 +69,152 @@ int32_t AirQualityTelemetryModule::runOnce()
if (moduleConfig.telemetry.air_quality_enabled) {
LOG_INFO("Air quality Telemetry: init");
#ifdef PMSA003I_ENABLE_PIN
// put the sensor to sleep on startup
pinMode(PMSA003I_ENABLE_PIN, OUTPUT);
digitalWrite(PMSA003I_ENABLE_PIN, LOW);
#endif /* PMSA003I_ENABLE_PIN */
if (!aqi.begin_I2C()) {
#ifndef I2C_NO_RESCAN
LOG_WARN("Could not establish i2c connection to AQI sensor. Rescan");
// rescan for late arriving sensors. AQI Module starts about 10 seconds into the boot so this is plenty.
uint8_t i2caddr_scan[] = {PMSA0031_ADDR};
uint8_t i2caddr_asize = 1;
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
#if defined(I2C_SDA1)
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize);
#endif
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize);
auto found = i2cScanner->find(ScanI2C::DeviceType::PMSA0031);
if (found.type != ScanI2C::DeviceType::NONE) {
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].first = found.address.address;
nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_PMSA003I].second =
i2cScanner->fetchI2CBus(found.address);
return setStartDelay();
}
#endif
return disable();
// check if we have at least one sensor
if (!sensors.empty()) {
result = DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
}
return setStartDelay();
}
return disable();
// it's possible to have this module enabled, only for displaying values on the screen.
// therefore, we should only enable the sensor loop if measurement is also enabled
return result == UINT32_MAX ? disable() : setStartDelay();
} else {
// if we somehow got to a second run of this module with measurement disabled, then just wait forever
if (!moduleConfig.telemetry.air_quality_enabled)
return disable();
switch (state) {
#ifdef PMSA003I_ENABLE_PIN
case State::IDLE:
// sensor is in standby; fire it up and sleep
LOG_DEBUG("runOnce(): state = idle");
digitalWrite(PMSA003I_ENABLE_PIN, HIGH);
state = State::ACTIVE;
return PMSA003I_WARMUP_MS;
#endif /* PMSA003I_ENABLE_PIN */
case State::ACTIVE:
// sensor is already warmed up; grab telemetry and send it
LOG_DEBUG("runOnce(): state = active");
if (((lastSentToMesh == 0) ||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
sendTelemetry();
lastSentToMesh = millis();
} else if (service->isToPhoneQueueEmpty()) {
// Just send to phone when it's not our time to send to mesh yet
// Only send while queue is empty (phone assumed connected)
sendTelemetry(NODENUM_BROADCAST, true);
}
#ifdef PMSA003I_ENABLE_PIN
// put sensor back to sleep
digitalWrite(PMSA003I_ENABLE_PIN, LOW);
state = State::IDLE;
#endif /* PMSA003I_ENABLE_PIN */
return sendToPhoneIntervalMs;
default:
if (!moduleConfig.telemetry.air_quality_enabled && !AIR_QUALITY_TELEMETRY_MODULE_ENABLE) {
return disable();
}
// Wake up the sensors that need it
LOG_INFO("Waking up sensors");
for (TelemetrySensor *sensor : sensors) {
if (!sensor->isActive()) {
return sensor->wakeUp();
}
}
if (((lastSentToMesh == 0) ||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
airTime->isTxAllowedAirUtil()) {
sendTelemetry();
lastSentToMesh = millis();
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
(service->isToPhoneQueueEmpty())) {
// Just send to phone when it's not our time to send to mesh yet
// Only send while queue is empty (phone assumed connected)
sendTelemetry(NODENUM_BROADCAST, true);
lastSentToPhone = millis();
}
// Send to sleep sensors that consume power
LOG_INFO("Sending sensors to sleep");
for (TelemetrySensor *sensor : sensors) {
sensor->sleep();
}
}
return min(sendToPhoneIntervalMs, result);
}
bool AirQualityTelemetryModule::wantUIFrame()
{
return moduleConfig.telemetry.air_quality_screen_enabled;
}
#if HAS_SCREEN
void AirQualityTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
// === Setup display ===
display->clear();
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
int line = 1;
// === Set Title
const char *titleStr = (graphics::currentResolution == graphics::ScreenResolution::High) ? "Air Quality" : "AQ.";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
// === Row spacing setup ===
const int rowHeight = FONT_HEIGHT_SMALL - 4;
int currentY = graphics::getTextPositions(display)[line++];
// === Show "No Telemetry" if no data available ===
if (!lastMeasurementPacket) {
display->drawString(x, currentY, "No Telemetry");
return;
}
// Decode the telemetry message from the latest received packet
const meshtastic_Data &p = lastMeasurementPacket->decoded;
meshtastic_Telemetry telemetry;
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &telemetry)) {
display->drawString(x, currentY, "No Telemetry");
return;
}
const auto &m = telemetry.variant.air_quality_metrics;
// Check if any telemetry field has valid data
bool hasAny = m.has_pm10_standard || m.has_pm25_standard || m.has_pm100_standard || m.has_pm10_environmental ||
m.has_pm25_environmental || m.has_pm100_environmental;
if (!hasAny) {
display->drawString(x, currentY, "No Telemetry");
return;
}
// === First line: Show sender name + time since received (left), and first metric (right) ===
const char *sender = getSenderShortName(*lastMeasurementPacket);
uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket);
String agoStr = (agoSecs > 864000) ? "?"
: (agoSecs > 3600) ? String(agoSecs / 3600) + "h"
: (agoSecs > 60) ? String(agoSecs / 60) + "m"
: String(agoSecs) + "s";
String leftStr = String(sender) + " (" + agoStr + ")";
display->drawString(x, currentY, leftStr); // Left side: who and when
// === Collect sensor readings as label strings (no icons) ===
std::vector<String> entries;
if (m.has_pm10_standard)
entries.push_back("PM1: " + String(m.pm10_standard) + "ug/m3");
if (m.has_pm25_standard)
entries.push_back("PM2.5: " + String(m.pm25_standard) + "ug/m3");
if (m.has_pm100_standard)
entries.push_back("PM10: " + String(m.pm100_standard) + "ug/m3");
// === Show first available metric on top-right of first line ===
if (!entries.empty()) {
String valueStr = entries.front();
int rightX = SCREEN_WIDTH - display->getStringWidth(valueStr);
display->drawString(rightX, currentY, valueStr);
entries.erase(entries.begin()); // Remove from queue
}
// === Advance to next line for remaining telemetry entries ===
currentY += rowHeight;
// === Draw remaining entries in 2-column format (left and right) ===
for (size_t i = 0; i < entries.size(); i += 2) {
// Left column
display->drawString(x, currentY, entries[i]);
// Right column if it exists
if (i + 1 < entries.size()) {
int rightX = SCREEN_WIDTH / 2;
display->drawString(rightX, currentY, entries[i + 1]);
}
currentY += rowHeight;
}
graphics::drawCommonFooter(display, x, y);
}
#endif
bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
{
if (t->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) {
@@ -144,35 +241,21 @@ bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPack
bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m)
{
if (!aqi.read(&data)) {
LOG_WARN("Skip send measurements. Could not read AQIn");
return false;
}
bool valid = true;
bool hasSensor = false;
m->time = getTime();
m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
m->variant.air_quality_metrics.has_pm10_standard = true;
m->variant.air_quality_metrics.pm10_standard = data.pm10_standard;
m->variant.air_quality_metrics.has_pm25_standard = true;
m->variant.air_quality_metrics.pm25_standard = data.pm25_standard;
m->variant.air_quality_metrics.has_pm100_standard = true;
m->variant.air_quality_metrics.pm100_standard = data.pm100_standard;
m->variant.air_quality_metrics = meshtastic_AirQualityMetrics_init_zero;
m->variant.air_quality_metrics.has_pm10_environmental = true;
m->variant.air_quality_metrics.pm10_environmental = data.pm10_env;
m->variant.air_quality_metrics.has_pm25_environmental = true;
m->variant.air_quality_metrics.pm25_environmental = data.pm25_env;
m->variant.air_quality_metrics.has_pm100_environmental = true;
m->variant.air_quality_metrics.pm100_environmental = data.pm100_env;
// TODO - Should we check for sensor state here?
// If a sensor is sleeping, we should know and check to wake it up
for (TelemetrySensor *sensor : sensors) {
LOG_INFO("Reading AQ sensors");
valid = valid && sensor->getMetrics(m);
hasSensor = true;
}
LOG_INFO("Send: PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i", m->variant.air_quality_metrics.pm10_standard,
m->variant.air_quality_metrics.pm25_standard, m->variant.air_quality_metrics.pm100_standard);
LOG_INFO(" | PM1.0(Environmental)=%i, PM2.5(Environmental)=%i, PM10.0(Environmental)=%i",
m->variant.air_quality_metrics.pm10_environmental, m->variant.air_quality_metrics.pm25_environmental,
m->variant.air_quality_metrics.pm100_environmental);
return true;
return valid && hasSensor;
}
meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply()
@@ -206,7 +289,15 @@ meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply()
bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
{
meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
m.time = getTime();
if (getAirQualityTelemetry(&m)) {
LOG_INFO("Send: pm10_standard=%u, pm25_standard=%u, pm100_standard=%u, \
pm10_environmental=%u, pm25_environmental=%u, pm100_environmental=%u",
m.variant.air_quality_metrics.pm10_standard, m.variant.air_quality_metrics.pm25_standard,
m.variant.air_quality_metrics.pm100_standard, m.variant.air_quality_metrics.pm10_environmental,
m.variant.air_quality_metrics.pm25_environmental, m.variant.air_quality_metrics.pm100_environmental);
meshtastic_MeshPacket *p = allocDataProtobuf(m);
p->to = dest;
p->decoded.want_response = false;
@@ -221,16 +312,44 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
lastMeasurementPacket = packetPool.allocCopy(*p);
if (phoneOnly) {
LOG_INFO("Send packet to phone");
LOG_INFO("Sending packet to phone");
service->sendToPhone(p);
} else {
LOG_INFO("Send packet to mesh");
LOG_INFO("Sending packet to mesh");
service->sendToMesh(p, RX_SRC_LOCAL, true);
if (config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR && config.power.is_power_saving) {
meshtastic_ClientNotification *notification = clientNotificationPool.allocZeroed();
notification->level = meshtastic_LogRecord_Level_INFO;
notification->time = getValidTime(RTCQualityFromNet);
sprintf(notification->message, "Sending telemetry and sleeping for %us interval in a moment",
Default::getConfiguredOrDefaultMs(moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs) /
1000U);
service->sendClientNotification(notification);
sleepOnNextExecution = true;
LOG_DEBUG("Start next execution in 5s, then sleep");
setIntervalFromNow(FIVE_SECONDS_MS);
}
}
return true;
}
return false;
}
AdminMessageHandleResult AirQualityTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response)
{
AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED;
for (TelemetrySensor *sensor : sensors) {
result = sensor->handleAdminMessage(mp, request, response);
if (result != AdminMessageHandleResult::NOT_HANDLED)
return result;
}
return result;
}
#endif
+26 -22
View File
@@ -1,14 +1,23 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include("Adafruit_PM25AQI.h")
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
#pragma once
#ifndef AIR_QUALITY_TELEMETRY_MODULE_ENABLE
#define AIR_QUALITY_TELEMETRY_MODULE_ENABLE 0
#endif
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "Adafruit_PM25AQI.h"
#include "NodeDB.h"
#include "ProtobufModule.h"
#include "detect/ScanI2CConsumer.h"
#include <OLEDDisplay.h>
#include <OLEDDisplayUi.h>
class AirQualityTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry>
class AirQualityTelemetryModule : private concurrency::OSThread,
public ScanI2CConsumer,
public ProtobufModule<meshtastic_Telemetry>
{
CallbackObserver<AirQualityTelemetryModule, const meshtastic::Status *> nodeStatusObserver =
CallbackObserver<AirQualityTelemetryModule, const meshtastic::Status *>(this,
@@ -16,22 +25,19 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf
public:
AirQualityTelemetryModule()
: concurrency::OSThread("AirQualityTelemetry"),
: concurrency::OSThread("AirQualityTelemetry"), ScanI2CConsumer(),
ProtobufModule("AirQualityTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
{
lastMeasurementPacket = nullptr;
setIntervalFromNow(10 * 1000);
aqi = Adafruit_PM25AQI();
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
#ifdef PMSA003I_ENABLE_PIN
// the PMSA003I sensor uses about 300mW on its own; support powering it off when it's not actively taking
// a reading
state = State::IDLE;
#else
state = State::ACTIVE;
#endif
setIntervalFromNow(10 * 1000);
}
virtual bool wantUIFrame() override;
#if !HAS_SCREEN
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
#else
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
#endif
protected:
/** Called to handle a particular incoming message
@@ -49,19 +55,17 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf
*/
bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
private:
enum State {
IDLE = 0,
ACTIVE = 1,
};
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
meshtastic_AdminMessage *request,
meshtastic_AdminMessage *response) override;
void i2cScanFinished(ScanI2C *i2cScanner);
State state;
Adafruit_PM25AQI aqi;
PM25_AQI_Data data = {0};
private:
bool firstTime = true;
meshtastic_MeshPacket *lastMeasurementPacket;
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
uint32_t lastSentToPhone = 0;
};
#endif
+3 -32
View File
@@ -53,7 +53,7 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#include "Sensor/LTR390UVSensor.h"
#endif
#if __has_include(MESHTASTIC_BME680_HEADER)
#if __has_include(<bsec2.h>) || __has_include(<Adafruit_BME680.h>)
#include "Sensor/BME680Sensor.h"
#endif
@@ -141,37 +141,10 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
#include "Sensor/AddI2CSensorTemplate.h"
#include "graphics/ScreenFonts.h"
#include <Throttle.h>
#include <forward_list>
static std::forward_list<TelemetrySensor *> sensors;
template <typename T> void addSensor(ScanI2C *i2cScanner, ScanI2C::DeviceType type)
{
ScanI2C::FoundDevice dev = i2cScanner->find(type);
if (dev.type != ScanI2C::DeviceType::NONE || type == ScanI2C::DeviceType::NONE) {
TelemetrySensor *sensor = new T();
#if WIRE_INTERFACES_COUNT > 1
TwoWire *bus = ScanI2CTwoWire::fetchI2CBus(dev.address);
if (dev.address.port != ScanI2C::I2CPort::WIRE1 && sensor->onlyWire1()) {
// This sensor only works on Wire (Wire1 is not supported)
delete sensor;
return;
}
#else
TwoWire *bus = &Wire;
#endif
if (sensor->initDevice(bus, &dev)) {
sensors.push_front(sensor);
return;
}
// destroy sensor
delete sensor;
}
}
void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
{
if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
@@ -214,7 +187,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include(<Adafruit_LTR390.h>)
addSensor<LTR390UVSensor>(i2cScanner, ScanI2C::DeviceType::LTR390UV);
#endif
#if __has_include(MESHTASTIC_BME680_HEADER)
#if __has_include(<bsec2.h>) || __has_include(<Adafruit_BME680.h>)
addSensor<BME680Sensor>(i2cScanner, ScanI2C::DeviceType::BME_680);
#endif
#if __has_include(<Adafruit_BMP280.h>)
@@ -642,8 +615,6 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
LOG_INFO("Send: soil_temperature=%f, soil_moisture=%u", m.variant.environment_metrics.soil_temperature,
m.variant.environment_metrics.soil_moisture);
sensor_read_error_count = 0;
meshtastic_MeshPacket *p = allocDataProtobuf(m);
p->to = dest;
p->decoded.want_response = false;
@@ -67,7 +67,6 @@ class EnvironmentTelemetryModule : private concurrency::OSThread,
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
uint32_t lastSentToPhone = 0;
uint32_t sensor_read_error_count = 0;
};
#endif
@@ -0,0 +1,34 @@
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
#include "TelemetrySensor.h"
#include "detect/ScanI2C.h"
#include "detect/ScanI2CTwoWire.h"
#include <Wire.h>
#include <forward_list>
static std::forward_list<TelemetrySensor *> sensors;
template <typename T> void addSensor(ScanI2C *i2cScanner, ScanI2C::DeviceType type)
{
ScanI2C::FoundDevice dev = i2cScanner->find(type);
if (dev.type != ScanI2C::DeviceType::NONE || type == ScanI2C::DeviceType::NONE) {
TelemetrySensor *sensor = new T();
#if WIRE_INTERFACES_COUNT > 1
TwoWire *bus = ScanI2CTwoWire::fetchI2CBus(dev.address);
if (dev.address.port != ScanI2C::I2CPort::WIRE1 && sensor->onlyWire1()) {
// This sensor only works on Wire (Wire1 is not supported)
delete sensor;
return;
}
#else
TwoWire *bus = &Wire;
#endif
if (sensor->initDevice(bus, &dev)) {
sensors.push_front(sensor);
return;
}
// destroy sensor
delete sensor;
}
}
#endif
@@ -1,6 +1,6 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER)
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && (__has_include(<bsec2.h>) || __has_include(<Adafruit_BME680.h>))
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "BME680Sensor.h"
@@ -10,7 +10,7 @@
BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {}
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#if __has_include(<bsec2.h>)
int32_t BME680Sensor::runOnce()
{
if (!bme680.run()) {
@@ -18,13 +18,13 @@ int32_t BME680Sensor::runOnce()
}
return 35;
}
#endif // defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
#endif
bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
status = 0;
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#if __has_include(<bsec2.h>)
if (!bme680.begin(dev->address.address, *bus))
checkStatus("begin");
@@ -56,7 +56,7 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
status = 1;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif
initI2CSensor();
return status;
@@ -64,7 +64,7 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#if __has_include(<bsec2.h>)
if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0)
return false;
@@ -98,11 +98,11 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F;
measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif
return true;
}
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#if __has_include(<bsec2.h>)
void BME680Sensor::loadState()
{
#ifdef FSCom
@@ -179,6 +179,6 @@ void BME680Sensor::checkStatus(const char *functionName)
else if (bme680.sensor.status > BME68X_OK)
LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status);
}
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif
#endif
+11 -11
View File
@@ -1,29 +1,29 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER)
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && (__has_include(<bsec2.h>) || __has_include(<Adafruit_BME680.h>))
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#if __has_include(<bsec2.h>)
#include <bme68xLibrary.h>
#include <bsec2.h>
#else
#include <Adafruit_BME680.h>
#include <memory>
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif
#define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis()
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#if __has_include(<bsec2.h>)
const uint8_t bsec_config[] = {
#include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt"
};
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif
class BME680Sensor : public TelemetrySensor
{
private:
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#if __has_include(<bsec2.h>)
Bsec2 bme680;
#else
using BME680Ptr = std::unique_ptr<Adafruit_BME680>;
@@ -31,10 +31,10 @@ class BME680Sensor : public TelemetrySensor
static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique<Adafruit_BME680>(bus); }
BME680Ptr bme680;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif
protected:
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#if __has_include(<bsec2.h>)
const char *bsecConfigFileName = "/prefs/bsec.dat";
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
uint8_t accuracy = 0;
@@ -51,13 +51,13 @@ class BME680Sensor : public TelemetrySensor
void loadState();
void updateState();
void checkStatus(const char *functionName);
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif
public:
BME680Sensor();
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#if __has_include(<bsec2.h>)
virtual int32_t runOnce() override;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
@@ -0,0 +1,158 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
#include "../detect/reClockI2C.h"
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "PMSA003ISensor.h"
#include "TelemetrySensor.h"
#include <Wire.h>
PMSA003ISensor::PMSA003ISensor() : TelemetrySensor(meshtastic_TelemetrySensorType_PMSA003I, "PMSA003I") {}
bool PMSA003ISensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s", sensorName);
#ifdef PMSA003I_ENABLE_PIN
pinMode(PMSA003I_ENABLE_PIN, OUTPUT);
#endif
_bus = bus;
_address = dev->address.address;
#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
uint32_t currentClock = reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus);
if (!currentClock) {
LOG_WARN("PMSA003I can't be used at this clock speed");
return false;
}
#endif
_bus->beginTransmission(_address);
if (_bus->endTransmission() != 0) {
LOG_WARN("PMSA003I not found on I2C at 0x12");
return false;
}
#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
reClockI2C(currentClock, _bus);
#endif
status = 1;
LOG_INFO("PMSA003I Enabled");
initI2CSensor();
return true;
}
bool PMSA003ISensor::getMetrics(meshtastic_Telemetry *measurement)
{
if (!isActive()) {
LOG_WARN("PMSA003I is not active");
return false;
}
#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
uint32_t currentClock = reClockI2C(PMSA003I_I2C_CLOCK_SPEED, _bus);
#endif
_bus->requestFrom(_address, PMSA003I_FRAME_LENGTH);
if (_bus->available() < PMSA003I_FRAME_LENGTH) {
LOG_WARN("PMSA003I read failed: incomplete data (%d bytes)", _bus->available());
return false;
}
#if defined(PMSA003I_I2C_CLOCK_SPEED) && defined(CAN_RECLOCK_I2C)
reClockI2C(currentClock, _bus);
#endif
for (uint8_t i = 0; i < PMSA003I_FRAME_LENGTH; i++) {
buffer[i] = _bus->read();
}
if (buffer[0] != 0x42 || buffer[1] != 0x4D) {
LOG_WARN("PMSA003I frame header invalid: 0x%02X 0x%02X", buffer[0], buffer[1]);
return false;
}
auto read16 = [](uint8_t *data, uint8_t idx) -> uint16_t { return (data[idx] << 8) | data[idx + 1]; };
computedChecksum = 0;
for (uint8_t i = 0; i < PMSA003I_FRAME_LENGTH - 2; i++) {
computedChecksum += buffer[i];
}
receivedChecksum = read16(buffer, PMSA003I_FRAME_LENGTH - 2);
if (computedChecksum != receivedChecksum) {
LOG_WARN("PMSA003I checksum failed: computed 0x%04X, received 0x%04X", computedChecksum, receivedChecksum);
return false;
}
measurement->variant.air_quality_metrics.has_pm10_standard = true;
measurement->variant.air_quality_metrics.pm10_standard = read16(buffer, 4);
measurement->variant.air_quality_metrics.has_pm25_standard = true;
measurement->variant.air_quality_metrics.pm25_standard = read16(buffer, 6);
measurement->variant.air_quality_metrics.has_pm100_standard = true;
measurement->variant.air_quality_metrics.pm100_standard = read16(buffer, 8);
// TODO - Add admin command to remove environmental metrics to save protobuf space
measurement->variant.air_quality_metrics.has_pm10_environmental = true;
measurement->variant.air_quality_metrics.pm10_environmental = read16(buffer, 10);
measurement->variant.air_quality_metrics.has_pm25_environmental = true;
measurement->variant.air_quality_metrics.pm25_environmental = read16(buffer, 12);
measurement->variant.air_quality_metrics.has_pm100_environmental = true;
measurement->variant.air_quality_metrics.pm100_environmental = read16(buffer, 14);
// TODO - Add admin command to remove PN to save protobuf space
measurement->variant.air_quality_metrics.has_particles_03um = true;
measurement->variant.air_quality_metrics.particles_03um = read16(buffer, 16);
measurement->variant.air_quality_metrics.has_particles_05um = true;
measurement->variant.air_quality_metrics.particles_05um = read16(buffer, 18);
measurement->variant.air_quality_metrics.has_particles_10um = true;
measurement->variant.air_quality_metrics.particles_10um = read16(buffer, 20);
measurement->variant.air_quality_metrics.has_particles_25um = true;
measurement->variant.air_quality_metrics.particles_25um = read16(buffer, 22);
measurement->variant.air_quality_metrics.has_particles_50um = true;
measurement->variant.air_quality_metrics.particles_50um = read16(buffer, 24);
measurement->variant.air_quality_metrics.has_particles_100um = true;
measurement->variant.air_quality_metrics.particles_100um = read16(buffer, 26);
return true;
}
bool PMSA003ISensor::isActive()
{
return state == State::ACTIVE;
}
void PMSA003ISensor::sleep()
{
#ifdef PMSA003I_ENABLE_PIN
digitalWrite(PMSA003I_ENABLE_PIN, LOW);
state = State::IDLE;
#endif
}
uint32_t PMSA003ISensor::wakeUp()
{
#ifdef PMSA003I_ENABLE_PIN
LOG_INFO("Waking up PMSA003I");
digitalWrite(PMSA003I_ENABLE_PIN, HIGH);
state = State::ACTIVE;
return PMSA003I_WARMUP_MS;
#endif
// No need to wait for warmup if already active
return 0;
}
#endif
@@ -0,0 +1,35 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#define PMSA003I_I2C_CLOCK_SPEED 100000
#define PMSA003I_FRAME_LENGTH 32
#define PMSA003I_WARMUP_MS 30000
class PMSA003ISensor : public TelemetrySensor
{
public:
PMSA003ISensor();
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
virtual bool isActive() override;
virtual void sleep() override;
virtual uint32_t wakeUp() override;
private:
enum class State { IDLE, ACTIVE };
State state = State::ACTIVE;
uint16_t computedChecksum = 0;
uint16_t receivedChecksum = 0;
uint8_t buffer[PMSA003I_FRAME_LENGTH]{};
TwoWire *_bus{};
uint8_t _address{};
};
#endif
@@ -1,6 +1,6 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "NodeDB.h"
@@ -58,6 +58,11 @@ class TelemetrySensor
// TODO: delete after migration
bool hasSensor() { return nodeTelemetrySensorsMap[sensorType].first > 0; }
// Functions to sleep / wakeup sensors that support it
virtual void sleep(){};
virtual uint32_t wakeUp() { return 0; }
// Return active by default, override per sensor
virtual bool isActive() { return true; }
#if WIRE_INTERFACES_COUNT > 1
// Set to true if Implementation only works first I2C port (Wire)
@@ -65,6 +70,7 @@ class TelemetrySensor
#endif
virtual int32_t runOnce() { return INT32_MAX; }
virtual bool isInitialized() { return initialized; }
// TODO: is this used?
virtual bool isRunning() { return status > 0; }
virtual bool getMetrics(meshtastic_Telemetry *measurement) = 0;
-2
View File
@@ -47,7 +47,6 @@ int32_t ICM20948Sensor::runOnce()
int32_t ICM20948Sensor::runOnce()
{
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN
#if defined(MUZI_BASE) // temporarily gated to single device due to feature freeze
if (screen && !screen->isScreenOn() && !config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) {
if (!isAsleep) {
LOG_DEBUG("sleeping IMU");
@@ -60,7 +59,6 @@ int32_t ICM20948Sensor::runOnce()
sensor->sleep(false);
isAsleep = false;
}
#endif
float magX = 0, magY = 0, magZ = 0;
if (sensor->dataReady()) {
+1 -1
View File
@@ -82,8 +82,8 @@ class ICM20948Sensor : public MotionSensor
private:
ICM20948Singleton *sensor = nullptr;
bool showingScreen = false;
#ifdef MUZI_BASE
bool isAsleep = false;
#ifdef MUZI_BASE
float highestX = 449.000000, lowestX = -140.000000, highestY = 422.000000, lowestY = -232.000000, highestZ = 749.000000,
lowestZ = 98.000000;
#else
-46
View File
@@ -1,46 +0,0 @@
#include "BleOta.h"
#include "Arduino.h"
#include <esp_ota_ops.h>
static const String MESHTASTIC_OTA_APP_PROJECT_NAME("Meshtastic-OTA");
const esp_partition_t *BleOta::findEspOtaAppPartition()
{
const esp_partition_t *part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, nullptr);
esp_app_desc_t app_desc;
esp_err_t ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc));
if (ret != ESP_OK || MESHTASTIC_OTA_APP_PROJECT_NAME != app_desc.project_name) {
part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, nullptr);
ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc));
}
if (ret == ESP_OK && MESHTASTIC_OTA_APP_PROJECT_NAME == app_desc.project_name) {
return part;
} else {
return nullptr;
}
}
String BleOta::getOtaAppVersion()
{
const esp_partition_t *part = findEspOtaAppPartition();
esp_app_desc_t app_desc;
esp_err_t ret = ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_get_partition_description(part, &app_desc));
String version;
if (ret == ESP_OK) {
version = app_desc.version;
}
return version;
}
bool BleOta::switchToOtaApp()
{
bool success = false;
const esp_partition_t *part = findEspOtaAppPartition();
if (part) {
success = (ESP_ERROR_CHECK_WITHOUT_ABORT(esp_ota_set_boot_partition(part)) == ESP_OK);
}
return success;
}
-20
View File
@@ -1,20 +0,0 @@
#ifndef BLEOTA_H
#define BLEOTA_H
#include <Arduino.h>
#include <functional>
class BleOta
{
public:
explicit BleOta(){};
static String getOtaAppVersion();
static bool switchToOtaApp();
private:
String mUserAgent;
static const esp_partition_t *findEspOtaAppPartition();
};
#endif // BLEOTA_H
@@ -1,13 +1,17 @@
#include "WiFiOTA.h"
#include "MeshtasticOTA.h"
#include "configuration.h"
#ifdef ESP_PLATFORM
#include <Preferences.h>
#include <esp_ota_ops.h>
#endif
namespace WiFiOTA
namespace MeshtasticOTA
{
static const char *nvsNamespace = "ota-wifi";
static const char *appProjectName = "OTA-WiFi";
static const char *nvsNamespace = "MeshtasticOTA";
static const char *combinedAppProjectName = "MeshtasticOTA";
static const char *bleOnlyAppProjectName = "MeshtasticOTA-BLE";
static const char *wifiOnlyAppProjectName = "MeshtasticOTA-WiFi";
static bool updated = false;
@@ -43,12 +47,14 @@ void recoverConfig(meshtastic_Config_NetworkConfig *network)
strncpy(network->wifi_psk, psk.c_str(), sizeof(network->wifi_psk));
}
void saveConfig(meshtastic_Config_NetworkConfig *network)
void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash)
{
LOG_INFO("Saving WiFi settings for upcoming OTA update");
Preferences prefs;
prefs.begin(nvsNamespace);
prefs.putUChar("method", method);
prefs.putBytes("ota_hash", ota_hash, 32);
prefs.putString("ssid", network->wifi_ssid);
prefs.putString("psk", network->wifi_psk);
prefs.putBool("updated", false);
@@ -62,21 +68,48 @@ const esp_partition_t *getAppPartition()
bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc)
{
if (esp_ota_get_partition_description(part, app_desc) != ESP_OK)
return false;
if (strcmp(app_desc->project_name, appProjectName) != 0)
if (esp_ota_get_partition_description(part, app_desc) != ESP_OK) {
LOG_INFO("esp_ota_get_partition_description failed");
return false;
}
return true;
}
bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method)
{
// Combined loader supports all (both) transports, BLE and WiFi
if (strcmp(app_desc->project_name, combinedAppProjectName) == 0) {
LOG_INFO("OTA partition contains combined BLE/WiFi OTA Loader");
return true;
}
if (method == METHOD_OTA_BLE && strcmp(app_desc->project_name, bleOnlyAppProjectName) == 0) {
LOG_INFO("OTA partition contains BLE-only OTA Loader");
return true;
}
if (method == METHOD_OTA_WIFI && strcmp(app_desc->project_name, wifiOnlyAppProjectName) == 0) {
LOG_INFO("OTA partition contains WiFi-only OTA Loader");
return true;
}
LOG_INFO("OTA partition does not contain a known OTA loader");
return false;
}
bool trySwitchToOTA()
{
const esp_partition_t *part = getAppPartition();
esp_app_desc_t app_desc;
if (!getAppDesc(part, &app_desc))
if (part == NULL) {
LOG_WARN("Unable to get app partition in preparation of OTA reboot");
return false;
if (esp_ota_set_boot_partition(part) != ESP_OK)
}
uint8_t result = esp_ota_set_boot_partition(part);
// Partition and app checks should now be done in the AdminModule before this is called
if (result != ESP_OK) {
LOG_WARN("Unable to switch to OTA partiton. (Reason %d)", result);
return false;
}
return true;
}
@@ -89,4 +122,4 @@ const char *getVersion()
return app_desc.version;
}
} // namespace WiFiOTA
} // namespace MeshtasticOTA
+26
View File
@@ -0,0 +1,26 @@
#ifndef MESHTASTICOTA_H
#define MESHTASTICOTA_H
#include "mesh-pb-constants.h"
#include <Arduino.h>
#ifdef ESP_PLATFORM
#include <esp_ota_ops.h>
#endif
#define METHOD_OTA_BLE 1
#define METHOD_OTA_WIFI 2
namespace MeshtasticOTA
{
void initialize();
bool isUpdated();
const esp_partition_t *getAppPartition();
bool getAppDesc(const esp_partition_t *part, esp_app_desc_t *app_desc);
bool checkOTACapability(esp_app_desc_t *app_desc, uint8_t method);
void recoverConfig(meshtastic_Config_NetworkConfig *network);
void saveConfig(meshtastic_Config_NetworkConfig *network, meshtastic_OTAMode method, uint8_t *ota_hash);
bool trySwitchToOTA();
const char *getVersion();
} // namespace MeshtasticOTA
#endif // MESHTASTICOTA_H
-18
View File
@@ -1,18 +0,0 @@
#ifndef WIFIOTA_H
#define WIFIOTA_H
#include "mesh-pb-constants.h"
#include <Arduino.h>
namespace WiFiOTA
{
void initialize();
bool isUpdated();
void recoverConfig(meshtastic_Config_NetworkConfig *network);
void saveConfig(meshtastic_Config_NetworkConfig *network);
bool trySwitchToOTA();
const char *getVersion();
} // namespace WiFiOTA
#endif // WIFIOTA_H
+5 -14
View File
@@ -5,11 +5,10 @@
#include "main.h"
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH
#include "BleOta.h"
#include "nimble/NimbleBluetooth.h"
#endif
#include <WiFiOTA.h>
#include <MeshtasticOTA.h>
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
@@ -144,22 +143,14 @@ void esp32Setup()
preferences.putUInt("hwVendor", HW_VENDOR);
preferences.end();
LOG_DEBUG("Number of Device Reboots: %d", rebootCounter);
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
String BLEOTA = BleOta::getOtaAppVersion();
if (BLEOTA.isEmpty()) {
LOG_INFO("No BLE OTA firmware available");
} else {
LOG_INFO("BLE OTA firmware version %s", BLEOTA.c_str());
}
#endif
#if !MESHTASTIC_EXCLUDE_WIFI
String version = WiFiOTA::getVersion();
String version = MeshtasticOTA::getVersion();
if (version.isEmpty()) {
LOG_INFO("No WiFi OTA firmware available");
LOG_INFO("MeshtasticOTA firmware not available");
} else {
LOG_INFO("WiFi OTA firmware version %s", version.c_str());
LOG_INFO("MeshtasticOTA firmware version %s", version.c_str());
}
WiFiOTA::initialize();
MeshtasticOTA::initialize();
#endif
// enableModemSleep();
+2
View File
@@ -74,6 +74,8 @@
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M3
#elif defined(ELECROW_ThinkNode_M6)
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M6
#elif defined(ELECROW_ThinkNode_M4)
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M4
#elif defined(NANO_G2_ULTRA)
#define HW_VENDOR meshtastic_HardwareModel_NANO_G2_ULTRA
#elif defined(CANARYONE)
+10
View File
@@ -55,6 +55,7 @@ void cpuDeepSleep(uint32_t msecs)
void updateBatteryLevel(uint8_t level) NOT_IMPLEMENTED("updateBatteryLevel");
int TCPPort = SERVER_API_DEFAULT_PORT;
bool checkConfigPort = true;
static error_t parse_opt(int key, char *arg, struct argp_state *state)
{
@@ -63,6 +64,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state)
if (sscanf(arg, "%d", &TCPPort) < 1)
return ARGP_ERR_UNKNOWN;
else
checkConfigPort = false;
printf("Using config file %d\n", TCPPort);
break;
case 'c':
@@ -870,6 +872,14 @@ bool loadConfig(const char *configPath)
std::cout << "Cannot set both MACAddress and MACAddressSource!" << std::endl;
exit(EXIT_FAILURE);
}
if (checkConfigPort) {
portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as<int>(-1);
if (portduino_config.api_port != -1 &&
portduino_config.api_port > 1023 &&
portduino_config.api_port < 65536) {
TCPPort = (portduino_config.api_port);
}
}
portduino_config.mac_address = (yamlConfig["General"]["MACAddress"]).as<std::string>("");
if (portduino_config.mac_address != "") {
portduino_config.mac_address_explicit = true;
+4 -1
View File
@@ -175,6 +175,7 @@ extern struct portduino_config_struct {
std::string mac_address = "";
bool mac_address_explicit = false;
std::string mac_address_source = "";
int api_port = -1;
std::string config_directory = "";
std::string available_directory = "/etc/meshtasticd/available.d/";
int maxtophone = 100;
@@ -508,6 +509,8 @@ extern struct portduino_config_struct {
out << YAML::Key << "General" << YAML::Value << YAML::BeginMap;
if (config_directory != "")
out << YAML::Key << "ConfigDirectory" << YAML::Value << config_directory;
if (api_port != -1)
out << YAML::Key << "TCPPort" << YAML::Value << api_port;
if (mac_address_explicit)
out << YAML::Key << "MACAddress" << YAML::Value << mac_address;
if (mac_address_source != "")
@@ -519,4 +522,4 @@ extern struct portduino_config_struct {
out << YAML::EndMap; // General
return out.c_str();
}
} portduino_config;
} portduino_config;
+2
View File
@@ -118,6 +118,8 @@ class Power : private concurrency::OSThread
bool lipoChargerInit();
/// Setup a meshSolar battery sensor
bool meshSolarInit();
/// Setup a serial battery sensor
bool serialBatteryInit();
private:
void shutdown();
+1
View File
@@ -45,6 +45,7 @@ build_flags =
-DLIBPAX_BLE
-DHAS_UDP_MULTICAST=1
;-DDEBUG_HEAP
-DCAN_RECLOCK_I2C
lib_deps =
${arduino_base.lib_deps}
+20
View File
@@ -11,3 +11,23 @@ build_flags =
custom_sdkconfig =
${esp32_common.custom_sdkconfig}
; Override lib_deps to use environmental_extra_no_bsec instead of environmental_extra
; BSEC library uses ~3.5KB DRAM which causes overflow on original ESP32 targets
lib_deps =
${arduino_base.lib_deps}
${networking_base.lib_deps}
${networking_extra.lib_deps}
${environmental_base.lib_deps}
${environmental_extra_no_bsec.lib_deps}
${radiolib_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic-esp32_https_server packageName=https://github.com/meshtastic/esp32_https_server gitBranch=master
https://github.com/meshtastic/esp32_https_server/archive/3223704846752e6d545139204837bdb2a55459ca.zip
# renovate: datasource=custom.pio depName=NimBLE-Arduino packageName=h2zero/library/NimBLE-Arduino
h2zero/NimBLE-Arduino@1.4.3
# renovate: datasource=git-refs depName=libpax packageName=https://github.com/dbinfrago/libpax gitBranch=master
https://github.com/dbinfrago/libpax/archive/3cdc0371c375676a97967547f4065607d4c53fd1.zip
# renovate: datasource=github-tags depName=XPowersLib packageName=lewisxhe/XPowersLib
https://github.com/lewisxhe/XPowersLib/archive/v0.3.2.zip
# renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto
rweather/Crypto@0.4.0
@@ -1,9 +1,9 @@
[env:heltec-wireless-bridge]
;build_type = debug ; to make it possible to step through our jtag debugger
;build_type = debug ; to make it possible to step through our jtag debugger
extends = esp32_base
board_level = extra
board = heltec_wifi_lora_32
build_flags =
build_flags =
${esp32_base.build_flags}
-I variants/esp32/heltec_wireless_bridge
-D HELTEC_WIRELESS_BRIDGE
@@ -13,6 +13,7 @@ build_flags =
-D MESHTASTIC_EXCLUDE_CANNEDMESSAGES=1
-D MESHTASTIC_EXCLUDE_DETECTIONSENSOR=1
-D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
-D MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1
-D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL=1
-D MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION=1
-D MESHTASTIC_EXCLUDE_GPS=1
@@ -10,6 +10,8 @@ custom_meshtastic_tags = M5Stack
extends = esp32c6_base
board = esp32-c6-devkitc-1
board_upload.flash_size = 16MB
board_build.partitions = default_16MB.csv
;OpenOCD flash method
;upload_protocol = esp-builtin
;Normal method
@@ -0,0 +1,28 @@
// Need this file for ESP32-S3
// No need to modify this file, changes to pins imported from variant.h
// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#include <variant.h>
#define USB_VID 0x303a
#define USB_PID 0x1001
// Serial
static const uint8_t TX = UART_TX;
static const uint8_t RX = UART_RX;
// Default SPI will be mapped to Radio
static const uint8_t SS = LORA_CS;
static const uint8_t SCK = LORA_SCK;
static const uint8_t MOSI = LORA_MOSI;
static const uint8_t MISO = LORA_MISO;
// The default Wire will be mapped to PMU and RTC
static const uint8_t SCL = I2C_SCL;
static const uint8_t SDA = I2C_SDA;
#endif /* Pins_Arduino_h */
@@ -0,0 +1,8 @@
[env:CDEBYTE_EoRa-Hub]
extends = esp32s3_base
board = CDEBYTE_EoRa-Hub
board_level = extra
build_flags =
${esp32s3_base.build_flags}
-D PRIVATE_HW
-I variants/esp32s3/CDEBYTE_EoRa-Hub
@@ -0,0 +1,19 @@
#include "RadioLib.h"
// This is rewritten to match the requirements of the E80-900M2213S
// The E80 does not conform to the reference Semtech switches(!) and therefore needs a custom matrix.
// See footnote #3 in "https://www.cdebyte.com/products/E80-900M2213S/2#Pin"
// RF Switch Matrix SubG RFO_HP_LF / RFO_LP_LF / RFI_[NP]_LF0
// DIO5 -> RFSW0_V1
// DIO6 -> RFSW1_V2
// DIO7 -> not connected on E80 module - note that GNSS and Wifi scanning are not possible.
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
// mode DIO5 DIO6 DIO7
{LR11x0::MODE_STBY, {LOW, LOW, LOW}}, {LR11x0::MODE_RX, {LOW, HIGH, LOW}},
{LR11x0::MODE_TX, {HIGH, HIGH, LOW}}, {LR11x0::MODE_TX_HP, {HIGH, LOW, LOW}},
{LR11x0::MODE_TX_HF, {LOW, LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW, HIGH}},
{LR11x0::MODE_WIFI, {LOW, LOW, LOW}}, END_OF_MODE_TABLE,
};
@@ -0,0 +1,50 @@
// EByte EoRA-Hub
// Uses E80 (LR1121) LoRa module
#define LED_PIN 35
// Button - user interface
#define BUTTON_PIN 0 // BOOT button
#define BATTERY_PIN 1
#define ADC_CHANNEL ADC1_GPIO1_CHANNEL
#define ADC_MULTIPLIER 103.0 // Calibrated value
#define ADC_ATTENUATION ADC_ATTEN_DB_0
#define ADC_CTRL 37
#define ADC_CTRL_ENABLED LOW
// Display - OLED connected via I2C by the default hardware configuration
#define HAS_SCREEN 1
#define USE_SSD1306
#define I2C_SCL 17
#define I2C_SDA 18
// UART - The 1mm JST SH connector closest to the USB-C port
#define UART_TX 43
#define UART_RX 44
// Peripheral I2C - The 1mm JST SH connector furthest from the USB-C port which follows Adafruit connection standard. There are no
// pull-up resistors on these lines, the downstream device needs to include them. TODO: test, currently untested
#define I2C_SCL1 21
#define I2C_SDA1 10
// Radio
#define USE_LR1121
#define LORA_SCK 9
#define LORA_MOSI 10
#define LORA_MISO 11
#define LORA_RESET 12
#define LORA_CS 8
#define LORA_DIO9 13
// LR1121
#define LR1121_IRQ_PIN 14
#define LR1121_NRESET_PIN LORA_RESET
#define LR1121_BUSY_PIN LORA_DIO9
#define LR1121_SPI_NSS_PIN LORA_CS
#define LR1121_SPI_SCK_PIN LORA_SCK
#define LR1121_SPI_MOSI_PIN LORA_MOSI
#define LR1121_SPI_MISO_PIN LORA_MISO
#define LR11X0_DIO3_TCXO_VOLTAGE 1.8
#define LR11X0_DIO_AS_RF_SWITCH
@@ -48,11 +48,11 @@ lib_deps = ${esp32s3_base.lib_deps}
earlephilhower/ESP8266Audio@2.4.1
# renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM
earlephilhower/ESP8266SAM@1.1.0
# renovate: datasource=custom.pio depName=TCA9534 packageName=hideakitai/library/TCA9534
hideakitai/TCA9534@0.1.1
# renovate: datasource=git-refs depName=LovyanGFX-develop packageName=https://github.com/lovyan03/LovyanGFX gitBranch=develop
https://github.com/lovyan03/LovyanGFX/archive/2689b7c12e384558991d324e19bc67782f986551.zip
; REVISIT note: v1.2.7 breaks the elecrow 7" display functionality
# renovate: datasource=custom.pio depName=TCA9534 packageName=hideakitai/library/TCA9534
hideakitai/TCA9534@0.1.1
[crowpanel_small_esp32s3_base] ; 2.4, 2.8, 3.5 inch
extends = crowpanel_base
+11
View File
@@ -34,6 +34,17 @@ build_flags =
-D I2C_SCL1=3
[env:heltec-v4-tft]
custom_meshtastic_hw_model = 110
custom_meshtastic_hw_model_slug = HELTEC_V4
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 1
custom_meshtastic_display_name = Heltec V4 TFT
custom_meshtastic_images = heltec_v4.svg
custom_meshtastic_tags = Heltec
custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 16MB
extends = heltec_v4_base
build_flags =
${heltec_v4_base.build_flags} ;-Os
+5 -2
View File
@@ -53,10 +53,13 @@
#define HAS_BMA423 1
#define BMA4XX_INT 14 // Interrupt for BMA_423 axis sensor
#define HAS_GPS 1
#define GPS_DEFAULT_NOT_PRESENT 1
#define GPS_BAUDRATE 38400
#define GPS_RX_PIN 42
#define GPS_TX_PIN 41
#define GPS_RX_PIN 41
#define GPS_TX_PIN 42
#define BUTTON_PIN 0 // only for Plus version
#define USE_SX1262
#define USE_SX1268
+1 -1
View File
@@ -41,7 +41,7 @@ lib_deps = ${esp32s3_base.lib_deps}
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
lewisxhe/SensorLib@0.3.3
# renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver
https://github.com/pschatzmann/arduino-audio-driver/archive/v0.1.3.zip
https://github.com/pschatzmann/arduino-audio-driver/archive/v0.2.0.zip
# TODO renovate
https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip
# TODO renovate
@@ -0,0 +1,15 @@
; ThinkNode M4 - Powerbank nrf52840/LR1110 by Elecrow
[env:thinknode_m4]
extends = nrf52840_base
board = ThinkNode-M4
board_check = true
debug_tool = jlink
build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/ELECROW-ThinkNode-M4
-DELECROW_ThinkNode_M4
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M4>
lib_deps =
${nrf52840_base.lib_deps}
lewisxhe/PCF8563_Library@^1.0.1
@@ -0,0 +1,11 @@
#include "RadioLib.h"
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[] = {
// mode DIO5 DIO6
{LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}},
{LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}},
{LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
{LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE,
};
@@ -0,0 +1,51 @@
/*
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_LED2, OUTPUT);
ledOff(PIN_LED2);
pinMode(LED_PAIRING, OUTPUT);
ledOff(LED_PAIRING);
pinMode(Battery_LED_1, OUTPUT);
ledOff(Battery_LED_1);
pinMode(Battery_LED_2, OUTPUT);
ledOff(Battery_LED_2);
pinMode(Battery_LED_3, OUTPUT);
ledOff(Battery_LED_3);
pinMode(Battery_LED_4, OUTPUT);
ledOff(Battery_LED_4);
}
@@ -0,0 +1,142 @@
/*
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_ELECROW_THINKNODE_M4_
#define _VARIANT_ELECROW_THINKNODE_M4_
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
#define USE_LFXO
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "WVariant.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
#define PINS_COUNT (48)
#define NUM_DIGITAL_PINS (48)
#define NUM_ANALOG_INPUTS (1)
#define NUM_ANALOG_OUTPUTS (0)
// LEDs
#define LED_BUILTIN -1
#define LED_BLUE -1
#define PIN_LED2 (32 + 9)
#define LED_PAIRING (13)
#define Battery_LED_1 (15)
#define Battery_LED_2 (17)
#define Battery_LED_3 (32 + 2)
#define Battery_LED_4 (32 + 4)
#define LED_STATE_ON 1
// Button
#define PIN_BUTTON1 (4)
// Battery ADC
#define PIN_A0 (2)
#define BATTERY_PIN PIN_A0
#define BATTERY_SENSE_SAMPLES 30
#define ADC_RESOLUTION 14
#define BATTERY_SENSE_RESOLUTION_BITS 12
#define BATTERY_SENSE_RESOLUTION 4096.0
#define ADC_MULTIPLIER (2.00F)
#undef AREF_VOLTAGE
#define AREF_VOLTAGE 3.0
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define HAS_SERIAL_BATTERY_LEVEL 1
#define SERIAL_BATTERY_RX 30
#define SERIAL_BATTERY_TX 5
static const uint8_t A0 = PIN_A0;
#define PIN_NFC1 (9)
#define PIN_NFC2 (10)
// I2C
#define WIRE_INTERFACES_COUNT 1
#define PIN_WIRE_SDA (23)
#define PIN_WIRE_SCL (25)
// actually the LORA Radio
#define PIN_POWER_EN (11)
// charger status
#define EXT_CHRG_DETECT (32 + 6)
#define EXT_CHRG_DETECT_VALUE HIGH
// SPI
#define SPI_INTERFACES_COUNT 1
#define PIN_SPI_MISO (8)
#define PIN_SPI_MOSI (7)
#define PIN_SPI_SCK (6)
#define LORA_RESET (32 + 8)
#define LORA_DIO1 (12)
#define LORA_DIO2 (26)
#define LORA_SCK PIN_SPI_SCK
#define LORA_MISO PIN_SPI_MISO
#define LORA_MOSI PIN_SPI_MOSI
#define LORA_CS (27)
#define USE_LR1110
#define LR1110_IRQ_PIN LORA_DIO1
#define LR1110_NRESET_PIN LORA_RESET
#define LR1110_BUSY_PIN LORA_DIO2
#define LR1110_SPI_NSS_PIN LORA_CS
#define LR1110_SPI_SCK_PIN LORA_SCK
#define LR1110_SPI_MOSI_PIN LORA_MOSI
#define LR1110_SPI_MISO_PIN LORA_MISO
#define LR11X0_DIO3_TCXO_VOLTAGE 1.6
#define LR11X0_DIO_AS_RF_SWITCH
// Peripherals on I2C bus. Active Low
#define VEXT_ENABLE (32)
#define VEXT_ON_VALUE LOW
// GPS L76K
#define HAS_GPS 1
#define GPS_L76K
#define GPS_BAUDRATE 9600
#define PIN_GPS_EN (32 + 11)
#define GPS_EN_ACTIVE LOW
#define PIN_GPS_RESET (3)
#define GPS_RESET_MODE HIGH
#define PIN_GPS_STANDBY (28)
#define GPS_STANDBY_ACTIVE HIGH
#define GPS_TX_PIN (32 + 12)
#define GPS_RX_PIN (32 + 14)
#define GPS_THREAD_INTERVAL 50
#define PIN_SERIAL1_RX GPS_RX_PIN
#define PIN_SERIAL1_TX GPS_TX_PIN
#ifdef __cplusplus
}
#endif
#endif
@@ -134,12 +134,14 @@ static const uint8_t A0 = PIN_A0;
#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 AREF_VOLTAGE 2.4
#define VBAT_AR_INTERNAL AR_INTERNAL_2_4
#define ADC_MULTIPLIER (1.75F)
#define HAS_SOLAR
#define OCV_ARRAY 4080, 3990, 3935, 3880, 3825, 3770, 3715, 3660, 3605, 3550, 3450
#ifdef __cplusplus
}
#endif
@@ -13,6 +13,7 @@ build_flags =
-DPIN_SERIAL1_RX=PB7
-DPIN_SERIAL1_TX=PB6
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
-DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1
-DMESHTASTIC_EXCLUDE_I2C=1
-DMESHTASTIC_EXCLUDE_GPS=1
+1
View File
@@ -12,6 +12,7 @@ build_flags =
-DPIN_WIRE_SDA=PA11
-DPIN_WIRE_SCL=PA12
-DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
-DMESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR=1
-DMESHTASTIC_EXCLUDE_I2C=1
-DMESHTASTIC_EXCLUDE_GPS=1
+21
View File
@@ -0,0 +1,21 @@
; Russell is a board designed to mount on an ER34615/IFR32700 cell and go Up! on a balloon
; Hardware repository: https://github.com/Meshtastic-Malaysia/russell
; - RAK3172 STM32WLE5CCU6 MCU + integrated SX1262 LoRa
; - CDtop CD-PA1010D GPS
; - Bosch Sensortec BME280 sensor
; - Consonance CN3158 LiFePO4 solar charger
[env:russell]
extends = stm32_base
board = wiscore_rak3172
board_level = extra
board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem
build_flags =
${stm32_base.build_flags}
-Ivariants/stm32/russell
-DPRIVATE_HW
lib_deps =
${stm32_base.lib_deps}
# renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library
adafruit/Adafruit BME280 Library@2.3.0
upload_port = stlink
+7
View File
@@ -0,0 +1,7 @@
// Pins from https://forum.rakwireless.com/t/rak3172-internal-schematic/4557/2
// PB8, PC13
static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PB8, PC13, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
static const Module::RfSwitchMode_t rfswitch_table[4] = {
{STM32WLx::MODE_IDLE, {LOW, LOW}}, {STM32WLx::MODE_RX, {HIGH, LOW}}, {STM32WLx::MODE_TX_HP, {LOW, HIGH}}, END_OF_MODE_TABLE};
+41
View File
@@ -0,0 +1,41 @@
#ifndef _VARIANT_RUSSELL_
#define _VARIANT_RUSSELL_
#define USE_STM32WLx
// I/O
#define LED_PIN PA0 // Red LED
#define LED_STATE_ON 1
#define BUTTON_PIN PH3 // Shared with BOOT0
#define BUTTON_NEED_PULLUP
// Charger IC charge/standby pins are open-drain with no hardware pull-up:
// Internal pull-up is needed on STM32 (TODO)
// #define EXT_CHRG_DETECT PA5
// #define EXT_PWR_DETECT PA4
// Bosch Sensortec BME280
#define HAS_SENSOR 1
// CDtop CD-PA1010D
#define ENABLE_HWSERIAL1
#define PIN_SERIAL1_RX PB7
#define PIN_SERIAL1_TX PB6
#define HAS_GPS 1
#define PIN_GPS_STANDBY PA15
#define GPS_RX_PIN PB7
#define GPS_TX_PIN PB6
// LoRa
/*
* RAK3172 (-20–85°C) -> No TCXO
* RAK3172-T (-40–85°C) -> 3.0V TCXO
* https://github.com/RAKWireless/RAK-STM32-RUI/blob/e5a28be8fab1a492bd9223dd425ca33a8a297d90/variants/WisDuo_RAK3172-T_Board/radio_conf.h#L91
*/
#define TCXO_OPTIONAL
#define SX126X_DIO3_TCXO_VOLTAGE 3.0
// Required to avoid Serial1 conflicts due to board definition here:
// https://github.com/stm32duino/Arduino_Core_STM32/blob/main/variants/STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U/variant_RAK3172_MODULE.h
#define RAK3172
#endif