Compare commits

...
25 Commits
Author SHA1 Message Date
Ben Meadors 8f18534ebd Protobufs 2026-02-11 06:24:02 -06:00
Ben Meadors fd084abbd9 Add STC31 CO2 sensor support and related configurations 2026-02-11 06:23:38 -06:00
Tom a092f6bb22 Refactor logging in ProtobufModule to ensure message details are logged after successful decoding (#9536) 2026-02-09 19:56:48 -06:00
Jason P 927a4e47b8 Update built-in documentation for current method of implementation (#9592) 2026-02-09 18:56:22 -06:00
Ben Meadors af18f061cf Merge remote-tracking branch 'origin/master' into develop 2026-02-09 13:53:19 -06:00
Ben Meadors b12cac3e00 Merge branch 'master' into develop 2026-02-09 12:52:22 -06:00
Eric SesterhennandBen Meadors 73adeee385 Enable FORTIFY and SP for native builds (#9537)
* Enable FORITFY and NX for native builds

meshtasticd does have an executable stack and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and a non-executable stack.

This gives the following improvements on Debian Trixie:

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   13516 Symbols	  No	0		17		./.pio/build/native/meshtasticd

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   13519 Symbols	  Yes	12		20		./.pio/build/native/meshtasticd

Tested with --sim mode I do not get any crashes or similar.

* Enable FORTIFY and NX for native builds

meshtasticd does have an executable stack and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and a non-executable stack.

This gives the following improvements on Debian Trixie:

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   13516 Symbols	  No	0		17		./.pio/build/native/meshtasticd

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   13519 Symbols	  Yes	12		20		./.pio/build/native/meshtasticd

Tested with --sim mode I do not get any crashes or similar.

* Enable FORTIFY and SP for native builds

meshtasticd does have a stack canaries and is not built with fortify, which makes exploitation of memory corruption bugs easier than it has to be. This enables fortify and stack canaries.

This gives the following improvements on Debian Trixie:

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   13516 Symbols	  No	0		17		./.pio/build/native/meshtasticd

$ checksec --file=./.pio/build/native/meshtasticd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   13519 Symbols	  Yes	12		20		./.pio/build/native/meshtasticd

Tested with --sim mode I do not get any crashes or similar.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-08 19:48:18 -06:00
Jonathan Bennett 4ce554e09d Make LED_POWER blip even in critical battery (#9545) 2026-02-08 06:49:30 -06:00
Jonathan Bennett eb145f8adc Add support for CW2015 LiPo battery fuel gauge (#9564)
* Add support for CW2015 LiPo battery fuel gauge

* Address Copilot's concerns, minor fixups
2026-02-07 22:30:14 -06:00
Jason P a60e7cfe62 Add Slash Key to VirtualKeyboard (#9563)
Addition of ? and / to the virtual Keyboard via short and long press
2026-02-07 20:43:26 -05:00
Austin 39139cc2ea RPM: Include meshtasticd-start.sh (#9561) 2026-02-07 11:13:01 -05:00
Austin 4a4b1f4a87 meshtasticd: Fix install on Fedora 43 (#9556) 2026-02-06 19:36:21 -06:00
Colby DillionandBen Meadors 779e446d14 Fix hop_limit upgrade detection (#9550)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-06 13:00:08 -06:00
Jonathan Bennett 2361776992 Rename LED_PIN to LED_POWER, move handling out of main to dedicated module (#9512)
* Rename LED_PIN to LED_POWER, move handling out of main to dedicated module

* Misc

* Remove errant endif
2026-02-05 05:41:00 -06:00
Eric SesterhennandBen Meadors 94b7149958 Remove unused hmx variable (#9529)
The variable is not used at all in the function, remove it to
silence the compiler warning.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-04 17:11:44 -06:00
Jonathan BennettandCopilot ac611c4b62 Add agc reset attempt (#8163)
* Add agc reset attempt

* Add radioLibInterface include

* Trunk

* AGC reset don't crash, don't naively call

* Update src/main.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Use Throttle function

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 14:47:44 -06:00
MaxandBen Meadors 89df5ef669 Undefine LED_BUILTIN (#9531)
Keep variant in sync with 
https://github.com/meshtastic/firmware/commit/df40085

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-04 11:15:49 -06:00
Jason P bfc3eebd54 HotFix for ReplyBot - Modules.cpp included and moved configuration.h (#9532) 2026-02-04 08:56:50 -06:00
538a5f0dfc Add reply bot module with DM-only responses and rate limiting (#9456)
* Implement Meshtastic reply bot module with ping and status features

Adds a reply bot module that listens for /ping, /hello, and /test commands received via direct messages or broadcasts on the primary channel. The module always replies via direct message to the sender only, reporting hop count, RSSI, and SNR. Per-sender cooldowns are enforced to reduce network spam, and the module can be excluded at build time via a compile flag. Updates include the new module source files and required build configuration changes.

* Update ReplyBotModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/modules/ReplyBotModule.h

Match the existing MESHTASTIC_EXCLUDE_* guard pattern so the module is excluded by default.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/modules/ReplyBotModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Tidying up

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-03 20:45:02 -06:00
Vortetty b7db22055d Inkhud battery icon improvements. (#9513)
* Inkhud battery icon improvements.
Fixes the battery icon draining from the flat side towards the bump, which is backwards from general design language seen on most devices
By request of kr0n05_ on discord, adds the ability to mirror the battery icon which fixes that issue in another way, and is also a common design seen on other devices.

* Remove option for icon mirroring

* Add border + dither to battery to prevent font overlap

* Fix trunk format

* Code cleanup, courtesy of Xaositek.
2026-02-03 20:02:54 -05:00
Eric Sesterhenn 0703e0e6d7 Make sure we always return a value in NodeDB::restorePreferences() (#9516)
In case FScom is not defined there is no return statement. This
moves the return outside of the ifdef to make sure a defined
value is returned.
2026-02-03 06:22:33 -06:00
Jonathan Bennett f514bc230b Prefer EXT_PWR_DETECT pin over chargingVolt to detect power unplugged (#9511) 2026-02-03 00:13:49 -06:00
Jason P 0022148323 Missed in reviews - fixing send bubble (#9505) 2026-02-01 19:10:00 -06:00
Jonathan Bennett 9d06c1bf34 Add StatusMessage module and config overrides (#9351)
* Add StatusMessage module and config overrides

* Trunk

* Don't reboot node simply for a StatusMessage config update
2026-01-31 14:55:51 -06:00
Jonathan Bennett 1d30342c00 Don't ever define PIN_LED or BLE_LED_INVERTED (#9494)
* Don't ever define PIN_LED

* Deprecate BLE_LED_INVERTED
2026-01-31 12:15:06 -06:00
147 changed files with 1016 additions and 422 deletions
+8 -1
View File
@@ -59,8 +59,14 @@ BuildRequires: pkgconfig(libbsd-overlay)
Requires: systemd-udev
# Declare that this package provides the user/group it creates in %pre
# Required for Fedora 43+ which tracks users/groups as RPM dependencies
Provides: user(%{meshtasticd_user})
Provides: group(%{meshtasticd_user})
Provides: group(spi)
%description
Meshtastic daemon for controlling Meshtastic devices. Meshtastic is an off-grid
Meshtastic daemon. Meshtastic is an off-grid
text communication platform that uses inexpensive LoRa radios.
%prep
@@ -151,6 +157,7 @@ fi
%license LICENSE
%doc README.md
%{_bindir}/meshtasticd
%{_bindir}/meshtasticd-start.sh
%dir %{_localstatedir}/lib/meshtasticd
%{_udevrulesdir}/99-meshtasticd-udev.rules
%dir %{_sysconfdir}/meshtasticd
+5
View File
@@ -50,6 +50,7 @@ build_flags = -Wno-missing-field-initializers
-DRADIOLIB_EXCLUDE_APRS=1
-DRADIOLIB_EXCLUDE_LORAWAN=1
-DMESHTASTIC_EXCLUDE_DROPZONE=1
-DMESHTASTIC_EXCLUDE_REPLYBOT=1
-DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1
-DMESHTASTIC_EXCLUDE_HEALTH_TELEMETRY=1
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
@@ -213,6 +214,8 @@ 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
# renovate: datasource=custom.pio depName=Sensirion I2C STC3x packageName=sensirion/library/Sensirion I2C STC3x
sensirion/Sensirion I2C STC3x@1.0.1
; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets)
[environmental_extra_no_bsec]
@@ -241,3 +244,5 @@ 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
# renovate: datasource=custom.pio depName=Sensirion I2C STC3x packageName=sensirion/library/Sensirion I2C STC3x
sensirion/Sensirion I2C STC3x@1.0.1
+2 -10
View File
@@ -89,22 +89,14 @@ class BluetoothStatus : public Status
case ConnectionState::CONNECTED:
LOG_DEBUG("BluetoothStatus CONNECTED");
#ifdef BLE_LED
#ifdef BLE_LED_INVERTED
digitalWrite(BLE_LED, LOW);
#else
digitalWrite(BLE_LED, HIGH);
#endif
digitalWrite(BLE_LED, LED_STATE_ON);
#endif
break;
case ConnectionState::DISCONNECTED:
LOG_DEBUG("BluetoothStatus DISCONNECTED");
#ifdef BLE_LED
#ifdef BLE_LED_INVERTED
digitalWrite(BLE_LED, HIGH);
#else
digitalWrite(BLE_LED, LOW);
#endif
digitalWrite(BLE_LED, LED_STATE_OFF);
#endif
break;
}
-66
View File
@@ -1,66 +0,0 @@
#include "Led.h"
#include "PowerMon.h"
#include "main.h"
#include "power.h"
GpioVirtPin ledForceOn, ledBlink;
#if defined(LED_PIN)
// Most boards have a GPIO for LED control
static GpioHwPin ledRawHwPin(LED_PIN);
#else
static GpioVirtPin ledRawHwPin; // Dummy pin for no hardware
#endif
#if LED_STATE_ON == 0
static GpioVirtPin ledHwPin;
static GpioNotTransformer ledInverter(&ledHwPin, &ledRawHwPin);
#else
static GpioPin &ledHwPin = ledRawHwPin;
#endif
#if defined(HAS_PMU)
/**
* A GPIO controlled by the PMU
*/
class GpioPmuPin : public GpioPin
{
public:
void set(bool value)
{
if (pmu_found && PMU) {
// blink the axp led
PMU->setChargingLedMode(value ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF);
}
}
} ledPmuHwPin;
// In some cases we need to drive a PMU LED and a normal LED
static GpioSplitter ledFinalPin(&ledHwPin, &ledPmuHwPin);
#else
static GpioPin &ledFinalPin = ledHwPin;
#endif
#ifdef USE_POWERMON
/**
* We monitor changes to the LED drive output because we use that as a sanity test in our power monitor stuff.
*/
class MonitoredLedPin : public GpioPin
{
public:
void set(bool value)
{
if (powerMon) {
if (value)
powerMon->setState(meshtastic_PowerMon_State_LED_On);
else
powerMon->clearState(meshtastic_PowerMon_State_LED_On);
}
ledFinalPin.set(value);
}
} monitoredLedPin;
#else
static GpioPin &monitoredLedPin = ledFinalPin;
#endif
static GpioBinaryTransformer ledForcer(&ledForceOn, &ledBlink, &monitoredLedPin, GpioBinaryTransformer::Or);
-7
View File
@@ -1,7 +0,0 @@
#include "GpioLogic.h"
#include "configuration.h"
/**
* ledForceOn and ledForceOff both override the normal ledBlinker behavior (which is controlled by main)
*/
extern GpioVirtPin ledForceOn, ledBlink;
+93 -8
View File
@@ -459,6 +459,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
}
// if it's not HIGH - check the battery
#endif
// If we have an EXT_PWR_DETECT pin and it indicates no external power, believe it.
return false;
// technically speaking this should work for all(?) NRF52 boards
// but needs testing across multiple devices. NRF52 USB would not even work if
@@ -690,7 +692,9 @@ bool Power::setup()
bool found = false;
if (axpChipInit()) {
found = true;
} else if (lipoInit()) {
} else if (cw2015Init()) {
found = true;
} else if (max17048Init()) {
found = true;
} else if (lipoChargerInit()) {
found = true;
@@ -1319,7 +1323,7 @@ bool Power::axpChipInit()
/**
* Wrapper class for an I2C MAX17048 Lipo battery sensor.
*/
class LipoBatteryLevel : public HasBatteryLevel
class MAX17048BatteryLevel : public HasBatteryLevel
{
private:
MAX17048Singleton *max17048 = nullptr;
@@ -1367,18 +1371,18 @@ class LipoBatteryLevel : public HasBatteryLevel
virtual bool isCharging() override { return max17048->isBatteryCharging(); }
};
LipoBatteryLevel lipoLevel;
MAX17048BatteryLevel max17048Level;
/**
* Init the Lipo battery level sensor
*/
bool Power::lipoInit()
bool Power::max17048Init()
{
bool result = lipoLevel.runOnce();
LOG_DEBUG("Power::lipoInit lipo sensor is %s", result ? "ready" : "not ready yet");
bool result = max17048Level.runOnce();
LOG_DEBUG("Power::max17048Init lipo sensor is %s", result ? "ready" : "not ready yet");
if (!result)
return false;
batteryLevel = &lipoLevel;
batteryLevel = &max17048Level;
return true;
}
@@ -1386,7 +1390,88 @@ bool Power::lipoInit()
/**
* The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel
*/
bool Power::lipoInit()
bool Power::max17048Init()
{
return false;
}
#endif
#if !MESHTASTIC_EXCLUDE_I2C && HAS_CW2015
class CW2015BatteryLevel : public AnalogBatteryLevel
{
public:
/**
* Battery state of charge, from 0 to 100 or -1 for unknown
*/
virtual int getBatteryPercent() override
{
int data = -1;
Wire.beginTransmission(CW2015_ADDR);
Wire.write(0x04);
if (Wire.endTransmission() == 0) {
if (Wire.requestFrom(CW2015_ADDR, (uint8_t)1)) {
data = Wire.read();
}
}
return data;
}
/**
* The raw voltage of the battery in millivolts, or NAN if unknown
*/
virtual uint16_t getBattVoltage() override
{
uint16_t mv = 0;
Wire.beginTransmission(CW2015_ADDR);
Wire.write(0x02);
if (Wire.endTransmission() == 0) {
if (Wire.requestFrom(CW2015_ADDR, (uint8_t)2)) {
mv = Wire.read();
mv <<= 8;
mv |= Wire.read();
// Voltage is read in 305uV units, convert to mV
mv = mv * 305 / 1000;
}
}
return mv;
}
};
CW2015BatteryLevel cw2015Level;
/**
* Init the CW2015 battery level sensor
*/
bool Power::cw2015Init()
{
Wire.beginTransmission(CW2015_ADDR);
uint8_t getInfo[] = {0x0a, 0x00};
Wire.write(getInfo, 2);
Wire.endTransmission();
delay(10);
Wire.beginTransmission(CW2015_ADDR);
Wire.write(0x00);
bool result = false;
if (Wire.endTransmission() == 0) {
if (Wire.requestFrom(CW2015_ADDR, (uint8_t)1)) {
uint8_t data = Wire.read();
LOG_DEBUG("CW2015 init read data: 0x%x", data);
if (data == 0x73) {
result = true;
batteryLevel = &cw2015Level;
}
}
}
return result;
}
#else
/**
* The CW2015 battery level sensor is unavailable - default to AnalogBatteryLevel
*/
bool Power::cw2015Init()
{
return false;
}
+4 -4
View File
@@ -9,13 +9,13 @@
*/
#include "PowerFSM.h"
#include "Default.h"
#include "Led.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerMon.h"
#include "configuration.h"
#include "graphics/Screen.h"
#include "main.h"
#include "modules/StatusLEDModule.h"
#include "sleep.h"
#include "target_specific.h"
@@ -103,7 +103,7 @@ static void lsIdle()
uint32_t sleepTime = SLEEP_TIME;
powerMon->setState(meshtastic_PowerMon_State_CPU_LightSleep);
ledBlink.set(false); // Never leave led on while in light sleep
statusLEDModule->setPowerLED(false);
esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL);
powerMon->clearState(meshtastic_PowerMon_State_CPU_LightSleep);
@@ -111,7 +111,7 @@ static void lsIdle()
case ESP_SLEEP_WAKEUP_TIMER:
// Normal case: timer expired, we should just go back to sleep ASAP
ledBlink.set(true); // briefly turn on led
statusLEDModule->setPowerLED(true);
wakeCause2 = doLightSleep(100); // leave led on for 1ms
secsSlept += sleepTime;
@@ -146,7 +146,7 @@ static void lsIdle()
}
} else {
// Time to stop sleeping!
ledBlink.set(false);
statusLEDModule->setPowerLED(false);
LOG_INFO("Reached ls_secs, service loop()");
powerFSM.trigger(EVENT_WAKE_TIMER);
}
+2
View File
@@ -233,6 +233,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define NAU7802_ADDR 0x2A
#define MAX30102_ADDR 0x57
#define SCD4X_ADDR 0x62
#define CW2015_ADDR 0x62
#define STC31_ADDR 0x2c // RAK12008 uses 0x2C address
#define MLX90614_ADDR_DEF 0x5A
#define CGRADSENS_ADDR 0x66
#define LTR390UV_ADDR 0x53
+3 -1
View File
@@ -89,7 +89,9 @@ class ScanI2C
DA217,
CHSC6X,
CST226SE,
SEN5X
CW2015,
SEN5X,
STC31
} DeviceType;
// typedef uint8_t DeviceAddress;
+75 -7
View File
@@ -154,6 +154,42 @@ String readSEN5xProductName(TwoWire *i2cBus, uint8_t address)
return String(productName);
}
/// for STC31 detection - probe by reading product identifier
/// Uses two-step sequence: prepareProductIdentifier (0x367c) then readProductIdentifier (0xe102)
bool probeSTC31(TwoWire *i2cBus, uint8_t address)
{
// Step 1: Send prepare product identifier command (0x367c)
uint8_t prepCmd[] = {0x36, 0x7c};
i2cBus->beginTransmission(address);
i2cBus->write(prepCmd, 2);
if (i2cBus->endTransmission() != 0)
return false;
delay(1);
// Step 2: Send read product identifier command (0xe102)
uint8_t readCmd[] = {0xe1, 0x02};
i2cBus->beginTransmission(address);
i2cBus->write(readCmd, 2);
if (i2cBus->endTransmission() != 0)
return false;
delay(10);
// Read 6 bytes: product number (4 bytes with CRC) + more data
// Product number is 32-bit: STC31 = 0x08010301
if (i2cBus->requestFrom(address, (uint8_t)6) != 6)
return false;
uint8_t response[6] = {0};
for (int i = 0; i < 6 && i2cBus->available(); ++i) {
response[i] = i2cBus->read();
}
uint32_t productId =
((uint32_t)response[0] << 24) | ((uint32_t)response[1] << 16) | ((uint32_t)response[3] << 8) | response[4];
return productId == 0x08010301;
}
#define SCAN_SIMPLE_CASE(ADDR, T, ...) \
case ADDR: \
logFoundDevice(__VA_ARGS__); \
@@ -532,13 +568,23 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
case TSL25911_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xA0 | 0x12), 1);
if (registerValue == 0x50) {
type = TSL2591;
logFoundDevice("TSL25911", (uint8_t)addr.address);
// TSL25911_ADDR (0x29) is shared by TSL2591, TSL2561, and STC31
// Probe for STC31 first (Sensirion CO2 sensor)
LOG_DEBUG("Probing address 0x29 for STC31...");
if (probeSTC31(i2cBus, addr.address)) {
type = STC31;
LOG_DEBUG("STC31 probe successful!");
logFoundDevice("STC31", (uint8_t)addr.address);
} else {
type = TSL2561;
logFoundDevice("TSL2561", (uint8_t)addr.address);
LOG_DEBUG("STC31 probe failed, checking for TSL sensors...");
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xA0 | 0x12), 1);
if (registerValue == 0x50) {
type = TSL2591;
logFoundDevice("TSL25911", (uint8_t)addr.address);
} else {
type = TSL2561;
logFoundDevice("TSL2561", (uint8_t)addr.address);
}
}
break;
@@ -548,6 +594,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(DFROBOT_RAIN_ADDR, DFROBOT_RAIN, "DFRobot Rain Gauge", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
case STC31_ADDR:
// RAK12008 uses address 0x2C for STC31
LOG_DEBUG("Probing address 0x2c for STC31...");
if (probeSTC31(i2cBus, addr.address)) {
type = STC31;
LOG_DEBUG("STC31 probe successful at 0x2c!");
logFoundDevice("STC31", (uint8_t)addr.address);
} else {
LOG_DEBUG("STC31 probe failed at 0x2c");
LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address);
}
break;
case CST328_ADDR:
// Do we have the CST328 or the CST226SE
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xAB), 1);
@@ -581,7 +639,17 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
case SCD4X_ADDR: {
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x8), 1);
if (registerValue == 0x18) {
logFoundDevice("CW2015", (uint8_t)addr.address);
type = CW2015;
} else {
logFoundDevice("SCD4X", (uint8_t)addr.address);
type = SCD4X;
}
break;
}
SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
#ifdef HAS_TPS65233
SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233", (uint8_t)addr.address);
-1
View File
@@ -221,7 +221,6 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
if (rtc_sec > 0) {
// === Build Time String ===
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
int hour, minute, second;
graphics::decomposeTime(rtc_sec, hour, minute, second);
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
+11 -3
View File
@@ -429,6 +429,10 @@ void VirtualKeyboard::drawKey(OLEDDisplay *display, const VirtualKey &key, bool
c = c - 'a' + 'A';
}
keyText = (key.character == ' ' || key.character == '_') ? "_" : std::string(1, c);
// Show the common "/" pairing next to "?" like on a real keyboard
if (key.type == VK_CHAR && key.character == '?') {
keyText = "?/";
}
}
int textWidth = display->getStringWidth(keyText.c_str());
@@ -518,9 +522,13 @@ char VirtualKeyboard::getCharForKey(const VirtualKey &key, bool isLongPress)
char c = key.character;
// Long-press: only keep letter lowercase->uppercase conversion; remove other symbol mappings
if (isLongPress && c >= 'a' && c <= 'z') {
c = (char)(c - 'a' + 'A');
// Long-press: letters become uppercase; for "?" provide "/" like a typical keyboard
if (isLongPress) {
if (c >= 'a' && c <= 'z') {
c = (char)(c - 'a' + 'A');
} else if (c == '?') {
c = '/';
}
}
return c;
+12
View File
@@ -1,3 +1,5 @@
#include "graphics/niche/InkHUD/Tile.h"
#include <cstdint>
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./Applet.h"
@@ -785,6 +787,16 @@ void InkHUD::Applet::drawHeader(std::string text)
drawPixel(x, 0, BLACK);
drawPixel(x, headerDivY, BLACK); // Dotted 50%
}
// Dither near battery
if (settings->optionalFeatures.batteryIcon) {
constexpr uint16_t ditherSizePx = 4;
Tile *batteryTile = ((Applet *)inkhud->getSystemApplet("BatteryIcon"))->getTile();
const uint16_t batteryTileLeft = batteryTile->getLeft();
const uint16_t batteryTileTop = batteryTile->getTop();
const uint16_t batteryTileHeight = batteryTile->getHeight();
hatchRegion(batteryTileLeft - ditherSizePx, batteryTileTop, ditherSizePx, batteryTileHeight, 2, WHITE);
}
}
// Get the height of the standard applet header
@@ -48,37 +48,27 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta
void InkHUD::BatteryIconApplet::onRender(bool full)
{
// Fill entire tile
// - size of icon controlled by size of tile
int16_t l = 0;
int16_t t = 0;
uint16_t w = width();
int16_t h = height();
// Clear the region beneath the tile
// Clear the region beneath the tile, including the border
// Most applets are drawing onto an empty frame buffer and don't need to do this
// We do need to do this with the battery though, as it is an "overlay"
fillRect(l, t, w, h, WHITE);
// Vertical centerline
const int16_t m = t + (h / 2);
fillRect(0, 0, width(), height(), WHITE);
// =====================
// Draw battery outline
// =====================
// Positive terminal "bump"
const int16_t &bumpL = l;
const uint16_t bumpH = h / 2;
const int16_t bumpT = m - (bumpH / 2);
constexpr uint16_t bumpW = 2;
const int16_t &bumpL = 1;
const uint16_t bumpH = (height() - 2) / 2;
const int16_t bumpT = (1 + ((height() - 2) / 2)) - (bumpH / 2);
fillRect(bumpL, bumpT, bumpW, bumpH, BLACK);
// Main body of battery
const int16_t bodyL = bumpL + bumpW;
const int16_t &bodyT = t;
const int16_t &bodyH = h;
const int16_t bodyW = w - bumpW;
const int16_t bodyL = 1 + bumpW;
const int16_t &bodyT = 1;
const int16_t &bodyH = height() - 2; // Handle top/bottom padding
const int16_t bodyW = (width() - 1) - bumpW; // Handle 1px left pad
drawRect(bodyL, bodyT, bodyW, bodyH, BLACK);
// Erase join between bump and body
@@ -89,12 +79,13 @@ void InkHUD::BatteryIconApplet::onRender(bool full)
// ===================
constexpr int16_t slicePad = 2;
const int16_t sliceL = bodyL + slicePad;
int16_t sliceL = bodyL + slicePad;
const int16_t sliceT = bodyT + slicePad;
const uint16_t sliceH = bodyH - (slicePad * 2);
uint16_t sliceW = bodyW - (slicePad * 2);
sliceW = (sliceW * socRounded) / 100; // Apply percentage
sliceW = (sliceW * socRounded) / 100; // Apply percentage
sliceL += ((bodyW - (slicePad * 2)) - sliceW); // Shift slice to the battery's negative terminal, correcting drain direction
hatchRegion(sliceL, sliceT, sliceW, sliceH, 2, BLACK);
drawRect(sliceL, sliceT, sliceW, sliceH, BLACK);
+4 -4
View File
@@ -510,10 +510,10 @@ void InkHUD::WindowManager::placeSystemTiles()
const uint16_t batteryIconWidth = batteryIconHeight * 1.8;
inkhud->getSystemApplet("BatteryIcon")
->getTile()
->setRegion(inkhud->width() - batteryIconWidth, // x
2, // y
batteryIconWidth, // width
batteryIconHeight); // height
->setRegion(inkhud->width() - batteryIconWidth - 1, // x
1, // y
batteryIconWidth + 1, // width
batteryIconHeight + 2); // height
// Note: the tiles of placeholder and menu applets are manipulated specially
// - menuApplet borrows user tiles
+20 -59
View File
@@ -7,13 +7,13 @@
#include "NodeDB.h"
#include "PowerFSM.h"
#include "PowerMon.h"
#include "RadioLibInterface.h"
#include "ReliableRouter.h"
#include "airtime.h"
#include "buzz.h"
#include "power/PowerHAL.h"
#include "FSCommon.h"
#include "Led.h"
#include "RTC.h"
#include "SPILock.h"
#include "Throttle.h"
@@ -193,6 +193,8 @@ bool kb_found = false;
// global bool to record that on-screen keyboard (OSK) is present
bool osk_found = false;
unsigned long last_listen = 0;
// The I2C address of the RTC Module (if found)
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
// The I2C address of the Accelerometer (if found)
@@ -242,26 +244,8 @@ const char *getDeviceName()
return name;
}
// TODO remove from main.cpp
static int32_t ledBlinker()
{
// Still set up the blinking (heartbeat) interval but skip code path below, so LED will blink if
// config.device.led_heartbeat_disabled is changed
if (config.device.led_heartbeat_disabled)
return 1000;
static bool ledOn;
ledOn ^= 1;
ledBlink.set(ledOn);
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
return powerStatus->getIsCharging() ? 1000 : (ledOn ? 1 : 1000);
}
uint32_t timeLastPowered = 0;
static Periodic *ledPeriodic;
static OSThread *powerFSMthread;
static OSThread *ambientLightingThread;
@@ -299,21 +283,16 @@ void earlyInitVariant() {}
// blink user led in 3 flashes sequence to indicate what is happening
void waitUntilPowerLevelSafe()
{
#ifdef LED_PIN
pinMode(LED_PIN, OUTPUT);
#endif
while (powerHAL_isPowerLevelSafe() == false) {
#ifdef LED_PIN
#ifdef LED_POWER
// 3x: blink for 300 ms, pause for 300 ms
for (int i = 0; i < 3; i++) {
digitalWrite(LED_PIN, LED_STATE_ON);
digitalWrite(LED_POWER, LED_STATE_ON);
delay(300);
digitalWrite(LED_PIN, LED_STATE_OFF);
digitalWrite(LED_POWER, LED_STATE_OFF);
delay(300);
}
#endif
@@ -337,6 +316,11 @@ void setup()
// initialize power HAL layer as early as possible
powerHAL_init();
#ifdef LED_POWER
pinMode(LED_POWER, OUTPUT);
digitalWrite(LED_POWER, LED_STATE_ON);
#endif
// prevent booting if device is in power failure mode
// boot sequence will follow when battery level raises to safe mode
waitUntilPowerLevelSafe();
@@ -349,11 +333,6 @@ void setup()
digitalWrite(PIN_POWER_EN, HIGH);
#endif
#ifdef LED_POWER
pinMode(LED_POWER, OUTPUT);
digitalWrite(LED_POWER, LED_STATE_ON);
#endif
#ifdef LED_NOTIFICATION
pinMode(LED_NOTIFICATION, OUTPUT);
digitalWrite(LED_NOTIFICATION, HIGH ^ LED_STATE_ON);
@@ -366,11 +345,7 @@ void setup()
#ifdef BLE_LED
pinMode(BLE_LED, OUTPUT);
#ifdef BLE_LED_INVERTED
digitalWrite(BLE_LED, HIGH);
#else
digitalWrite(BLE_LED, LOW);
#endif
digitalWrite(BLE_LED, LED_STATE_OFF);
#endif
concurrency::hasBeenSetup = true;
@@ -488,14 +463,6 @@ void setup()
OSThread::setup();
// TODO make this ifdef based on defined pins and move from main.cpp
#if defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2)
// The ThinkNodes have their own blink logic
// ledPeriodic = new Periodic("Blink", elecrowLedBlinker);
#else
ledPeriodic = new Periodic("Blink", ledBlinker);
#endif
fsInit();
#if !MESHTASTIC_EXCLUDE_I2C
@@ -713,19 +680,13 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::SCD4X, meshtastic_TelemetrySensorType_SCD4X);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::STC31, meshtastic_TelemetrySensorType_STC31);
#endif
#ifdef HAS_SDCARD
setupSDCard();
#endif
// LED init
#ifdef LED_PIN
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LED_STATE_ON); // turn on for now
#endif
// Hello
printInfo();
#ifdef BUILD_EPOCH
@@ -833,7 +794,7 @@ void setup()
SPI.begin();
#endif
#else
// ESP32
// ESP32
#if defined(HW_SPI1_DEVICE)
SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
@@ -966,12 +927,6 @@ void setup()
setupNicheGraphics();
#endif
#ifdef LED_PIN
// Turn LED off after boot, if heartbeat by config
if (config.device.led_heartbeat_disabled)
digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON);
#endif
// Do this after service.init (because that clears error_code)
#ifdef HAS_PMU
if (!pmu_found)
@@ -1168,6 +1123,12 @@ void loop()
#endif
power->powerCommandsCheck();
if (RadioLibInterface::instance != nullptr && !Throttle::isWithinTimespanMs(last_listen, 1000 * 60) &&
!(RadioLibInterface::instance->isSending() || RadioLibInterface::instance->isActivelyReceiving())) {
RadioLibInterface::instance->startReceive();
LOG_DEBUG("attempting AGC reset");
}
#ifdef DEBUG_STACK
static uint32_t lastPrint = 0;
if (!Throttle::isWithinTimespanMs(lastPrint, 10 * 1000L)) {
+1
View File
@@ -33,6 +33,7 @@ extern ScanI2C::DeviceAddress cardkb_found;
extern uint8_t kb_model;
extern bool kb_found;
extern bool osk_found;
extern unsigned long last_listen;
extern ScanI2C::DeviceAddress rtc_found;
extern ScanI2C::DeviceAddress accelerometer_found;
extern ScanI2C::FoundDevice rgb_found;
+10
View File
@@ -1408,6 +1408,15 @@ void NodeDB::loadFromDisk()
if (portduino_config.has_configDisplayMode) {
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode;
}
if (portduino_config.has_statusMessage) {
moduleConfig.has_statusmessage = true;
strncpy(moduleConfig.statusmessage.node_status, portduino_config.statusMessage.c_str(),
sizeof(moduleConfig.statusmessage.node_status));
moduleConfig.statusmessage.node_status[sizeof(moduleConfig.statusmessage.node_status) - 1] = '\0';
}
if (portduino_config.enable_UDP) {
config.network.enabled_protocols = true;
}
#endif
}
@@ -1548,6 +1557,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
moduleConfig.has_ambient_lighting = true;
moduleConfig.has_audio = true;
moduleConfig.has_paxcounter = true;
moduleConfig.has_statusmessage = true;
success &=
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
+2 -1
View File
@@ -82,7 +82,6 @@ template <class T> class ProtobufModule : protected SinglePortModule
// it would be better to update even if the message was destined to others.
auto &p = mp.decoded;
LOG_INFO("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d", name, mp.from, mp.id, p.portnum, p.payload.size);
T scratch;
T *decoded = NULL;
@@ -90,6 +89,8 @@ template <class T> class ProtobufModule : protected SinglePortModule
memset(&scratch, 0, sizeof(scratch));
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) {
decoded = &scratch;
LOG_INFO("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d", name, mp.from, mp.id, p.portnum,
p.payload.size);
} else {
LOG_ERROR("Error decoding proto module!");
// if we can't decode it, nobody can process it!
+2
View File
@@ -514,6 +514,8 @@ void RadioLibInterface::handleReceiveInterrupt()
void RadioLibInterface::startReceive()
{
// Note the updated timestamp, to avoid unneeded AGC resets
last_listen = millis();
isReceiving = true;
powerMon->setState(meshtastic_PowerMon_State_Lora_RXOn);
}
-43
View File
@@ -9,7 +9,6 @@
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#endif
#include "Led.h"
#include "SPILock.h"
#include "power.h"
#include "serialization/JSON.h"
@@ -92,7 +91,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
ResourceNode *nodeFormUpload = new ResourceNode("/upload", "POST", &handleFormUpload);
ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks);
ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED);
ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport);
ResourceNode *nodeJsonNodes = new ResourceNode("/json/nodes", "GET", &handleNodes);
ResourceNode *nodeJsonFsBrowseStatic = new ResourceNode("/json/fs/browse/static", "GET", &handleFsBrowseStatic);
@@ -110,7 +108,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
secureServer->registerNode(nodeRestart);
secureServer->registerNode(nodeFormUpload);
secureServer->registerNode(nodeJsonScanNetworks);
secureServer->registerNode(nodeJsonBlinkLED);
secureServer->registerNode(nodeJsonFsBrowseStatic);
secureServer->registerNode(nodeJsonDelete);
secureServer->registerNode(nodeJsonReport);
@@ -133,7 +130,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
insecureServer->registerNode(nodeRestart);
insecureServer->registerNode(nodeFormUpload);
insecureServer->registerNode(nodeJsonScanNetworks);
insecureServer->registerNode(nodeJsonBlinkLED);
insecureServer->registerNode(nodeJsonFsBrowseStatic);
insecureServer->registerNode(nodeJsonDelete);
insecureServer->registerNode(nodeJsonReport);
@@ -904,45 +900,6 @@ void handleRestart(HTTPRequest *req, HTTPResponse *res)
webServerThread->requestRestart = (millis() / 1000) + 5;
}
void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
{
res->setHeader("Content-Type", "application/json");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "POST");
ResourceParameters *params = req->getParams();
std::string blink_target;
if (!params->getQueryParameter("blink_target", blink_target)) {
// if no blink_target was supplied in the URL parameters of the
// POST request, then assume we should blink the LED
blink_target = "LED";
}
if (blink_target == "LED") {
uint8_t count = 10;
while (count > 0) {
ledBlink.set(true);
delay(50);
ledBlink.set(false);
delay(50);
count = count - 1;
}
} else {
#if HAS_SCREEN
if (screen)
screen->blink();
#endif
}
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
}
void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
{
res->setHeader("Content-Type", "application/json");
-1
View File
@@ -11,7 +11,6 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res);
void handleScanNetworks(HTTPRequest *req, HTTPResponse *res);
void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res);
void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res);
void handleBlinkLED(HTTPRequest *req, HTTPResponse *res);
void handleReport(HTTPRequest *req, HTTPResponse *res);
void handleNodes(HTTPRequest *req, HTTPResponse *res);
void handleUpdateFs(HTTPRequest *req, HTTPResponse *res);
+15 -9
View File
@@ -643,12 +643,6 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
accelerometerThread->enabled = true;
accelerometerThread->start();
}
#endif
#ifdef LED_PIN
// Turn LED off if heartbeat by config
if (c.payload_variant.device.led_heartbeat_disabled) {
digitalWrite(LED_PIN, HIGH ^ LED_STATE_ON);
}
#endif
if (config.device.button_gpio == c.payload_variant.device.button_gpio &&
config.device.buzzer_gpio == c.payload_variant.device.buzzer_gpio &&
@@ -905,10 +899,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
{
bool shouldReboot = true;
// If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth
// Otherwise, disable Bluetooth to prevent the phone from interfering with the config
if (!hasOpenEditTransaction &&
!IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, meshtastic_ModuleConfig_serial_tag)) {
if (!hasOpenEditTransaction && !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag,
meshtastic_ModuleConfig_serial_tag, meshtastic_ModuleConfig_statusmessage_tag)) {
disableBluetooth();
}
@@ -1000,8 +995,14 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
moduleConfig.has_paxcounter = true;
moduleConfig.paxcounter = c.payload_variant.paxcounter;
break;
case meshtastic_ModuleConfig_statusmessage_tag:
LOG_INFO("Set module config: StatusMessage");
moduleConfig.has_statusmessage = true;
moduleConfig.statusmessage = c.payload_variant.statusmessage;
shouldReboot = false;
break;
}
saveChanges(SEGMENT_MODULECONFIG);
saveChanges(SEGMENT_MODULECONFIG, shouldReboot);
return true;
}
@@ -1180,6 +1181,11 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter;
break;
case meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG:
LOG_INFO("Get module config: StatusMessage");
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_statusmessage_tag;
res.get_module_config_response.payload_variant.statusmessage = moduleConfig.statusmessage;
break;
}
// NOTE: The phone app needs to know the ls_secsvalue so it can properly expect sleep behavior.
+12 -3
View File
@@ -1,9 +1,12 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
#include "buzz/BuzzerFeedbackThread.h"
#include "modules/StatusLEDModule.h"
#include "modules/SystemCommandsModule.h"
#endif
#include "modules/StatusLEDModule.h"
#if !MESHTASTIC_EXCLUDE_REPLYBOT
#include "ReplyBotModule.h"
#endif
#if !MESHTASTIC_EXCLUDE_PKI
#include "KeyVerificationModule.h"
#endif
@@ -90,6 +93,9 @@
#if !MESHTASTIC_EXCLUDE_DROPZONE
#include "modules/DropzoneModule.h"
#endif
#if !MESHTASTIC_EXCLUDE_STATUS
#include "modules/StatusMessageModule.h"
#endif
#if defined(HAS_HARDWARE_WATCHDOG)
#include "watchdog/watchdogThread.h"
@@ -106,10 +112,10 @@ void setupModules()
buzzerFeedbackThread = new BuzzerFeedbackThread();
}
#endif
#if defined(LED_CHARGE) || defined(LED_PAIRING)
statusLEDModule = new StatusLEDModule();
#if !MESHTASTIC_EXCLUDE_REPLYBOT
new ReplyBotModule();
#endif
#if !MESHTASTIC_EXCLUDE_ADMIN
adminModule = new AdminModule();
#endif
@@ -150,6 +156,9 @@ void setupModules()
#if !MESHTASTIC_EXCLUDE_DROPZONE
dropzoneModule = new DropzoneModule();
#endif
#if !MESHTASTIC_EXCLUDE_STATUS
statusMessageModule = new StatusMessageModule();
#endif
#if !MESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE
new GenericThreadModule();
#endif
+4 -3
View File
@@ -1,5 +1,4 @@
#include "PowerStressModule.h"
#include "Led.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerMon.h"
@@ -78,10 +77,12 @@ int32_t PowerStressModule::runOnce()
switch (p.cmd) {
case meshtastic_PowerStressMessage_Opcode_LED_ON:
ledForceOn.set(true);
// FIXME - implement
// ledForceOn.set(true);
break;
case meshtastic_PowerStressMessage_Opcode_LED_OFF:
ledForceOn.set(false);
// FIXME - implement
// ledForceOn.set(false);
break;
case meshtastic_PowerStressMessage_Opcode_GPS_ON:
// FIXME - implement
+183
View File
@@ -0,0 +1,183 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_REPLYBOT
/*
* ReplyBotModule.cpp
*
* This module implements a simple reply bot for the Meshtastic firmware. It listens for
* specific text commands ("/ping", "/hello" and "/test") delivered either via a direct
* message (DM) or a broadcast on the primary channel. When a supported command is
* received the bot responds with a short status message that includes the hop count
* (minimum number of relays), RSSI and SNR of the received packet. To avoid spamming
* the network it enforces a per‑sender cooldown between responses. By default the
* module is disabled. See the official firmware documentation for guidance on adding modules.
* To enable this module, set `#undef MESHTASTIC_EXCLUDE_REPLYBOT` in your variant.h file.
*/
#include "Channels.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "ReplyBotModule.h"
#include "mesh/MeshTypes.h"
#include <Arduino.h>
#include <cctype>
#include <cstring>
//
// Rate limiting data structures
//
// Each sender is tracked in a small ring buffer. When a message arrives from a
// sender we check the last time we responded to them. If the difference is
// less than the configured cooldown (different values for DM vs broadcast)
// the message is ignored; otherwise we update the last response time and
// proceed with replying.
struct ReplyBotCooldownEntry {
uint32_t from = 0;
uint32_t lastMs = 0;
};
static constexpr uint8_t REPLYBOT_COOLDOWN_SLOTS = 8; // ring buffer size
static constexpr uint32_t REPLYBOT_DM_COOLDOWN_MS = 15 * 1000; // 15 seconds for DMs
static constexpr uint32_t REPLYBOT_LF_COOLDOWN_MS = 60 * 1000; // 60 seconds for LongFast broadcasts
static ReplyBotCooldownEntry replybotCooldown[REPLYBOT_COOLDOWN_SLOTS];
static uint8_t replybotCooldownIdx = 0;
// Return true if a reply should be rate‑limited for this sender, updating the
// entry table as needed.
static bool replybotRateLimited(uint32_t from, uint32_t cooldownMs)
{
const uint32_t now = millis();
for (auto &e : replybotCooldown) {
if (e.from == from) {
// Found existing entry; check if cooldown expired
if ((uint32_t)(now - e.lastMs) < cooldownMs) {
return true;
}
e.lastMs = now;
return false;
}
}
// No entry found – insert new sender into the ring
replybotCooldown[replybotCooldownIdx].from = from;
replybotCooldown[replybotCooldownIdx].lastMs = now;
replybotCooldownIdx = (replybotCooldownIdx + 1) % REPLYBOT_COOLDOWN_SLOTS;
return false;
}
// Constructor – registers a single text port and marks the module promiscuous
// so that broadcast messages on the primary channel are visible.
ReplyBotModule::ReplyBotModule() : SinglePortModule("replybot", meshtastic_PortNum_TEXT_MESSAGE_APP)
{
isPromiscuous = true;
}
void ReplyBotModule::setup()
{
// In future we may add a protobuf configuration; for now the module is
// always enabled when compiled in.
}
// Determine whether we want to process this packet. We only care about
// plain text messages addressed to our port.
bool ReplyBotModule::wantPacket(const meshtastic_MeshPacket *p)
{
return (p && p->decoded.portnum == ourPortNum);
}
ProcessMessage ReplyBotModule::handleReceived(const meshtastic_MeshPacket &mp)
{
// Accept only direct messages to us or broadcasts on the Primary channel
// (regardless of modem preset: LongFast, MediumFast, etc).
const uint32_t ourNode = nodeDB->getNodeNum();
const bool isDM = (mp.to == ourNode);
const bool isPrimaryChannel = (mp.channel == channels.getPrimaryIndex()) && isBroadcast(mp.to);
if (!isDM && !isPrimaryChannel) {
return ProcessMessage::CONTINUE;
}
// Ignore empty payloads
if (mp.decoded.payload.size == 0) {
return ProcessMessage::CONTINUE;
}
// Copy payload into a null‑terminated buffer
char buf[260];
memset(buf, 0, sizeof(buf));
size_t n = mp.decoded.payload.size;
if (n > sizeof(buf) - 1)
n = sizeof(buf) - 1;
memcpy(buf, mp.decoded.payload.bytes, n);
// React only to supported slash commands
if (!isCommand(buf)) {
return ProcessMessage::CONTINUE;
}
// Apply rate limiting per sender depending on DM/broadcast
const uint32_t cooldownMs = isDM ? REPLYBOT_DM_COOLDOWN_MS : REPLYBOT_LF_COOLDOWN_MS;
if (replybotRateLimited(mp.from, cooldownMs)) {
return ProcessMessage::CONTINUE;
}
// Compute hop count indicator – if the relay_node is non‑zero we know
// there was at least one relay. Some firmware builds support a hop_start
// field which could be used for more accurate counts, but here we use
// the available relay_node flag only.
// int hopsAway = mp.hop_start - mp.hop_limit;
int hopsAway = getHopsAway(mp);
// Normalize RSSI: if positive adjust down by 200 to align with typical values
int rssi = mp.rx_rssi;
if (rssi > 0) {
rssi -= 200;
}
float snr = mp.rx_snr;
// Build the reply message and send it back via DM
char reply[96];
snprintf(reply, sizeof(reply), "🎙️ Mic Check : %d Hops away | RSSI %d | SNR %.1f", hopsAway, rssi, snr);
sendDm(mp, reply);
return ProcessMessage::CONTINUE;
}
// Check if the message starts with one of the supported commands. Leading
// whitespace is skipped and commands must be followed by end‑of‑string or
// whitespace.
bool ReplyBotModule::isCommand(const char *msg) const
{
if (!msg)
return false;
while (*msg == ' ' || *msg == '\t')
msg++;
auto isEndOrSpace = [](char c) { return c == '\0' || std::isspace(static_cast<unsigned char>(c)); };
if (strncmp(msg, "/ping", 5) == 0 && isEndOrSpace(msg[5]))
return true;
if (strncmp(msg, "/hello", 6) == 0 && isEndOrSpace(msg[6]))
return true;
if (strncmp(msg, "/test", 5) == 0 && isEndOrSpace(msg[5]))
return true;
return false;
}
// Send a direct message back to the originating node.
void ReplyBotModule::sendDm(const meshtastic_MeshPacket &rx, const char *text)
{
if (!text)
return;
meshtastic_MeshPacket *p = allocDataPacket();
p->to = rx.from;
p->channel = rx.channel;
p->want_ack = false;
p->decoded.want_response = false;
size_t len = strlen(text);
if (len > sizeof(p->decoded.payload.bytes)) {
len = sizeof(p->decoded.payload.bytes);
}
p->decoded.payload.size = len;
memcpy(p->decoded.payload.bytes, text, len);
service->sendToMesh(p);
}
#endif // MESHTASTIC_EXCLUDE_REPLYBOT
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_REPLYBOT
#include "SinglePortModule.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
class ReplyBotModule : public SinglePortModule
{
public:
ReplyBotModule();
void setup() override;
bool wantPacket(const meshtastic_MeshPacket *p) override;
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
protected:
bool isCommand(const char *msg) const;
void sendDm(const meshtastic_MeshPacket &rx, const char *text);
};
#endif // MESHTASTIC_EXCLUDE_REPLYBOT
+77 -7
View File
@@ -13,8 +13,10 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule")
{
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
powerStatusObserver.observe(&powerStatus->onNewStatus);
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
if (inputBroker)
inputObserver.observe(inputBroker);
#endif
}
int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
@@ -62,19 +64,22 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
}
return 0;
};
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
int StatusLEDModule::handleInputEvent(const InputEvent *event)
{
lastUserbuttonTime = millis();
return 0;
}
#endif
int32_t StatusLEDModule::runOnce()
{
my_interval = 1000;
if (power_state == charging) {
#ifndef POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING
CHARGE_LED_state = !CHARGE_LED_state;
#endif
} else if (power_state == charged) {
CHARGE_LED_state = LED_STATE_ON;
} else if (power_state == critical) {
@@ -88,13 +93,19 @@ int32_t StatusLEDModule::runOnce()
my_interval = 250;
if (POWER_LED_starttime + 2000 < millis()) {
doing_fast_blink = false;
CHARGE_LED_state = LED_STATE_OFF;
}
} else {
CHARGE_LED_state = LED_STATE_OFF;
}
}
} else {
CHARGE_LED_state = LED_STATE_OFF;
if (power_state != charging && power_state != charged && !doing_fast_blink) {
if (CHARGE_LED_state == LED_STATE_ON) {
CHARGE_LED_state = LED_STATE_OFF;
my_interval = 999;
} else {
CHARGE_LED_state = LED_STATE_ON;
my_interval = 1;
}
}
if (!config.bluetooth.enabled || PAIRING_LED_starttime + 30 * 1000 < millis() || doing_fast_blink) {
@@ -112,6 +123,11 @@ int32_t StatusLEDModule::runOnce()
PAIRING_LED_state = LED_STATE_ON;
}
// Override if disabled in config
if (config.device.led_heartbeat_disabled) {
CHARGE_LED_state = LED_STATE_OFF;
}
#ifdef Battery_LED_1
bool chargeIndicatorLED1 = LED_STATE_OFF;
bool chargeIndicatorLED2 = LED_STATE_OFF;
bool chargeIndicatorLED3 = LED_STATE_OFF;
@@ -126,9 +142,23 @@ int32_t StatusLEDModule::runOnce()
if (powerStatus && powerStatus->getBatteryChargePercent() >= 75)
chargeIndicatorLED4 = LED_STATE_ON;
}
#endif
#ifdef LED_CHARGE
digitalWrite(LED_CHARGE, CHARGE_LED_state);
#if defined(HAS_PMU)
if (pmu_found && PMU) {
// blink the axp led
PMU->setChargingLedMode(CHARGE_LED_state ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF);
}
#endif
#ifdef PCA_LED_POWER
io.digitalWrite(PCA_LED_POWER, CHARGE_LED_state);
#endif
#ifdef PCA_LED_ENABLE
io.digitalWrite(PCA_LED_ENABLE, CHARGE_LED_state);
#endif
#ifdef LED_POWER
digitalWrite(LED_POWER, CHARGE_LED_state);
#endif
#ifdef LED_PAIRING
digitalWrite(LED_PAIRING, PAIRING_LED_state);
@@ -149,3 +179,43 @@ int32_t StatusLEDModule::runOnce()
return (my_interval);
}
void StatusLEDModule::setPowerLED(bool LEDon)
{
#if defined(HAS_PMU)
if (pmu_found && PMU) {
// blink the axp led
PMU->setChargingLedMode(LEDon ? XPOWERS_CHG_LED_ON : XPOWERS_CHG_LED_OFF);
}
#endif
if (LEDon)
LEDon = LED_STATE_ON;
else
LEDon = LED_STATE_OFF;
#ifdef PCA_LED_POWER
io.digitalWrite(PCA_LED_POWER, LEDon);
#endif
#ifdef PCA_LED_ENABLE
io.digitalWrite(PCA_LED_ENABLE, LEDon);
#endif
#ifdef LED_POWER
digitalWrite(LED_POWER, LEDon);
#endif
#ifdef LED_PAIRING
digitalWrite(LED_PAIRING, LEDon);
#endif
#ifdef Battery_LED_1
digitalWrite(Battery_LED_1, LEDon);
#endif
#ifdef Battery_LED_2
digitalWrite(Battery_LED_2, LEDon);
#endif
#ifdef Battery_LED_3
digitalWrite(Battery_LED_3, LEDon);
#endif
#ifdef Battery_LED_4
digitalWrite(Battery_LED_4, LEDon);
#endif
}
+11 -2
View File
@@ -5,10 +5,14 @@
#include "PowerStatus.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#include "input/InputBroker.h"
#include "main.h"
#include <Arduino.h>
#include <functional>
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
#include "input/InputBroker.h"
#endif
class StatusLEDModule : private concurrency::OSThread
{
bool slowTrack = false;
@@ -17,8 +21,11 @@ class StatusLEDModule : private concurrency::OSThread
StatusLEDModule();
int handleStatusUpdate(const meshtastic::Status *);
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
int handleInputEvent(const InputEvent *arg);
#endif
void setPowerLED(bool);
protected:
unsigned int my_interval = 1000; // interval in millisconds
@@ -28,8 +35,10 @@ 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);
#if !MESHTASTIC_EXCLUDE_INPUTBROKER
CallbackObserver<StatusLEDModule, const InputEvent *> inputObserver =
CallbackObserver<StatusLEDModule, const InputEvent *>(this, &StatusLEDModule::handleInputEvent);
#endif
private:
bool CHARGE_LED_state = LED_STATE_OFF;
+41
View File
@@ -0,0 +1,41 @@
#if !MESHTASTIC_EXCLUDE_STATUS
#include "StatusMessageModule.h"
#include "MeshService.h"
#include "ProtobufModule.h"
StatusMessageModule *statusMessageModule;
int32_t StatusMessageModule::runOnce()
{
if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') {
// create and send message with the status message set
meshtastic_StatusMessage ourStatus = meshtastic_StatusMessage_init_zero;
strncpy(ourStatus.status, moduleConfig.statusmessage.node_status, sizeof(ourStatus.status));
ourStatus.status[sizeof(ourStatus.status) - 1] = '\0'; // ensure null termination
meshtastic_MeshPacket *p = allocDataPacket();
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes),
meshtastic_StatusMessage_fields, &ourStatus);
p->to = NODENUM_BROADCAST;
p->decoded.want_response = false;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
p->channel = 0;
service->sendToMesh(p);
}
return 1000 * 12 * 60 * 60;
}
ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
{
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
meshtastic_StatusMessage incomingMessage;
if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StatusMessage_fields,
&incomingMessage)) {
LOG_INFO("Received a NodeStatus message %s", incomingMessage.status);
}
}
return ProcessMessage::CONTINUE;
}
#endif
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#if !MESHTASTIC_EXCLUDE_STATUS
#include "SinglePortModule.h"
#include "configuration.h"
class StatusMessageModule : public SinglePortModule, private concurrency::OSThread
{
public:
/** Constructor
* name is for debugging output
*/
StatusMessageModule()
: SinglePortModule("statusMessage", meshtastic_PortNum_NODE_STATUS_APP), concurrency::OSThread("StatusMessage")
{
if (moduleConfig.has_statusmessage && moduleConfig.statusmessage.node_status[0] != '\0') {
this->setInterval(2 * 60 * 1000);
} else {
this->setInterval(1000 * 12 * 60 * 60);
}
// TODO: If we have a string, set the initial delay (15 minutes maybe)
}
virtual int32_t runOnce() override;
protected:
/** Called to handle a particular incoming message
*/
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
private:
};
extern StatusMessageModule *statusMessageModule;
#endif
+22 -10
View File
@@ -25,9 +25,14 @@
#if __has_include(<SensirionI2cScd4x.h>)
#include "Sensor/SCD4XSensor.h"
#endif
#if __has_include(<SensirionI2cStc3x.h>)
#include "Sensor/STC31Sensor.h"
#endif
void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
{
LOG_DEBUG("AirQualityTelemetryModule::i2cScanFinished called, air_quality_enabled=%d",
moduleConfig.telemetry.air_quality_enabled);
if (!moduleConfig.telemetry.air_quality_enabled && !AIR_QUALITY_TELEMETRY_MODULE_ENABLE) {
return;
}
@@ -50,6 +55,9 @@ void AirQualityTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include(<SensirionI2cScd4x.h>)
addSensor<SCD4XSensor>(i2cScanner, ScanI2C::DeviceType::SCD4X);
#endif
#if __has_include(<SensirionI2cStc3x.h>)
addSensor<STC31Sensor>(i2cScanner, ScanI2C::DeviceType::STC31);
#endif
}
int32_t AirQualityTelemetryModule::runOnce()
@@ -92,6 +100,10 @@ int32_t AirQualityTelemetryModule::runOnce()
return disable();
}
bool isSensorRole = config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR;
// SENSOR role bypasses airtime restrictions - telemetry is its primary purpose
bool airTimeAllows = isSensorRole || (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil());
// Wake up the sensors that need it
LOG_INFO("Waking up sensors...");
for (TelemetrySensor *sensor : sensors) {
@@ -102,8 +114,7 @@ int32_t AirQualityTelemetryModule::runOnce()
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()) {
airTimeAllows) {
if (!sensor->isActive()) {
LOG_DEBUG("Waking up: %s", sensor->sensorName);
return sensor->wakeUp();
@@ -117,18 +128,19 @@ int32_t AirQualityTelemetryModule::runOnce()
}
}
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()) {
bool timeToSend = (lastSentToMesh == 0) ||
!Throttle::isWithinTimespanMs(
lastSentToMesh,
Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.air_quality_interval,
default_telemetry_broadcast_interval_secs, numOnlineNodes));
if (timeToSend && airTimeAllows) {
sendTelemetry();
lastSentToMesh = millis();
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
(service->isToPhoneQueueEmpty())) {
(isSensorRole || 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)
// SENSOR role always sends; others only send while queue is empty (phone assumed connected)
sendTelemetry(NODENUM_BROADCAST, true);
lastSentToPhone = millis();
}
@@ -26,6 +26,7 @@
// Sensors
#include "Sensor/CGRadSensSensor.h"
#include "Sensor/RCWL9620Sensor.h"
#include "Sensor/TelemetrySensor.h"
#include "Sensor/nullSensor.h"
namespace graphics
@@ -571,6 +572,13 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
valid = valid || get_metrics;
hasSensor = true;
#endif
// Update shared humidity for cross-sensor compensation (e.g., STC31 CO2 sensor)
if (m->variant.environment_metrics.has_relative_humidity) {
lastEnvironmentHumidity = m->variant.environment_metrics.relative_humidity;
hasValidHumidity = true;
}
return valid && hasSensor;
}
@@ -0,0 +1,137 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include(<SensirionI2cStc3x.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "STC31Sensor.h"
#define STC31_NO_ERROR 0
// Binary gas configurations from STC31 datasheet
// See: setBinaryGas() - section 3.3.2 in the datasheet
#define STC31_BINARY_GAS_CO2_N2_100 0x0000 // CO2 in N2, 0-100 vol%
#define STC31_BINARY_GAS_CO2_AIR_100 0x0001 // CO2 in air, 0-100 vol%
#define STC31_BINARY_GAS_CO2_N2_25 0x0002 // CO2 in N2, 0-25 vol%
#define STC31_BINARY_GAS_CO2_AIR_25 0x0003 // CO2 in air, 0-25 vol%
STC31Sensor::STC31Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_STC31, "STC31") {}
bool STC31Sensor::configureBinaryGas()
{
// STC31 requires binary gas mode to be set before measurements
// It resets to default mode (no gas selected) after power cycle or reset
LOG_DEBUG("%s: Configuring binary gas mode (CO2 in air, 0-25%%)", sensorName);
int16_t error = stc3x.setBinaryGas(STC31_BINARY_GAS_CO2_AIR_25);
if (error != STC31_NO_ERROR) {
LOG_ERROR("%s: Failed to set binary gas mode, error: %d", sensorName, error);
return false;
}
LOG_DEBUG("%s: Binary gas mode configured successfully", sensorName);
binaryGasConfigured = true;
return true;
}
bool STC31Sensor::setHumidityCompensation()
{
// Use actual humidity from environment sensor if available, otherwise default to 50%
float humidity = hasValidHumidity ? lastEnvironmentHumidity : 50.0f;
LOG_DEBUG("%s: Setting humidity compensation to %.1f%%", sensorName, humidity);
int16_t error = stc3x.setRelativeHumidity(humidity);
if (error != STC31_NO_ERROR) {
LOG_WARN("%s: Failed to set humidity compensation (%.1f%%), error: %d", sensorName, humidity, error);
return false;
}
return true;
}
bool STC31Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
LOG_INFO("Init sensor: %s at address 0x%02x", sensorName, dev->address.address);
_bus = bus;
_address = dev->address.address;
stc3x.begin(*_bus, _address);
// Allow sensor to fully stabilize after power-on
delay(100);
// Run self-test to verify sensor is working
STC3xTestResultT selfTestResult;
int16_t testError = stc3x.selfTest(selfTestResult);
if (testError != STC31_NO_ERROR) {
LOG_WARN("%s: Self-test command failed with error: %d", sensorName, testError);
} else if (selfTestResult.value != 0) {
LOG_WARN("%s: Self-test reported error: 0x%04x", sensorName, selfTestResult.value);
} else {
LOG_DEBUG("%s: Self-test passed", sensorName);
}
// Wait after self-test before configuring
delay(50);
if (!configureBinaryGas())
return false;
setHumidityCompensation(); // Non-fatal if this fails
status = 1;
initI2CSensor();
return true;
}
bool STC31Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
float gasConcentration = 0;
float temperature = 0;
// Binary gas mode is configured once during initDevice() and persists
// Humidity compensation can be updated for each measurement
setHumidityCompensation(); // Non-fatal if this fails
// Small delay for humidity compensation to take effect
delay(10);
// Retry measurement up to 3 times with increasing delays
int16_t error = STC31_NO_ERROR;
for (int attempt = 0; attempt < 3; attempt++) {
error = stc3x.measureGasConcentration(gasConcentration, temperature);
if (error == STC31_NO_ERROR)
break;
LOG_WARN("%s: Measurement attempt %d failed (error %d), retrying...", sensorName, attempt + 1, error);
// If we get NotEnoughDataError (0x00F in error), the sensor may have lost its config
// Try re-configuring binary gas mode
if ((error & 0x00F) == 0x00F && attempt == 1) {
LOG_WARN("%s: Sensor may have lost configuration, re-configuring binary gas mode", sensorName);
configureBinaryGas();
delay(100);
} else {
delay(100 * (attempt + 1));
}
}
if (error != STC31_NO_ERROR) {
LOG_ERROR("%s: Error reading measurement after retries: %d", sensorName, error);
// Mark that we need to re-configure on next attempt
binaryGasConfigured = false;
return false;
}
// Convert percentage to ppm: 1% = 10000 ppm
uint32_t co2_ppm = (uint32_t)(gasConcentration * 10000.0f);
LOG_DEBUG("%s readings: %.2f%% CO2 (=%u ppm), %.2f degC", sensorName, gasConcentration, co2_ppm, temperature);
measurement->variant.air_quality_metrics.has_co2 = true;
measurement->variant.air_quality_metrics.co2 = co2_ppm;
measurement->variant.air_quality_metrics.has_co2_temperature = true;
measurement->variant.air_quality_metrics.co2_temperature = temperature;
return true;
}
#endif
@@ -0,0 +1,26 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR && __has_include(<SensirionI2cStc3x.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include <SensirionI2cStc3x.h>
class STC31Sensor : public TelemetrySensor
{
private:
SensirionI2cStc3x stc3x;
TwoWire *_bus{};
uint8_t _address{};
bool binaryGasConfigured = false;
bool configureBinaryGas();
bool setHumidityCompensation();
public:
STC31Sensor();
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
};
#endif
@@ -7,4 +7,9 @@
#include "TelemetrySensor.h"
#include "main.h"
// Shared humidity value for cross-sensor compensation
// Default to 50% if no humidity sensor is available
float lastEnvironmentHumidity = 50.0f;
bool hasValidHumidity = false;
#endif
@@ -16,6 +16,11 @@ class TwoWire;
#define DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS 1000
extern std::pair<uint8_t, TwoWire *> nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1];
// Shared humidity value for cross-sensor compensation (e.g., STC31 CO2 sensor)
// Updated by environment sensors that measure humidity
extern float lastEnvironmentHumidity;
extern bool hasValidHumidity;
class TelemetrySensor
{
protected:
+1 -5
View File
@@ -757,11 +757,7 @@ void NimbleBluetooth::deinit()
isDeInit = true;
#ifdef BLE_LED
#ifdef BLE_LED_INVERTED
digitalWrite(BLE_LED, HIGH);
#else
digitalWrite(BLE_LED, LOW);
#endif
digitalWrite(BLE_LED, LED_STATE_OFF);
#endif
#ifndef NIMBLE_TWO
NimBLEDevice::deinit();
+1 -1
View File
@@ -158,7 +158,7 @@
#endif
#ifdef PIN_LED1
#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK
#define LED_POWER PIN_LED1 // LED1 on nrf52840-DK
#endif
#ifdef PIN_BUTTON1
+8
View File
@@ -872,6 +872,7 @@ bool loadConfig(const char *configPath)
}
if (yamlConfig["Config"]) {
portduino_config.has_config_overrides = true;
if (yamlConfig["Config"]["DisplayMode"]) {
portduino_config.has_configDisplayMode = true;
if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "TWOCOLOR") {
@@ -884,6 +885,13 @@ bool loadConfig(const char *configPath)
portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
}
}
if (yamlConfig["Config"]["StatusMessage"]) {
portduino_config.has_statusMessage = true;
portduino_config.statusMessage = (yamlConfig["Config"]["StatusMessage"]).as<std::string>("");
}
if ((yamlConfig["Config"]["EnableUDP"]).as<bool>(false)) {
portduino_config.enable_UDP = true;
}
}
if (yamlConfig["General"]) {
+27 -14
View File
@@ -177,8 +177,12 @@ extern struct portduino_config_struct {
int hostMetrics_channel = 0;
// config
bool has_config_overrides = false;
int configDisplayMode = 0;
bool has_configDisplayMode = false;
std::string statusMessage = "";
bool has_statusMessage = false;
bool enable_UDP = false;
// General
std::string mac_address = "";
@@ -505,21 +509,30 @@ extern struct portduino_config_struct {
}
// config
if (has_configDisplayMode) {
if (has_config_overrides) {
out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap;
switch (configDisplayMode) {
case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR:
out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR";
break;
case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED:
out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED";
break;
case meshtastic_Config_DisplayConfig_DisplayMode_COLOR:
out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR";
break;
case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT:
out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT";
break;
if (has_configDisplayMode) {
switch (configDisplayMode) {
case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR:
out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR";
break;
case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED:
out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED";
break;
case meshtastic_Config_DisplayConfig_DisplayMode_COLOR:
out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR";
break;
case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT:
out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT";
break;
}
}
if (has_statusMessage) {
out << YAML::Key << "StatusMessage" << YAML::Value << statusMessage;
}
if (enable_UDP) {
out << YAML::Key << "EnableUDP" << YAML::Value << true;
}
out << YAML::EndMap; // Config
+4 -2
View File
@@ -103,8 +103,10 @@ class Power : private concurrency::OSThread
bool axpChipInit();
/// Setup a simple ADC input based battery sensor
bool analogInit();
/// Setup a Lipo battery level sensor
bool lipoInit();
/// Setup cw2015 battery level sensor
bool cw2015Init();
/// Setup a 17048 battery level sensor
bool max17048Init();
/// Setup a Lipo charger
bool lipoChargerInit();
/// Setup a meshSolar battery sensor
+2 -3
View File
@@ -5,7 +5,6 @@
#endif
#include "Default.h"
#include "Led.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
@@ -13,6 +12,7 @@
#include "detect/LoRaRadioType.h"
#include "error.h"
#include "main.h"
#include "modules/StatusLEDModule.h"
#include "sleep.h"
#include "target_specific.h"
@@ -268,8 +268,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
digitalWrite(PIN_WD_EN, LOW);
#endif
#endif
ledBlink.set(false);
statusLEDModule->setPowerLED(false);
#ifdef RESET_OLED
digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power
#endif
@@ -14,7 +14,7 @@
#define LORA_CS 5
#define RF95_FAN_EN 17
// #define LED_PIN 16 // This is a LED_WS2812 not a standard LED
// This is a LED_WS2812 not a standard LED
#define HAS_NEOPIXEL // Enable the use of neopixels
#define NEOPIXEL_COUNT 1 // How many neopixels are connected
#define NEOPIXEL_DATA 16 // gpio pin used to send data to the neopixels
+1 -1
View File
@@ -20,7 +20,7 @@
#define LORA_DIO2
#define LORA_DIO3
#define LED_PIN 16 // green - blue is at 17
#define LED_POWER 16 // green - blue is at 17
#define BUTTON_PIN 25
#define BUTTON_NEED_PULLUP
-2
View File
@@ -23,8 +23,6 @@
#define SX126X_TXEN RADIOLIB_NC
#define SX126X_RXEN RADIOLIB_NC
// Status
// #define LED_PIN 1
// External notification
// FIXME: Check if EXT_NOTIFY_OUT actualy has any effect and removes the need for setting the external notication pin in the
// app/preferences
@@ -21,8 +21,8 @@
#define BUTTON_PIN 15 // Right side button - if not available, set device.button_gpio to 0 from Meshtastic client
// LEDs
#define LED_PIN 13 // Tx LED
#define USER_LED 2 // Rx LED
#define LED_POWER 13 // Tx LED
#define USER_LED 2 // Rx LED
// Buzzer
#define PIN_BUZZER 33
+1 -1
View File
@@ -15,7 +15,7 @@
#define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k)
#define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards
#define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975).
#define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets)
#define LED_POWER 2 // add status LED (compatible with core-pcb and DIY targets)
// Radio
#define USE_SX1262 // E22-900M30S uses SX1262
+1 -1
View File
@@ -15,7 +15,7 @@
#define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k)
#define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards
#define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975).
#define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets)
#define LED_POWER 2 // add status LED (compatible with core-pcb and DIY targets)
#define LORA_DIO0 26 // a No connect on the SX1262/SX1268 module
#define LORA_RESET 23 // RST for SX1276, and for SX1262/SX1268
@@ -3,7 +3,7 @@
// HACKBOX LoRa IO Kit
// Uses a ESP-32-WROOM and a RA-01SH (SX1262) LoRa Board
#define LED_PIN 2 // LED
#define LED_POWER 2 // LED
#define LED_STATE_ON 1 // State when LED is lit
#define HAS_SCREEN 0
+1 -1
View File
@@ -12,7 +12,7 @@
#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller
#define LED_PIN 25 // If defined we will blink this LED
#define LED_POWER 25 // If defined we will blink this LED
#define BUTTON_PIN 0 // If defined, this will be used for user button presses
#define USE_RF95
+1 -1
View File
@@ -18,7 +18,7 @@
#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller
#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
#define LED_PIN 25 // If defined we will blink this LED
#define LED_POWER 25 // If defined we will blink this LED
#define BUTTON_PIN 0 // If defined, this will be used for user button presses
#define USE_RF95
+1 -1
View File
@@ -13,7 +13,7 @@
#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller
#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
#define LED_PIN 25 // If defined we will blink this LED
#define LED_POWER 25 // If defined we will blink this LED
#define BUTTON_PIN 0 // If defined, this will be used for user button presses
#define USE_RF95
@@ -15,7 +15,7 @@
#undef GPS_TX_PIN
// Green / Lora = PIN 22 / GPIO2, Yellow / Wifi = PIN 23 / GPIO0, Blue / BLE = PIN 25 / GPIO16
#define LED_PIN 22
#define LED_POWER 22
#define WIFI_LED 23
#define BLE_LED 25
+1 -1
View File
@@ -1,7 +1,7 @@
#define I2C_SCL SCL
#define I2C_SDA SDA
#define LED_PIN LED
#define LED_POWER LED
// active low, powers the Battery reader, but no lora antenna boost (?)
// #define VEXT_ENABLE Vext
+1 -1
View File
@@ -11,7 +11,7 @@
// Green LED
#define LED_STATE_ON 1 // State when LED is lit
#define LED_PIN 10
#define LED_POWER 10
// PCF8563 RTC Module
#define PCF8563_RTC 0x51
@@ -37,7 +37,7 @@
/*
LED PIN setup.
*/
#define LED_PIN 15
#define LED_POWER 15
/*
Five way button when using ADC.
+1 -1
View File
@@ -43,7 +43,7 @@ static const uint8_t SCK = 33;
#undef GPS_TX_PIN
#define GPS_TX_PIN (TX1)
#define LED_PIN LED_BLUE
#define LED_POWER LED_BLUE
#define PIN_VBAT WB_A0
#define BATTERY_PIN PIN_VBAT
+2 -2
View File
@@ -9,7 +9,7 @@
#define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module.
#define LED_STATE_ON 0 // State when LED is lit
#define LED_PIN 4 // Newer tbeams (1.1) have an extra led on GPIO4
#define LED_POWER 4 // Newer tbeams (1.1) have an extra led on GPIO4
// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
// not found then probe for SX1262
@@ -49,7 +49,7 @@
#undef EXT_NOTIFY_OUT
#undef LED_STATE_ON
#undef LED_PIN
#undef LED_POWER
#define HAS_CST226SE 1
#define HAS_TOUCHSCREEN 1
+1 -1
View File
@@ -5,7 +5,7 @@
#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
#define VEXT_ON_VALUE LOW
#define LED_PIN 2 // If defined we will blink this LED
#define LED_POWER 2 // If defined we will blink this LED
#define BUTTON_PIN 0 // If defined, this will be used for user button presses
#define BUTTON_NEED_PULLUP
#define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module.
+1 -1
View File
@@ -7,7 +7,7 @@
#define RESET_OLED 16 // If defined, this pin will be used to reset the display controller
#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
#define LED_PIN 25 // If defined we will blink this LED
#define LED_POWER 25 // If defined we will blink this LED
#define BUTTON_PIN 36
#define BUTTON_NEED_PULLUP
+1 -1
View File
@@ -5,7 +5,7 @@
#define I2C_SCL 22
#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost
#define LED_PIN 25 // If defined we will blink this LED
#define LED_POWER 25 // If defined we will blink this LED
#define BUTTON_PIN \
0 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one
// between this pin and ground
+1 -1
View File
@@ -22,4 +22,4 @@ build_flags =
${env:tlora-v2-1-1_6.build_flags}
-DBUTTON_PIN=0
-DPIN_BUZZER=25
-DLED_PIN=-1
-DLED_POWER=-1
+3 -3
View File
@@ -8,10 +8,10 @@
#define I2C_SDA 21 // I2C pins for this board
#define I2C_SCL 22
#if defined(LED_PIN) && LED_PIN == -1
#undef LED_PIN
#if defined(LED_POWER) && LED_POWER == -1
#undef LED_POWER
#else
#define LED_PIN 25 // If defined we will blink this LED
#define LED_POWER 25 // If defined we will blink this LED
#endif
#define USE_RF95
@@ -7,4 +7,5 @@ build_flags =
-D TLORA_V2_1_16
-I variants/esp32/tlora_v2_1_16
-D LORA_TCXO_GPIO=33
upload_speed = 115200
-ULED_BUILTIN
upload_speed = 115200
+1 -1
View File
@@ -6,7 +6,7 @@
#define I2C_SDA 21 // I2C pins for this board
#define I2C_SCL 22
#define LED_PIN 25 // If defined we will blink this LED
#define LED_POWER 25 // If defined we will blink this LED
#define BUTTON_PIN 12 // If defined, this will be used for user button presses,
#define BUTTON_NEED_PULLUP
+1 -1
View File
@@ -8,7 +8,7 @@
#define GPS_RX_PIN 9
#define GPS_TX_PIN 10
#define LED_PIN 13 // 13 red, 2 blue, 15 red
#define LED_POWER 13 // 13 red, 2 blue, 15 red
#define BUTTON_PIN 0
#define BUTTON_NEED_PULLUP
-1
View File
@@ -26,7 +26,6 @@
#undef GPS_TX_PIN
#define NO_GPS 1
#define HAS_GPS 0
#define NO_SCREEN
#define HAS_SCREEN 0
// Default SPI1 will be mapped to the display
+1 -1
View File
@@ -4,7 +4,7 @@
#define I2C_SCL SCL
#define BUTTON_PIN 9 // BOOT button
#define LED_PIN 30 // RGB LED
#define LED_POWER 30 // RGB LED
#define USE_RF95
#define LORA_SCK 4
@@ -3,7 +3,7 @@
// Hackerboxes LoRa ESP32-C3 OLED Kit
// Uses a ESP32-C3 OLED Board and a RA-01SH (SX1262) LoRa Board
#define LED_PIN 8 // LED
#define LED_POWER 8 // LED
#define LED_STATE_ON 1 // State when LED is lit
#define HAS_SCREEN 0
+1 -1
View File
@@ -3,7 +3,7 @@
// LED pin on HT-DEV-ESP_V2 and HT-DEV-ESP_V3
// https://resource.heltec.cn/download/HT-CT62/HT-CT62_Reference_Design.pdf
// https://resource.heltec.cn/download/HT-DEV-ESP/HT-DEV-ESP_V3_Sch.pdf
#define LED_PIN 2 // LED
#define LED_POWER 2 // LED
#define LED_STATE_ON 1 // State when LED is lit
#define HAS_SCREEN 0
+1 -1
View File
@@ -1,7 +1,7 @@
#define I2C_SDA 8 // I2C pins for this board
#define I2C_SCL 9
#define LED_PIN 7 // If defined we will blink this LED
#define LED_POWER 7 // If defined we will blink this LED
#define LED_STATE_ON 0 // State when LED is lit
#define USE_SX1262
+1 -1
View File
@@ -1,7 +1,7 @@
#define I2C_SDA 34 // I2C pins for this board
#define I2C_SCL 36
#define LED_PIN 15 // If defined we will blink this LED
#define LED_POWER 15 // If defined we will blink this LED
#define HAS_NEOPIXEL // Enable the use of neopixels
#define NEOPIXEL_COUNT 3 // How many neopixels are connected
+1 -1
View File
@@ -1,7 +1,7 @@
// EByte EoRA-Hub
// Uses E80 (LR1121) LoRa module
#define LED_PIN 35
#define LED_POWER 35
// Button - user interface
#define BUTTON_PIN 0 // BOOT button
+1 -1
View File
@@ -1,5 +1,5 @@
// LED - status indication
#define LED_PIN 37
#define LED_POWER 37
// Button - user interface
#define BUTTON_PIN 0 // This is the BOOT button, and it has its own pull-up resistor
+1 -1
View File
@@ -100,7 +100,7 @@
*/
// Status
#define LED_PIN 1
#define LED_POWER 1
#define LED_STATE_ON 1 // State when LED is lit
// External notification
// FIXME: Check if EXT_NOTIFY_OUT actualy has any effect and removes the need for setting the external notication pin in the
@@ -1,5 +1,5 @@
// Status
#define LED_PIN 1
#define LED_POWER 1
#define PIN_BUTTON1 47 // 功能键
#define PIN_BUTTON2 4 // 电源键
@@ -8,7 +8,13 @@ void earlyInitVariant()
Wire1.begin(48, 47);
io.pinMode(PCA_PIN_EINK_EN, OUTPUT);
io.pinMode(PCA_PIN_POWER_EN, OUTPUT);
io.pinMode(PCA_LED_POWER, OUTPUT);
io.pinMode(PCA_LED_USER, OUTPUT);
io.pinMode(PCA_LED_ENABLE, OUTPUT);
io.digitalWrite(PCA_PIN_POWER_EN, HIGH);
io.digitalWrite(PCA_LED_USER, LOW);
io.digitalWrite(PCA_LED_ENABLE, LOW);
}
void variant_shutdown()
@@ -8,8 +8,10 @@
// LED
// Both of these are on the GPIO expander
#define PCA_LED_USER 1 // the Blue LED
#define PCA_LED_POWER 3 // the Red LED? Seems to have hardware logic to blink when USB is plugged in.
#define PCA_LED_USER 1 // the Blue LED
#define PCA_LED_ENABLE 2 // the power supply to the LEDs, in an OR arrangement with VBUS power
#define PCA_LED_POWER 3 // the Red LED? Seems to have hardware logic to blink when USB is plugged in.
#define POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING
// USB_CHECK
#define EXT_PWR_DETECT 12
@@ -11,7 +11,7 @@
#define I2C_SDA 12
#define I2C_SCL 14
#define LED_PIN 46
#define LED_POWER 46
#define LED_STATE_ON 0 // State when LED is litted
// #define BUTTON_PIN 15 // Pico OLED 1.3 User key 0 - removed User key 1 (17)
@@ -26,7 +26,7 @@
// #define GPS_RX_PIN 44
// #define GPS_TX_PIN 43
#define LED_PIN 41
#define LED_POWER 41
#define BUTTON_PIN 2
#define BUTTON_NEED_PULLUP
@@ -11,7 +11,7 @@
#define I2C_SDA 18 // 1 // I2C pins for this board
#define I2C_SCL 17 // 2
// #define LED_PIN 38 // This is a RGB LED not a standard LED
// #define LED_POWER 38 // This is a RGB LED not a standard LED
#define HAS_NEOPIXEL // Enable the use of neopixels
#define NEOPIXEL_COUNT 1 // How many neopixels are connected
#define NEOPIXEL_DATA 38 // gpio pin used to send data to the neopixels
@@ -11,7 +11,7 @@
#define I2C_SDA 18 // 1 // I2C pins for this board
#define I2C_SCL 17 // 2
// #define LED_PIN 38 // This is a RGB LED not a standard LED
// #define LED_POWER 38 // This is a RGB LED not a standard LED
#define HAS_NEOPIXEL // Enable the use of neopixels
#define NEOPIXEL_COUNT 1 // How many neopixels are connected
#define NEOPIXEL_DATA 38 // gpio pin used to send data to the neopixels
+1 -1
View File
@@ -6,7 +6,7 @@
#define I2C_SDA1 45
#define I2C_SCL1 46
#define LED_PIN 6
#define LED_POWER 6
#define LED_STATE_ON 1
#define BUTTON_PIN 0
-1
View File
@@ -8,7 +8,6 @@
#define EXT_NOTIFY_OUT 22
#define BUTTON_PIN 0 // 17
// #define LED_PIN PIN_LED
// Board has RGB LED 21
#define HAS_NEOPIXEL // Enable the use of neopixels
#define NEOPIXEL_COUNT 1 // How many neopixels are connected
@@ -1,5 +1,5 @@
#define LED_PIN 33
#define LED_PIN2 34
#define LED_POWER 33
#define LED_POWER2 34
#define EXT_PWR_DETECT 35
#define BUTTON_PIN 18
+1 -1
View File
@@ -1,4 +1,4 @@
#define LED_PIN LED
#define LED_POWER LED
#define USE_SSD1306 // Heltec_v3 has a SSD1306 display
+1 -1
View File
@@ -26,7 +26,7 @@ build_flags =
${heltec_v4_base.build_flags}
-D HELTEC_V4_OLED
-D USE_SSD1306 ; Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver)
-D LED_PIN=35
-D LED_POWER=35
-D RESET_OLED=21
-D I2C_SDA=17
-D I2C_SCL=18
@@ -1,4 +1,4 @@
#define LED_PIN 45 // LED is not populated on earliest board variant
#define LED_POWER 45 // LED is not populated on earliest board variant
#define BUTTON_PIN 0
#define PIN_BUTTON2 21 // Second built-in button
#define ALT_BUTTON_PIN PIN_BUTTON2 // Send the up event
@@ -1,4 +1,4 @@
#define LED_PIN 45 // LED is not populated on earliest board variant
#define LED_POWER 45 // LED is not populated on earliest board variant
#define BUTTON_PIN 0
#define PIN_BUTTON2 21 // Second built-in button
#define ALT_BUTTON_PIN PIN_BUTTON2 // Send the up event
@@ -1,4 +1,4 @@
#define LED_PIN 18
#define LED_POWER 18
#define BUTTON_PIN 0
// I2C
@@ -1,4 +1,4 @@
#define LED_PIN 18
#define LED_POWER 18
#define BUTTON_PIN 0
// I2C
@@ -1,4 +1,4 @@
#define LED_PIN 18
#define LED_POWER 18
#define _VARIANT_HELTEC_WIRELESS_TRACKER
#define HELTEC_TRACKER_V1_X
@@ -1,4 +1,4 @@
#define LED_PIN 18
#define LED_POWER 18
#define HELTEC_TRACKER_V1_X
@@ -1,4 +1,4 @@
#define LED_PIN 18
#define LED_POWER 18
#define _VARIANT_HELTEC_WIRELESS_TRACKER
+1 -1
View File
@@ -1,7 +1,7 @@
#define I2C_SCL SCL
#define I2C_SDA SDA
#define LED_PIN LED
#define LED_POWER LED
#define VEXT_ENABLE Vext // active low, powers the oled display and the lora antenna boost
#define VEXT_ON_VALUE LOW
+1 -1
View File
@@ -13,7 +13,7 @@
#define ADC_CHANNEL ADC1_GPIO4_CHANNEL
// LED
#define LED_PIN 21
#define LED_POWER 21
// Button
#define BUTTON_PIN 0
+1 -1
View File
@@ -1,7 +1,7 @@
#define I2C_SDA 11 // I2C pins for this board
#define I2C_SCL 10
#define LED_PIN 1 // If defined we will blink this LED
#define LED_POWER 1 // If defined we will blink this LED
#define BUTTON_PIN 0 // If defined, this will be used for user button presses
#define BUTTON_NEED_PULLUP
+1 -1
View File
@@ -4,7 +4,7 @@
#define USE_SSD1306
#define DISPLAY_FLIP_SCREEN
#define LED_PIN 15 // If defined we will blink this LED
#define LED_POWER 15 // If defined we will blink this LED
#define HAS_NEOPIXEL // Enable the use of neopixels
#define NEOPIXEL_COUNT 3 // How many neopixels are connected

Some files were not shown because too many files have changed in this diff Show More