Merge branch 'develop' of https://github.com/meshtastic/firmware into clear-rangetest-results

This commit is contained in:
ford-jones
2025-09-03 13:35:50 +12:00
106 changed files with 2584 additions and 525 deletions
+7 -7
View File
@@ -4,19 +4,19 @@ cli:
plugins: plugins:
sources: sources:
- id: trunk - id: trunk
ref: v1.7.1 ref: v1.7.2
uri: https://github.com/trunk-io/plugins uri: https://github.com/trunk-io/plugins
lint: lint:
enabled: enabled:
- checkov@3.2.461 - checkov@3.2.467
- renovate@41.74.0 - renovate@41.90.1
- prettier@3.6.2 - prettier@3.6.2
- trufflehog@3.90.5 - trufflehog@3.90.5
- yamllint@1.37.1 - yamllint@1.37.1
- bandit@1.8.6 - bandit@1.8.6
- trivy@0.64.1 - trivy@0.65.0
- taplo@0.9.3 - taplo@0.10.0
- ruff@0.12.7 - ruff@0.12.11
- isort@6.0.1 - isort@6.0.1
- markdownlint@0.45.0 - markdownlint@0.45.0
- oxipng@9.1.5 - oxipng@9.1.5
@@ -25,7 +25,7 @@ lint:
- flake8@7.3.0 - flake8@7.3.0
- hadolint@2.12.1-beta - hadolint@2.12.1-beta
- shfmt@3.6.0 - shfmt@3.6.0
- shellcheck@0.10.0 - shellcheck@0.11.0
- black@25.1.0 - black@25.1.0
- git-diff-check - git-diff-check
- gitleaks@8.28.0 - gitleaks@8.28.0
+1 -1
View File
@@ -50,7 +50,7 @@ lib_deps =
${radiolib_base.lib_deps} ${radiolib_base.lib_deps}
# renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main # renovate: datasource=git-refs depName=caveman99-stm32-Crypto packageName=https://github.com/caveman99/Crypto gitBranch=main
https://github.com/caveman99/Crypto/archive/eae9c768054118a9399690f8af202853d1ae8516.zip https://github.com/caveman99/Crypto/archive/1aa30eb536bd52a576fde6dfa393bf7349cf102d.zip
lib_ignore = lib_ignore =
OneButton OneButton
+1 -2
View File
@@ -100,7 +100,6 @@ IF NOT "!FILENAME:update=!"=="!FILENAME!" (
) )
:skip-filename :skip-filename
SET "ESPTOOL_BAUD=1200"
CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..." CALL :LOG_MESSAGE DEBUG "Determine the correct esptool command to use..."
IF NOT "__%PYTHON%__"=="____" ( IF NOT "__%PYTHON%__"=="____" (
@@ -142,7 +141,7 @@ CALL :LOG_MESSAGE INFO "Using esptool baud: !ESPTOOL_BAUD!."
IF %BPS_RESET% EQU 1 ( IF %BPS_RESET% EQU 1 (
@REM Attempt to change mode via 1200bps Reset. @REM Attempt to change mode via 1200bps Reset.
CALL :RUN_ESPTOOL !ESPTOOL_BAUD! --after no_reset read_flash_status CALL :RUN_ESPTOOL 1200 --after no_reset read_flash_status
GOTO eof GOTO eof
) )
@@ -87,6 +87,12 @@
</screenshots> </screenshots>
<releases> <releases>
<release version="2.7.8" date="2025-08-30">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.8</url>
</release>
<release version="2.7.7" date="2025-08-28">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.7</url>
</release>
<release version="2.7.6" date="2025-08-12"> <release version="2.7.6" date="2025-08-12">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.6</url> <url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.6</url>
</release> </release>
+54
View File
@@ -0,0 +1,54 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x4405"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x0071"]
],
"usb_product": "HT-n5262",
"mcu": "nrf52840",
"variant": "heltec_mesh_solar",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Heltec nrf (Adafruit BSP)",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://heltec.org/project/meshsolar/",
"vendor": "Heltec"
}
+1 -1
View File
@@ -5,7 +5,7 @@
}, },
"core": "stm32", "core": "stm32",
"cpu": "cortex-m4", "cpu": "cortex-m4",
"extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_GENERIC_WLE5CCUX", "extra_flags": "-DSTM32WLxx -DSTM32WLE5xx -DARDUINO_RAK3172_MODULE",
"f_cpu": "48000000L", "f_cpu": "48000000L",
"mcu": "stm32wle5ccu", "mcu": "stm32wle5ccu",
"variant": "STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U", "variant": "STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U",
+6 -2
View File
@@ -1,4 +1,4 @@
meshtasticd (2.7.6.0) UNRELEASED; urgency=medium meshtasticd (2.7.8.0) UNRELEASED; urgency=medium
[ Austin Lane ] [ Austin Lane ]
* Initial packaging * Initial packaging
@@ -39,5 +39,9 @@ meshtasticd (2.7.6.0) UNRELEASED; urgency=medium
[ ] [ ]
* GitHub Actions Automatic version bump * GitHub Actions Automatic version bump
* GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Tue, 12 Aug 2025 23:48:48 +0000 [ ]
* GitHub Actions Automatic version bump
-- <github-actions[bot]@users.noreply.github.com> Sat, 30 Aug 2025 00:26:04 +0000
+1 -1
View File
@@ -118,7 +118,7 @@ lib_deps =
[device-ui_base] [device-ui_base]
lib_deps = lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/3dc7cf3e233aaa8cc23492cca50541fc099ebfa1.zip https://github.com/meshtastic/device-ui/archive/a3e0e1be372d069f47b4c19d718f5267251744d7.zip
; Common libs for environmental measurements in telemetry module ; Common libs for environmental measurements in telemetry module
[environmental_base] [environmental_base]
+1
View File
@@ -8,6 +8,7 @@
"replacements:all", "replacements:all",
"workarounds:all" "workarounds:all"
], ],
"baseBranchPatterns": ["master"],
"forkProcessing": "enabled", "forkProcessing": "enabled",
"ignoreDeps": [ "ignoreDeps": [
"protobufs" "protobufs"
+8 -1
View File
@@ -1,7 +1,14 @@
#include "DisplayFormatters.h" #include "DisplayFormatters.h"
const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName) const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
bool usePreset)
{ {
// If use_preset is false, always return "Custom"
if (!usePreset) {
return "Custom";
}
switch (preset) { switch (preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO: case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
return useShortName ? "ShortT" : "ShortTurbo"; return useShortName ? "ShortT" : "ShortTurbo";
+2 -1
View File
@@ -4,5 +4,6 @@
class DisplayFormatters class DisplayFormatters
{ {
public: public:
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName); static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
bool usePreset);
}; };
+95 -19
View File
@@ -128,6 +128,7 @@ RAK9154Sensor rak9154Sensor;
#ifdef HAS_PPM #ifdef HAS_PPM
// note: XPOWERS_CHIP_XXX must be defined in variant.h // note: XPOWERS_CHIP_XXX must be defined in variant.h
#include <XPowersLib.h> #include <XPowersLib.h>
XPowersPPM *PPM = NULL;
#endif #endif
#ifdef HAS_BQ27220 #ifdef HAS_BQ27220
@@ -681,6 +682,8 @@ bool Power::setup()
found = true; found = true;
} else if (lipoChargerInit()) { } else if (lipoChargerInit()) {
found = true; found = true;
} else if (meshSolarInit()) {
found = true;
} else if (analogInit()) { } else if (analogInit()) {
found = true; found = true;
} }
@@ -743,7 +746,11 @@ void Power::shutdown()
#if HAS_SCREEN #if HAS_SCREEN
if (screen) { if (screen) {
#ifdef T_DECK_PRO
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
#else
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
#endif
} }
#endif #endif
#if !defined(ARCH_STM32WL) #if !defined(ARCH_STM32WL)
@@ -761,7 +768,7 @@ void Power::shutdown()
#ifdef PIN_LED3 #ifdef PIN_LED3
ledOff(PIN_LED3); ledOff(PIN_LED3);
#endif #endif
doDeepSleep(DELAY_FOREVER, false, true); doDeepSleep(DELAY_FOREVER, true, true);
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
#else #else
@@ -1318,7 +1325,6 @@ bool Power::lipoInit()
class LipoCharger : public HasBatteryLevel class LipoCharger : public HasBatteryLevel
{ {
private: private:
XPowersPPM *ppm = nullptr;
BQ27220 *bq = nullptr; BQ27220 *bq = nullptr;
public: public:
@@ -1327,41 +1333,41 @@ class LipoCharger : public HasBatteryLevel
*/ */
bool runOnce() bool runOnce()
{ {
if (ppm == nullptr) { if (PPM == nullptr) {
ppm = new XPowersPPM; PPM = new XPowersPPM;
bool result = ppm->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR); bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
if (result) { if (result) {
LOG_INFO("PPM BQ25896 init succeeded"); LOG_INFO("PPM BQ25896 init succeeded");
// Set the minimum operating voltage. Below this voltage, the PPM will protect // Set the minimum operating voltage. Below this voltage, the PPM will protect
// ppm->setSysPowerDownVoltage(3100); // PPM->setSysPowerDownVoltage(3100);
// Set input current limit, default is 500mA // Set input current limit, default is 500mA
// ppm->setInputCurrentLimit(800); // PPM->setInputCurrentLimit(800);
// Disable current limit pin // Disable current limit pin
// ppm->disableCurrentLimitPin(); // PPM->disableCurrentLimitPin();
// Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV // Set the charging target voltage, Range:3840 ~ 4608mV ,step:16 mV
ppm->setChargeTargetVoltage(4288); PPM->setChargeTargetVoltage(4288);
// Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA // Set the precharge current , Range: 64mA ~ 1024mA ,step:64mA
// ppm->setPrechargeCurr(64); // PPM->setPrechargeCurr(64);
// The premise is that limit pin is disabled, or it will // The premise is that limit pin is disabled, or it will
// only follow the maximum charging current set by limit pin. // only follow the maximum charging current set by limit pin.
// Set the charging current , Range:0~5056mA ,step:64mA // Set the charging current , Range:0~5056mA ,step:64mA
ppm->setChargerConstantCurr(1024); PPM->setChargerConstantCurr(1024);
// To obtain voltage data, the ADC must be enabled first // To obtain voltage data, the ADC must be enabled first
ppm->enableMeasure(); PPM->enableMeasure();
// Turn on charging function // Turn on charging function
// If there is no battery connected, do not turn on the charging function // If there is no battery connected, do not turn on the charging function
ppm->enableCharge(); PPM->enableCharge();
} else { } else {
LOG_WARN("PPM BQ25896 init failed"); LOG_WARN("PPM BQ25896 init failed");
delete ppm; delete PPM;
ppm = nullptr; PPM = nullptr;
return false; return false;
} }
} }
@@ -1402,23 +1408,23 @@ class LipoCharger : public HasBatteryLevel
/** /**
* return true if there is a battery installed in this unit * return true if there is a battery installed in this unit
*/ */
virtual bool isBatteryConnect() override { return ppm->getBattVoltage() > 0; } virtual bool isBatteryConnect() override { return PPM->getBattVoltage() > 0; }
/** /**
* return true if there is an external power source detected * return true if there is an external power source detected
*/ */
virtual bool isVbusIn() override { return ppm->getVbusVoltage() > 0; } virtual bool isVbusIn() override { return PPM->getVbusVoltage() > 0; }
/** /**
* return true if the battery is currently charging * return true if the battery is currently charging
*/ */
virtual bool isCharging() override virtual bool isCharging() override
{ {
bool isCharging = ppm->isCharging(); bool isCharging = PPM->isCharging();
if (isCharging) { if (isCharging) {
LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull()); LOG_DEBUG("BQ27220 time to full charge: %d min", bq->getTimeToFull());
} else { } else {
if (!ppm->isVbusIn()) { if (!PPM->isVbusIn()) {
LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity()); LOG_DEBUG("BQ27220 time to empty: %d min (%d mAh)", bq->getTimeToEmpty(), bq->getRemainingCapacity());
} }
} }
@@ -1450,3 +1456,73 @@ bool Power::lipoChargerInit()
return false; return false;
} }
#endif #endif
#ifdef HELTEC_MESH_SOLAR
#include "meshSolarApp.h"
/**
* meshSolar class for an SMBUS battery sensor.
*/
class meshSolarBatteryLevel : public HasBatteryLevel
{
public:
/**
* Init the I2C meshSolar battery level sensor
*/
bool runOnce()
{
meshSolarStart();
return true;
}
/**
* Battery state of charge, from 0 to 100 or -1 for unknown
*/
virtual int getBatteryPercent() override { return meshSolarGetBatteryPercent(); }
/**
* The raw voltage of the battery in millivolts, or NAN if unknown
*/
virtual uint16_t getBattVoltage() override { return meshSolarGetBattVoltage(); }
/**
* return true if there is a battery installed in this unit
*/
virtual bool isBatteryConnect() override { return meshSolarIsBatteryConnect(); }
/**
* return true if there is an external power source detected
*/
virtual bool isVbusIn() override { return meshSolarIsVbusIn(); }
/**
* return true if the battery is currently charging
*/
virtual bool isCharging() override { return meshSolarIsCharging(); }
};
meshSolarBatteryLevel meshSolarLevel;
/**
* Init the meshSolar battery level sensor
*/
bool Power::meshSolarInit()
{
bool result = meshSolarLevel.runOnce();
LOG_DEBUG("Power::meshSolarInit mesh solar sensor is %s", result ? "ready" : "not ready yet");
if (!result)
return false;
batteryLevel = &meshSolarLevel;
return true;
}
#else
/**
* The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel
*/
bool Power::meshSolarInit()
{
return false;
}
#endif
+8
View File
@@ -64,6 +64,14 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
int32_t SerialConsole::runOnce() int32_t SerialConsole::runOnce()
{ {
#ifdef HELTEC_MESH_SOLAR
//After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module.
if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port
&& moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)
{
return 250;
}
#endif
return runOncePart(); return runOncePart();
} }
+2
View File
@@ -33,7 +33,9 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
break; break;
case INPUT_BROKER_UP: case INPUT_BROKER_UP:
case INPUT_BROKER_UP_LONG:
case INPUT_BROKER_DOWN: case INPUT_BROKER_DOWN:
case INPUT_BROKER_DOWN_LONG:
case INPUT_BROKER_LEFT: case INPUT_BROKER_LEFT:
case INPUT_BROKER_RIGHT: case INPUT_BROKER_RIGHT:
playChirp(); // Navigation feedback playChirp(); // Navigation feedback
+3 -3
View File
@@ -26,10 +26,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <Arduino.h> #include <Arduino.h>
#ifdef RV3028_RTC #if __has_include("Melopero_RV3028.h")
#include "Melopero_RV3028.h" #include "Melopero_RV3028.h"
#endif #endif
#ifdef PCF8563_RTC #if __has_include("pcf8563.h")
#include "pcf8563.h" #include "pcf8563.h"
#endif #endif
@@ -135,7 +135,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// OLED & Input // OLED & Input
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if defined(SEEED_WIO_TRACKER_L1) #if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK)
#define SSD1306_ADDRESS 0x3D #define SSD1306_ADDRESS 0x3D
#define USE_SH1106 #define USE_SH1106
#else #else
+2 -1
View File
@@ -79,7 +79,8 @@ class ScanI2C
BQ27220, BQ27220,
LTR553ALS, LTR553ALS,
BHI260AP, BHI260AP,
BMM150 BMM150,
DRV2605
} DeviceType; } DeviceType;
// typedef uint8_t DeviceAddress; // typedef uint8_t DeviceAddress;
+8 -2
View File
@@ -483,8 +483,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = MLX90614; type = MLX90614;
logFoundDevice("MLX90614", (uint8_t)addr.address); logFoundDevice("MLX90614", (uint8_t)addr.address);
} else { } else {
type = MPR121KB; registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // DRV2605_REG_STATUS
logFoundDevice("MPR121KB", (uint8_t)addr.address); if (registerValue == 0xe0) {
type = DRV2605;
logFoundDevice("DRV2605", (uint8_t)addr.address);
} else {
type = MPR121KB;
logFoundDevice("MPR121KB", (uint8_t)addr.address);
}
} }
break; break;
+28 -56
View File
@@ -843,9 +843,6 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
setPowerPMU(true); // Power (PMU): on setPowerPMU(true); // Power (PMU): on
writePinStandby(false); // Standby (pin): awake (not standby) writePinStandby(false); // Standby (pin): awake (not standby)
setPowerUBLOX(true); // Standby (UBLOX): awake setPowerUBLOX(true); // Standby (UBLOX): awake
#ifdef GNSS_AIROHA
lastFixStartMsec = 0;
#endif
break; break;
case GPS_SOFTSLEEP: case GPS_SOFTSLEEP:
@@ -863,9 +860,7 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
writePinStandby(true); // Standby (pin): asleep (not awake) writePinStandby(true); // Standby (pin): asleep (not awake)
setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed setPowerUBLOX(false, sleepTime); // Standby (UBLOX): asleep, timed
#ifdef GNSS_AIROHA #ifdef GNSS_AIROHA
if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) { digitalWrite(PIN_GPS_EN, LOW);
digitalWrite(PIN_GPS_EN, LOW);
}
#endif #endif
break; break;
@@ -877,9 +872,7 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
writePinStandby(true); // Standby (pin): asleep writePinStandby(true); // Standby (pin): asleep
setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely setPowerUBLOX(false, 0); // Standby (UBLOX): asleep, indefinitely
#ifdef GNSS_AIROHA #ifdef GNSS_AIROHA
if (config.position.gps_update_interval * 1000 >= GPS_FIX_HOLD_TIME * 2) { digitalWrite(PIN_GPS_EN, LOW);
digitalWrite(PIN_GPS_EN, LOW);
}
#endif #endif
break; break;
} }
@@ -1062,6 +1055,8 @@ void GPS::down()
} }
// If update interval long enough (or softsleep unsupported): hardsleep instead // If update interval long enough (or softsleep unsupported): hardsleep instead
setPowerState(GPS_HARDSLEEP, sleepTime); setPowerState(GPS_HARDSLEEP, sleepTime);
// Reset the fix quality to 0, since we're off.
fixQual = 0;
} }
} }
@@ -1121,11 +1116,19 @@ int32_t GPS::runOnce()
shouldPublish = true; shouldPublish = true;
} }
uint8_t prev_fixQual = fixQual;
bool gotLoc = lookForLocation(); bool gotLoc = lookForLocation();
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
LOG_DEBUG("hasValidLocation RISING EDGE"); LOG_DEBUG("hasValidLocation RISING EDGE");
hasValidLocation = true; hasValidLocation = true;
shouldPublish = true; shouldPublish = true;
// Hold for 20secs after getting a lock to download ephemeris etc
fixHoldEnds = millis() + 20000;
}
if (gotLoc && prev_fixQual == 0) { // just got a lock after turning back on.
fixHoldEnds = millis() + 20000;
shouldPublish = true; // Publish immediately, since next publish is at end of hold
} }
bool tooLong = scheduling.searchedTooLong(); bool tooLong = scheduling.searchedTooLong();
@@ -1134,8 +1137,7 @@ int32_t GPS::runOnce()
// Once we get a location we no longer desperately want an update // Once we get a location we no longer desperately want an update
if ((gotLoc && gotTime) || tooLong) { if ((gotLoc && gotTime) || tooLong) {
if (tooLong && !gotLoc) {
if (tooLong) {
// we didn't get a location during this ack window, therefore declare loss of lock // we didn't get a location during this ack window, therefore declare loss of lock
if (hasValidLocation) { if (hasValidLocation) {
LOG_DEBUG("hasValidLocation FALLING EDGE"); LOG_DEBUG("hasValidLocation FALLING EDGE");
@@ -1143,9 +1145,15 @@ int32_t GPS::runOnce()
p = meshtastic_Position_init_default; p = meshtastic_Position_init_default;
hasValidLocation = false; hasValidLocation = false;
} }
if (millis() > fixHoldEnds) {
down(); shouldPublish = true; // publish our update at the end of the lock hold
shouldPublish = true; // publish our update for this just finished acquisition window publishUpdate();
down();
#ifdef GPS_DEBUG
} else {
LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view);
#endif
}
} }
// If state has changed do a publish // If state has changed do a publish
@@ -1504,28 +1512,10 @@ static int32_t toDegInt(RawDegrees d)
* Perform any processing that should be done only while the GPS is awake and looking for a fix. * Perform any processing that should be done only while the GPS is awake and looking for a fix.
* Override this method to check for new locations * Override this method to check for new locations
* *
* @return true if we've acquired a new location * @return true if we've set a new time
*/ */
bool GPS::lookForTime() bool GPS::lookForTime()
{ {
#ifdef GNSS_AIROHA
uint8_t fix = reader.fixQuality();
if (fix >= 1 && fix <= 5) {
if (lastFixStartMsec > 0) {
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false;
} else {
clearBuffer();
}
} else {
lastFixStartMsec = millis();
return false;
}
} else {
return false;
}
#endif
auto ti = reader.time; auto ti = reader.time;
auto d = reader.date; auto d = reader.date;
if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed if (ti.isValid() && d.isValid()) { // Note: we don't check for updated, because we'll only be called if needed
@@ -1544,11 +1534,12 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
if (t.tm_mon > -1) { if (t.tm_mon > -1) {
LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min, LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min,
t.tm_sec, ti.age()); t.tm_sec, ti.age());
if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) { if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultSuccess) {
// Clear the GPS buffer if we got an invalid time LOG_DEBUG("Time set.");
clearBuffer(); return true;
} else {
return false;
} }
return true;
} else } else
return false; return false;
} else } else
@@ -1563,25 +1554,6 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
*/ */
bool GPS::lookForLocation() bool GPS::lookForLocation()
{ {
#ifdef GNSS_AIROHA
if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) {
uint8_t fix = reader.fixQuality();
if (fix >= 1 && fix <= 5) {
if (lastFixStartMsec > 0) {
if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false;
} else {
clearBuffer();
}
} else {
lastFixStartMsec = millis();
return false;
}
} else {
return false;
}
}
#endif
// By default, TinyGPS++ does not parse GPGSA lines, which give us // By default, TinyGPS++ does not parse GPGSA lines, which give us
// the 2D/3D fixType (see NMEAGPS.h) // the 2D/3D fixType (see NMEAGPS.h)
// At a minimum, use the fixQuality indicator in GPGGA (FIXME?) // At a minimum, use the fixQuality indicator in GPGGA (FIXME?)
+1 -1
View File
@@ -159,7 +159,7 @@ class GPS : private concurrency::OSThread
uint8_t fixType = 0; // fix type from GPGSA uint8_t fixType = 0; // fix type from GPGSA
#endif #endif
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0; uint32_t fixHoldEnds = 0;
uint32_t rx_gpio = 0; uint32_t rx_gpio = 0;
uint32_t tx_gpio = 0; uint32_t tx_gpio = 0;
+34 -8
View File
@@ -23,7 +23,7 @@ static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only upda
* Reads the current date and time from the RTC module and updates the system time. * Reads the current date and time from the RTC module and updates the system time.
* @return True if the RTC was successfully read and the system time was updated, false otherwise. * @return True if the RTC was successfully read and the system time was updated, false otherwise.
*/ */
void readFromRTC() RTCSetResult readFromRTC()
{ {
struct timeval tv; /* btw settimeofday() is helpful here too*/ struct timeval tv; /* btw settimeofday() is helpful here too*/
#ifdef RV3028_RTC #ifdef RV3028_RTC
@@ -44,15 +44,23 @@ void readFromRTC()
t.tm_sec = rtc.getSecond(); t.tm_sec = rtc.getSecond();
tv.tv_sec = gm_mktime(&t); tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0; tv.tv_usec = 0;
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime;
}
#endif
LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1, LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch); t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
if (currentQuality == RTCQualityNone) { if (currentQuality == RTCQualityNone) {
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
currentQuality = RTCQualityDevice; currentQuality = RTCQualityDevice;
} }
return RTCSetResultSuccess;
} }
#elif defined(PCF8563_RTC) #elif defined(PCF8563_RTC)
if (rtc_found.address == PCF8563_RTC) { if (rtc_found.address == PCF8563_RTC) {
@@ -75,15 +83,23 @@ void readFromRTC()
t.tm_sec = tc.second; t.tm_sec = tc.second;
tv.tv_sec = gm_mktime(&t); tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0; tv.tv_usec = 0;
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime;
}
#endif
LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1, LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch); t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
if (currentQuality == RTCQualityNone) { if (currentQuality == RTCQualityNone) {
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
currentQuality = RTCQualityDevice; currentQuality = RTCQualityDevice;
} }
return RTCSetResultSuccess;
} }
#else #else
if (!gettimeofday(&tv, NULL)) { if (!gettimeofday(&tv, NULL)) {
@@ -92,8 +108,10 @@ void readFromRTC()
LOG_DEBUG("Read RTC time as %ld", printableEpoch); LOG_DEBUG("Read RTC time as %ld", printableEpoch);
timeStartMsec = now; timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec; zeroOffsetSecs = tv.tv_sec;
return RTCSetResultSuccess;
} }
#endif #endif
return RTCSetResultNotSet;
} }
/** /**
@@ -101,7 +119,7 @@ void readFromRTC()
* *
* @param q The quality of the provided time. * @param q The quality of the provided time.
* @param tv A pointer to a timeval struct containing the time to potentially set the RTC to. * @param tv A pointer to a timeval struct containing the time to potentially set the RTC to.
* @return True if the RTC was set, false otherwise. * @return RTCSetResult
* *
* If we haven't yet set our RTC this boot, set it from a GPS derived time * If we haven't yet set our RTC this boot, set it from a GPS derived time
*/ */
@@ -114,6 +132,10 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
if (tv->tv_sec < BUILD_EPOCH) { if (tv->tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime; return RTCSetResultInvalidTime;
} else if (tv->tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
BUILD_EPOCH + FORTY_YEARS);
return RTCSetResultInvalidTime;
} }
#endif #endif
@@ -232,6 +254,10 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
if (tv.tv_sec < BUILD_EPOCH) { if (tv.tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
return RTCSetResultInvalidTime; return RTCSetResultInvalidTime;
} else if (tv.tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
BUILD_EPOCH + FORTY_YEARS);
return RTCSetResultInvalidTime;
} }
#endif #endif
+4 -1
View File
@@ -48,10 +48,13 @@ uint32_t getTime(bool local = false);
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero /// Return time since 1970 in secs. If quality is RTCQualityNone return zero
uint32_t getValidTime(RTCQuality minQuality, bool local = false); uint32_t getValidTime(RTCQuality minQuality, bool local = false);
void readFromRTC(); RTCSetResult readFromRTC();
time_t gm_mktime(struct tm *tm); time_t gm_mktime(struct tm *tm);
#define SEC_PER_DAY 86400 #define SEC_PER_DAY 86400
#define SEC_PER_HOUR 3600 #define SEC_PER_HOUR 3600
#define SEC_PER_MIN 60 #define SEC_PER_MIN 60
#ifdef BUILD_EPOCH
#define FORTY_YEARS (40UL * 365 * SEC_PER_DAY) // probably time to update your firmware
#endif
+14 -5
View File
@@ -67,20 +67,28 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
// FIXME - only draw bits have changed (use backbuf similar to the other displays) // FIXME - only draw bits have changed (use backbuf similar to the other displays)
const bool flipped = config.display.flip_screen; const bool flipped = config.display.flip_screen;
// HACK for L1 EInk
#if defined(SEEED_WIO_TRACKER_L1_EINK)
// For SEEED_WIO_TRACKER_L1_EINK, setRotation(3) is correct but mirrored; flip both axes
for (uint32_t y = 0; y < displayHeight; y++) {
for (uint32_t x = 0; x < displayWidth; x++) {
auto b = buffer[x + (y / 8) * displayWidth];
auto isset = b & (1 << (y & 7));
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
}
}
#else
for (uint32_t y = 0; y < displayHeight; y++) { for (uint32_t y = 0; y < displayHeight; y++) {
for (uint32_t x = 0; x < displayWidth; x++) { for (uint32_t x = 0; x < displayWidth; x++) {
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient
auto b = buffer[x + (y / 8) * displayWidth]; auto b = buffer[x + (y / 8) * displayWidth];
auto isset = b & (1 << (y & 7)); auto isset = b & (1 << (y & 7));
// Handle flip here, rather than with setRotation(),
// Avoids issues when display width is not a multiple of 8
if (flipped) if (flipped)
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE); adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
else else
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE); adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
} }
} }
#endif
// Trigger the refresh in GxEPD2 // Trigger the refresh in GxEPD2
LOG_DEBUG("Update E-Paper"); LOG_DEBUG("Update E-Paper");
@@ -235,7 +243,7 @@ bool EInkDisplay::connect()
adafruitDisplay->setRotation(1); adafruitDisplay->setRotation(1);
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
} }
#elif defined(HELTEC_MESH_POCKET) #elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
{ {
spi1 = &SPI1; spi1 = &SPI1;
spi1->begin(); spi1->begin();
@@ -249,6 +257,7 @@ bool EInkDisplay::connect()
// Init GxEPD2 // Init GxEPD2
adafruitDisplay->init(); adafruitDisplay->init();
adafruitDisplay->setRotation(3); adafruitDisplay->setRotation(3);
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
} }
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) #elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
+1 -1
View File
@@ -84,7 +84,7 @@ class EInkDisplay : public OLEDDisplay
SPIClass *hspi = NULL; SPIClass *hspi = NULL;
#endif #endif
#if defined(HELTEC_MESH_POCKET) #if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK)
SPIClass *spi1 = NULL; SPIClass *spi1 = NULL;
#endif #endif
+154 -71
View File
@@ -356,7 +356,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
dispdev = new SSD1306Wire(address.address, -1, -1, geometry, dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \ #elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
dispdev = new TFTDisplay(address.address, -1, -1, geometry, dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) #elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
@@ -588,7 +588,7 @@ void Screen::setup()
#else #else
if (!config.display.flip_screen) { if (!config.display.flip_screen) {
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ #if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically(); static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
#elif defined(USE_ST7789) #elif defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically(); static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
@@ -941,71 +941,86 @@ void Screen::setFrames(FrameFocus focus)
} }
#if defined(DISPLAY_CLOCK_FRAME) #if defined(DISPLAY_CLOCK_FRAME)
fsi.positions.clock = numframes; if (!hiddenFrames.clock) {
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame fsi.positions.clock = numframes;
: graphics::ClockRenderer::drawDigitalClockFrame; normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
indicatorIcons.push_back(digital_icon_clock); : graphics::ClockRenderer::drawDigitalClockFrame;
indicatorIcons.push_back(digital_icon_clock);
}
#endif #endif
// Declare this early so its available in FOCUS_PRESERVE block // Declare this early so its available in FOCUS_PRESERVE block
bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message); bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
fsi.positions.home = numframes; if (!hiddenFrames.home) {
normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused; fsi.positions.home = numframes;
indicatorIcons.push_back(icon_home); normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
indicatorIcons.push_back(icon_home);
}
fsi.positions.textMessage = numframes; fsi.positions.textMessage = numframes;
normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame; normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
indicatorIcons.push_back(icon_mail); indicatorIcons.push_back(icon_mail);
#ifndef USE_EINK #ifndef USE_EINK
fsi.positions.nodelist = numframes; if (!hiddenFrames.nodelist) {
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen; fsi.positions.nodelist = numframes;
indicatorIcons.push_back(icon_nodes); normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
indicatorIcons.push_back(icon_nodes);
}
#endif #endif
// Show detailed node views only on E-Ink builds // Show detailed node views only on E-Ink builds
#ifdef USE_EINK #ifdef USE_EINK
fsi.positions.nodelist_lastheard = numframes; if (!hiddenFrames.nodelist_lastheard) {
normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen; fsi.positions.nodelist_lastheard = numframes;
indicatorIcons.push_back(icon_nodes); normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
indicatorIcons.push_back(icon_nodes);
fsi.positions.nodelist_hopsignal = numframes; }
normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen; if (!hiddenFrames.nodelist_hopsignal) {
indicatorIcons.push_back(icon_signal); fsi.positions.nodelist_hopsignal = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
fsi.positions.nodelist_distance = numframes; indicatorIcons.push_back(icon_signal);
normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen; }
indicatorIcons.push_back(icon_distance); if (!hiddenFrames.nodelist_distance) {
fsi.positions.nodelist_distance = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
indicatorIcons.push_back(icon_distance);
}
#endif #endif
#if HAS_GPS #if HAS_GPS
fsi.positions.nodelist_bearings = numframes; if (!hiddenFrames.nodelist_bearings) {
normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses; fsi.positions.nodelist_bearings = numframes;
indicatorIcons.push_back(icon_list); normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
indicatorIcons.push_back(icon_list);
fsi.positions.gps = numframes; }
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen; if (!hiddenFrames.gps) {
indicatorIcons.push_back(icon_compass); fsi.positions.gps = numframes;
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
indicatorIcons.push_back(icon_compass);
}
#endif #endif
if (RadioLibInterface::instance) { if (RadioLibInterface::instance && !hiddenFrames.lora) {
fsi.positions.lora = numframes; fsi.positions.lora = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused; normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
indicatorIcons.push_back(icon_radio); indicatorIcons.push_back(icon_radio);
} }
if (!dismissedFrames.memory) { if (!hiddenFrames.system) {
fsi.positions.memory = numframes; fsi.positions.system = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawMemoryUsage; normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen;
indicatorIcons.push_back(icon_memory); indicatorIcons.push_back(icon_system);
} }
#if !defined(DISPLAY_CLOCK_FRAME) #if !defined(DISPLAY_CLOCK_FRAME)
fsi.positions.clock = numframes; if (!hiddenFrames.clock) {
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame fsi.positions.clock = numframes;
: graphics::ClockRenderer::drawDigitalClockFrame; normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
indicatorIcons.push_back(digital_icon_clock); : graphics::ClockRenderer::drawDigitalClockFrame;
indicatorIcons.push_back(digital_icon_clock);
}
#endif #endif
#if HAS_WIFI && !defined(ARCH_PORTDUINO) #if HAS_WIFI && !defined(ARCH_PORTDUINO)
if (!dismissedFrames.wifi && isWifiAvailable()) { if (!hiddenFrames.wifi && isWifiAvailable()) {
fsi.positions.wifi = numframes; fsi.positions.wifi = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline; normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
indicatorIcons.push_back(icon_wifi); indicatorIcons.push_back(icon_wifi);
@@ -1047,27 +1062,29 @@ void Screen::setFrames(FrameFocus focus)
if (numMeshNodes > 0) if (numMeshNodes > 0)
numMeshNodes--; numMeshNodes--;
// Temporary array to hold favorite node frames if (!hiddenFrames.show_favorites) {
std::vector<FrameCallback> favoriteFrames; // Temporary array to hold favorite node frames
std::vector<FrameCallback> favoriteFrames;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) { for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i); const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) { if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo); favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
}
} }
}
// Insert favorite frames *after* collecting them all // Insert favorite frames *after* collecting them all
if (!favoriteFrames.empty()) { if (!favoriteFrames.empty()) {
fsi.positions.firstFavorite = numframes; fsi.positions.firstFavorite = numframes;
for (const auto &f : favoriteFrames) { for (const auto &f : favoriteFrames) {
normalFrames[numframes++] = f; normalFrames[numframes++] = f;
indicatorIcons.push_back(icon_node); indicatorIcons.push_back(icon_node);
}
fsi.positions.lastFavorite = numframes - 1;
} else {
fsi.positions.firstFavorite = 255;
fsi.positions.lastFavorite = 255;
} }
fsi.positions.lastFavorite = numframes - 1;
} else {
fsi.positions.firstFavorite = 255;
fsi.positions.lastFavorite = 255;
} }
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
@@ -1106,7 +1123,7 @@ void Screen::setFrames(FrameFocus focus)
ui->switchToFrame(fsi.positions.clock); ui->switchToFrame(fsi.positions.clock);
break; break;
case FOCUS_SYSTEM: case FOCUS_SYSTEM:
ui->switchToFrame(fsi.positions.memory); ui->switchToFrame(fsi.positions.system);
break; break;
case FOCUS_PRESERVE: case FOCUS_PRESERVE:
@@ -1134,30 +1151,96 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
setFastFramerate(); setFastFramerate();
} }
void Screen::toggleFrameVisibility(const std::string &frameName)
{
#ifndef USE_EINK
if (frameName == "nodelist") {
hiddenFrames.nodelist = !hiddenFrames.nodelist;
}
#endif
#ifdef USE_EINK
if (frameName == "nodelist_lastheard") {
hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard;
}
if (frameName == "nodelist_hopsignal") {
hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal;
}
if (frameName == "nodelist_distance") {
hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance;
}
#endif
#if HAS_GPS
if (frameName == "nodelist_bearings") {
hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
}
if (frameName == "gps") {
hiddenFrames.gps = !hiddenFrames.gps;
}
#endif
if (frameName == "lora") {
hiddenFrames.lora = !hiddenFrames.lora;
}
if (frameName == "clock") {
hiddenFrames.clock = !hiddenFrames.clock;
}
if (frameName == "show_favorites") {
hiddenFrames.show_favorites = !hiddenFrames.show_favorites;
}
}
bool Screen::isFrameHidden(const std::string &frameName) const
{
#ifndef USE_EINK
if (frameName == "nodelist")
return hiddenFrames.nodelist;
#endif
#ifdef USE_EINK
if (frameName == "nodelist_lastheard")
return hiddenFrames.nodelist_lastheard;
if (frameName == "nodelist_hopsignal")
return hiddenFrames.nodelist_hopsignal;
if (frameName == "nodelist_distance")
return hiddenFrames.nodelist_distance;
#endif
#if HAS_GPS
if (frameName == "nodelist_bearings")
return hiddenFrames.nodelist_bearings;
if (frameName == "gps")
return hiddenFrames.gps;
#endif
if (frameName == "lora")
return hiddenFrames.lora;
if (frameName == "clock")
return hiddenFrames.clock;
if (frameName == "show_favorites")
return hiddenFrames.show_favorites;
return false;
}
// Dismisses the currently displayed screen frame, if possible // Dismisses the currently displayed screen frame, if possible
// Relevant for text message, waypoint, others in future? // Relevant for text message, waypoint, others in future?
// Triggered with a CardKB keycombo // Triggered with a CardKB keycombo
void Screen::dismissCurrentFrame() void Screen::hideCurrentFrame()
{ {
uint8_t currentFrame = ui->getUiState()->currentFrame; uint8_t currentFrame = ui->getUiState()->currentFrame;
bool dismissed = false; bool dismissed = false;
if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) { if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
LOG_INFO("Dismiss Text Message"); LOG_INFO("Hide Text Message");
devicestate.has_rx_text_message = false; devicestate.has_rx_text_message = false;
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message)); memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
} else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) { } else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
LOG_DEBUG("Dismiss Waypoint"); LOG_DEBUG("Hide Waypoint");
devicestate.has_rx_waypoint = false; devicestate.has_rx_waypoint = false;
dismissedFrames.waypoint = true; hiddenFrames.waypoint = true;
dismissed = true; dismissed = true;
} else if (currentFrame == framesetInfo.positions.wifi) { } else if (currentFrame == framesetInfo.positions.wifi) {
LOG_DEBUG("Dismiss WiFi Screen"); LOG_DEBUG("Hide WiFi Screen");
dismissedFrames.wifi = true; hiddenFrames.wifi = true;
dismissed = true; dismissed = true;
} else if (currentFrame == framesetInfo.positions.memory) { } else if (currentFrame == framesetInfo.positions.lora) {
LOG_INFO("Dismiss Memory"); LOG_INFO("Hide LoRa");
dismissedFrames.memory = true; hiddenFrames.lora = true;
dismissed = true; dismissed = true;
} }
@@ -1309,7 +1392,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
// Outgoing message (likely sent from phone) // Outgoing message (likely sent from phone)
devicestate.has_rx_text_message = false; devicestate.has_rx_text_message = false;
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message)); memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
dismissedFrames.textMessage = true; hiddenFrames.textMessage = true;
hasUnreadMessage = false; // Clear unread state when user replies hasUnreadMessage = false; // Clear unread state when user replies
setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
@@ -1439,7 +1522,7 @@ int Screen::handleInputEvent(const InputEvent *event)
} else if (event->inputEvent == INPUT_BROKER_SELECT) { } else if (event->inputEvent == INPUT_BROKER_SELECT) {
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) { if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
menuHandler::homeBaseMenu(); menuHandler::homeBaseMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.memory) { } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) {
menuHandler::systemBaseMenu(); menuHandler::systemBaseMenu();
#if HAS_GPS #if HAS_GPS
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) { } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
@@ -1448,7 +1531,7 @@ int Screen::handleInputEvent(const InputEvent *event)
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) { } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
menuHandler::clockMenu(); menuHandler::clockMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) { } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
menuHandler::LoraRegionPicker(); menuHandler::loraMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) { } else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
if (devicestate.rx_text_message.from) { if (devicestate.rx_text_message.from) {
menuHandler::messageResponseMenu(); menuHandler::messageResponseMenu();
+25 -5
View File
@@ -593,7 +593,11 @@ class Screen : public concurrency::OSThread
void setSSLFrames(); void setSSLFrames();
// Dismiss the currently focussed frame, if possible (e.g. text message, waypoint) // Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
void dismissCurrentFrame(); void hideCurrentFrame();
// Menu-driven Show / Hide Toggle
void toggleFrameVisibility(const std::string &frameName);
bool isFrameHidden(const std::string &frameName) const;
#ifdef USE_EINK #ifdef USE_EINK
/// Draw an image to remain on E-Ink display after screen off /// Draw an image to remain on E-Ink display after screen off
@@ -655,7 +659,7 @@ class Screen : public concurrency::OSThread
uint8_t settings = 255; uint8_t settings = 255;
uint8_t wifi = 255; uint8_t wifi = 255;
uint8_t deviceFocused = 255; uint8_t deviceFocused = 255;
uint8_t memory = 255; uint8_t system = 255;
uint8_t gps = 255; uint8_t gps = 255;
uint8_t home = 255; uint8_t home = 255;
uint8_t textMessage = 255; uint8_t textMessage = 255;
@@ -673,12 +677,28 @@ class Screen : public concurrency::OSThread
uint8_t frameCount = 0; uint8_t frameCount = 0;
} framesetInfo; } framesetInfo;
struct DismissedFrames { struct hiddenFrames {
bool textMessage = false; bool textMessage = false;
bool waypoint = false; bool waypoint = false;
bool wifi = false; bool wifi = false;
bool memory = false; bool system = false;
} dismissedFrames; bool home = false;
bool clock = false;
#ifndef USE_EINK
bool nodelist = false;
#endif
#ifdef USE_EINK
bool nodelist_lastheard = false;
bool nodelist_hopsignal = false;
bool nodelist_distance = false;
#endif
#if HAS_GPS
bool nodelist_bearings = false;
bool gps = false;
#endif
bool lora = false;
bool show_favorites = false;
} hiddenFrames;
/// Try to start drawing ASAP /// Try to start drawing ASAP
void setFastFramerate(); void setFastFramerate();
+1 -1
View File
@@ -73,7 +73,7 @@
#endif #endif
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS)) && \ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
// The screen is bigger so use bigger fonts // The screen is bigger so use bigger fonts
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
+179 -20
View File
@@ -562,6 +562,91 @@ class LGFX : public lgfx::LGFX_Device
static LGFX *tft = nullptr; static LGFX *tft = nullptr;
#elif defined(ST7796_CS)
#include <LovyanGFX.hpp> // Graphics and font library for ST7796 driver chip
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ST7796 _panel_instance;
lgfx::Bus_SPI _bus_instance;
lgfx::Light_PWM _light_instance;
public:
LGFX(void)
{
{
auto cfg = _bus_instance.config();
// SPI
cfg.spi_host = ST7796_SPI_HOST;
cfg.spi_mode = 0;
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
// 80MHz by an integer)
cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
cfg.spi_3wire = false;
cfg.use_lock = true; // Set to true to use transaction locking
cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
// SPI_DMA_CH_AUTO=auto setting)
cfg.pin_sclk = ST7796_SCK; // Set SPI SCLK pin number
cfg.pin_mosi = ST7796_SDA; // Set SPI MOSI pin number
cfg.pin_miso = ST7796_MISO; // Set SPI MISO pin number (-1 = disable)
cfg.pin_dc = ST7796_RS; // Set SPI DC pin number (-1 = disable)
_bus_instance.config(cfg); // applies the set value to the bus.
_panel_instance.setBus(&_bus_instance); // set the bus on the panel.
}
{ // Set the display panel control.
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
cfg.pin_cs = ST7796_CS; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = ST7796_RESET; // Pin number where RST is connected (-1 = disable)
cfg.pin_busy = ST7796_BUSY; // Pin number where BUSY is connected (-1 = disable)
// cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
// cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
cfg.panel_width = TFT_WIDTH; // actual displayable width
cfg.panel_height = TFT_HEIGHT; // actual displayable height
cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored)
#ifdef TFT_DUMMY_READ_PIXELS
cfg.dummy_read_pixel = TFT_DUMMY_READ_PIXELS; // Number of bits for dummy read before pixel readout
#else
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
#endif
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
cfg.readable = true; // Set to true if data can be read
cfg.invert = true; // Set to true if the light/darkness of the panel is reversed
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
cfg.dlen_16bit =
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
_panel_instance.config(cfg);
}
#ifdef ST7796_BL
// Set the backlight control. (delete if not necessary)
{
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
cfg.pin_bl = ST7796_BL; // Pin number to which the backlight is connected
cfg.invert = false; // true to invert the brightness of the backlight
cfg.freq = 44100;
cfg.pwm_channel = 7;
_light_instance.config(cfg);
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
}
#endif
setPanel(&_panel_instance); // Sets the panel to use.
}
};
static LGFX *tft = nullptr;
#elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) #elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER)
#include <LovyanGFX.hpp> // Graphics and font library for ILI9341/ILI9342 driver chip #include <LovyanGFX.hpp> // Graphics and font library for ILI9341/ILI9342 driver chip
@@ -997,8 +1082,9 @@ static LGFX *tft = nullptr;
#endif #endif
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ #if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ST7796_CS) || defined(ILI9341_DRIVER) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || (ARCH_PORTDUINO && HAS_SCREEN != 0) defined(ILI9342_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || \
(ARCH_PORTDUINO && HAS_SCREEN != 0)
#include "SPILock.h" #include "SPILock.h"
#include "TFTDisplay.h" #include "TFTDisplay.h"
#include <SPI.h> #include <SPI.h>
@@ -1047,32 +1133,97 @@ void TFTDisplay::display(bool fromBlank)
{ {
if (fromBlank) if (fromBlank)
tft->fillScreen(TFT_BLACK); tft->fillScreen(TFT_BLACK);
// tft->clear();
concurrency::LockGuard g(spiLock); concurrency::LockGuard g(spiLock);
uint16_t x, y; uint32_t x, y;
uint32_t y_byteIndex;
uint8_t y_byteMask;
uint32_t x_FirstPixelUpdate;
uint32_t x_LastPixelUpdate;
bool isset, dblbuf_isset;
uint16_t colorTftMesh, colorTftBlack;
bool somethingChanged = false;
for (y = 0; y < displayHeight; y++) { // Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step
for (x = 0; x < displayWidth; x++) { colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8);
auto isset = buffer[x + (y / 8) * displayWidth] & (1 << (y & 7)); colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8);
y = 0;
while (y < displayHeight) {
y_byteIndex = (y / 8) * displayWidth;
y_byteMask = (1 << (y & 7));
// Step 1: Do a quick scan of 8 rows together. This allows fast-forwarding over unchanged screen areas.
if (y_byteMask == 1) {
if (!fromBlank) { if (!fromBlank) {
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent for (x = 0; x < displayWidth; x++) {
auto dblbuf_isset = buffer_back[x + (y / 8) * displayWidth] & (1 << (y & 7)); if (buffer[x + y_byteIndex] != buffer_back[x + y_byteIndex])
if (isset != dblbuf_isset) { break;
tft->drawPixel(x, y, isset ? TFT_MESH : TFT_BLACK);
} }
} else if (isset) { } else {
tft->drawPixel(x, y, TFT_MESH); for (x = 0; x < displayWidth; x++) {
if (buffer[x + y_byteIndex] != 0)
break;
}
}
if (x >= displayWidth) {
// No changed pixels found in these 8 rows, fast-forward to the next 8
y = y + 8;
continue;
} }
} }
// Step 2: Scan each of the 8 rows individually. Find the first pixel in each row that needs updating
for (x_FirstPixelUpdate = 0; x_FirstPixelUpdate < displayWidth; x_FirstPixelUpdate++) {
isset = buffer[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
if (!fromBlank) {
// get src pixel in the page based ordering the OLED lib uses
dblbuf_isset = buffer_back[x_FirstPixelUpdate + y_byteIndex] & y_byteMask;
if (isset != dblbuf_isset) {
break;
}
} else if (isset) {
break;
}
}
// Did we find a pixel that needs updating on this row?
if (x_FirstPixelUpdate < displayWidth) {
// Quickly write out the first changed pixel (saves another array lookup)
linePixelBuffer[x_FirstPixelUpdate] = isset ? colorTftMesh : colorTftBlack;
x_LastPixelUpdate = x_FirstPixelUpdate;
// Step 3: copy all remaining pixels in this row into the pixel line buffer,
// while also recording the last pixel in the row that needs updating
for (x = x_FirstPixelUpdate + 1; x < displayWidth; x++) {
isset = buffer[x + y_byteIndex] & y_byteMask;
linePixelBuffer[x] = isset ? colorTftMesh : colorTftBlack;
if (!fromBlank) {
dblbuf_isset = buffer_back[x + y_byteIndex] & y_byteMask;
if (isset != dblbuf_isset) {
x_LastPixelUpdate = x;
}
} else if (isset) {
x_LastPixelUpdate = x;
}
}
// Step 4: Send the changed pixels on this line to the screen as a single block transfer.
// This function accepts pixel data MSB first so it can dump the memory straight out the SPI port.
tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
&linePixelBuffer[x_FirstPixelUpdate]);
somethingChanged = true;
}
y++;
} }
// Copy the Buffer to the Back Buffer // Copy the Buffer to the Back Buffer
for (y = 0; y < (displayHeight / 8); y++) { if (somethingChanged)
for (x = 0; x < displayWidth; x++) { memcpy(buffer_back, buffer, displayBufferSize);
uint16_t pos = x + y * displayWidth;
buffer_back[pos] = buffer[pos];
}
}
} }
void TFTDisplay::sdlLoop() void TFTDisplay::sdlLoop()
@@ -1264,13 +1415,21 @@ bool TFTDisplay::connect()
tft->setRotation(1); // T-Deck has the TFT in landscape tft->setRotation(1); // T-Deck has the TFT in landscape
#elif defined(T_WATCH_S3) #elif defined(T_WATCH_S3)
tft->setRotation(2); // T-Watch S3 left-handed orientation tft->setRotation(2); // T-Watch S3 left-handed orientation
#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) #elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) || defined(T_LORA_PAGER)
tft->setRotation(0); // use config.yaml to set rotation tft->setRotation(0); // use config.yaml to set rotation
#else #else
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
#endif #endif
tft->fillScreen(TFT_BLACK); tft->fillScreen(TFT_BLACK);
if (this->linePixelBuffer == NULL) {
this->linePixelBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth);
if (!this->linePixelBuffer) {
LOG_ERROR("Not enough memory to create TFT line buffer\n");
return false;
}
}
return true; return true;
} }
+2
View File
@@ -58,4 +58,6 @@ class TFTDisplay : public OLEDDisplay
// Connect to the display // Connect to the display
virtual bool connect() override; virtual bool connect() override;
uint16_t *linePixelBuffer = nullptr;
}; };
+7 -11
View File
@@ -94,7 +94,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat, if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
(storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit (storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12,
8, imgQuestionL1); 8, imgQuestionL1);
@@ -106,7 +107,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
#endif #endif
} else { } else {
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16, display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16,
8, imgSFL1); 8, imgSFL1);
@@ -121,7 +122,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16
} else { } else {
// TODO: Raspberry Pi supports more than just the one screen size // TODO: Raspberry Pi supports more than just the one screen size
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \
ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1); imgInfoL1);
@@ -261,12 +263,6 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
display->drawString(x + 1, y, "USB"); display->drawString(x + 1, y, "USB");
} }
// auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true);
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
// if (config.display.heading_bold)
// display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
uint32_t currentMillis = millis(); uint32_t currentMillis = millis();
uint32_t seconds = currentMillis / 1000; uint32_t seconds = currentMillis / 1000;
uint32_t minutes = seconds / 60; uint32_t minutes = seconds / 60;
@@ -396,7 +392,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->drawString(nameX, getTextPositions(display)[line++], shortnameble); display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
// === Second Row: Radio Preset === // === Second Row: Radio Preset ===
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
char regionradiopreset[25]; char regionradiopreset[25];
const char *region = myRegion ? myRegion->name : NULL; const char *region = myRegion ? myRegion->name : NULL;
if (region != nullptr) { if (region != nullptr) {
@@ -485,7 +481,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
// **************************** // ****************************
// * System Screen * // * System Screen *
// **************************** // ****************************
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{ {
display->clear(); display->clear();
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
+2 -2
View File
@@ -31,8 +31,8 @@ void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state
// LoRa information display // LoRa information display
void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// Memory screen display // System screen display
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
} // namespace DebugRenderer } // namespace DebugRenderer
} // namespace graphics } // namespace graphics
+150 -9
View File
@@ -29,6 +29,24 @@ menuHandler::screenMenus menuHandler::menuQueue = menu_none;
bool test_enabled = false; bool test_enabled = false;
uint8_t test_count = 0; uint8_t test_count = 0;
void menuHandler::loraMenu()
{
static const char *optionsArray[] = {"Back", "Region Picker"};
enum optionsNumbers { Back = 0, lora_picker = 1 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "LoRa Actions";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
// No action
} else if (selected == lora_picker) {
menuHandler::menuQueue = menuHandler::lora_picker;
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::OnboardMessage() void menuHandler::OnboardMessage()
{ {
static const char *optionsArray[] = {"OK", "Got it!"}; static const char *optionsArray[] = {"OK", "Got it!"};
@@ -308,7 +326,7 @@ void menuHandler::messageResponseMenu()
bannerOptions.optionsCount = options; bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dismiss) { if (selected == Dismiss) {
screen->dismissCurrentFrame(); screen->hideCurrentFrame();
} else if (selected == Preset) { } else if (selected == Preset) {
if (devicestate.rx_text_message.to == NODENUM_BROADCAST) { if (devicestate.rx_text_message.to == NODENUM_BROADCAST) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel); cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel);
@@ -349,8 +367,11 @@ void menuHandler::homeBaseMenu()
optionsArray[options] = "Sleep Screen"; optionsArray[options] = "Sleep Screen";
optionsEnumArray[options++] = Sleep; optionsEnumArray[options++] = Sleep;
#endif #endif
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
optionsArray[options] = "Send Position"; optionsArray[options] = "Send Position";
} else {
optionsArray[options] = "Send Node Info";
}
optionsEnumArray[options++] = Position; optionsEnumArray[options++] = Position;
optionsArray[options] = "New Preset Msg"; optionsArray[options] = "New Preset Msg";
optionsEnumArray[options++] = Preset; optionsEnumArray[options++] = Preset;
@@ -430,19 +451,22 @@ void menuHandler::textMessageBaseMenu()
void menuHandler::systemBaseMenu() void menuHandler::systemBaseMenu()
{ {
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd }; enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"}; static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back}; static int optionsEnumArray[enumEnd] = {Back};
int options = 1; int options = 1;
optionsArray[options] = "Notifications"; optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Notifications; optionsEnumArray[options++] = Notifications;
#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \ #if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \
defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Screen Options"; optionsArray[options] = "Screen Options";
optionsEnumArray[options++] = ScreenOptions; optionsEnumArray[options++] = ScreenOptions;
#endif #endif
optionsArray[options] = "Frame Visiblity Toggle";
optionsEnumArray[options++] = FrameToggles;
optionsArray[options] = "Bluetooth Toggle"; optionsArray[options] = "Bluetooth Toggle";
optionsEnumArray[options++] = Bluetooth; optionsEnumArray[options++] = Bluetooth;
@@ -469,6 +493,9 @@ void menuHandler::systemBaseMenu()
} else if (selected == PowerMenu) { } else if (selected == PowerMenu) {
menuHandler::menuQueue = menuHandler::power_menu; menuHandler::menuQueue = menuHandler::power_menu;
screen->runNow(); screen->runNow();
} else if (selected == FrameToggles) {
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == Test) { } else if (selected == Test) {
menuHandler::menuQueue = menuHandler::test_menu; menuHandler::menuQueue = menuHandler::test_menu;
screen->runNow(); screen->runNow();
@@ -535,6 +562,7 @@ void menuHandler::positionBaseMenu()
optionsArray[options] = "Compass Calibrate"; optionsArray[options] = "Compass Calibrate";
optionsEnumArray[options++] = CompassCalibrate; optionsEnumArray[options++] = CompassCalibrate;
} }
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Position Action"; bannerOptions.message = "Position Action";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
@@ -728,7 +756,7 @@ void menuHandler::BrightnessPickerMenu()
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190)
// For HELTEC devices, use analogWrite to control backlight // For HELTEC devices, use analogWrite to control backlight
analogWrite(VTFT_LEDA, uiconfig.screen_brightness); analogWrite(VTFT_LEDA, uiconfig.screen_brightness);
#elif defined(ST7789_CS) #elif defined(ST7789_CS) || defined(ST7796_CS)
static_cast<TFTDisplay *>(screen->getDisplayDevice())->setDisplayBrightness(uiconfig.screen_brightness); static_cast<TFTDisplay *>(screen->getDisplayDevice())->setDisplayBrightness(uiconfig.screen_brightness);
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) #elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
screen->getDisplayDevice()->setBrightness(uiconfig.screen_brightness); screen->getDisplayDevice()->setBrightness(uiconfig.screen_brightness);
@@ -771,7 +799,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 10; bannerOptions.optionsCount = 10;
bannerOptions.bannerCallback = [display](int selected) -> void { bannerOptions.bannerCallback = [display](int selected) -> void {
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
uint8_t TFT_MESH_r = 0; uint8_t TFT_MESH_r = 0;
uint8_t TFT_MESH_g = 0; uint8_t TFT_MESH_g = 0;
uint8_t TFT_MESH_b = 0; uint8_t TFT_MESH_b = 0;
@@ -1048,7 +1076,7 @@ void menuHandler::screenOptionsMenu()
} }
// Only show screen color for TFT displays // Only show screen color for TFT displays
#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT
optionsArray[options] = "Screen Color"; optionsArray[options] = "Screen Color";
optionsEnumArray[options++] = ScreenColor; optionsEnumArray[options++] = ScreenColor;
#endif #endif
@@ -1146,6 +1174,116 @@ void menuHandler::keyVerificationFinalPrompt()
} }
} }
void menuHandler::FrameToggles_menu()
{
enum optionsNumbers {
Finish,
nodelist,
nodelist_lastheard,
nodelist_hopsignal,
nodelist_distance,
nodelist_bearings,
gps,
lora,
clock,
show_favorites,
enumEnd
};
static const char *optionsArray[enumEnd] = {"Finish"};
static int optionsEnumArray[enumEnd] = {Finish};
int options = 1;
// Track last selected index (not enum value!)
static int lastSelectedIndex = 0;
#ifndef USE_EINK
optionsArray[options] = screen->isFrameHidden("nodelist") ? "Show Node List" : "Hide Node List";
optionsEnumArray[options++] = nodelist;
#endif
#ifdef USE_EINK
optionsArray[options] = screen->isFrameHidden("nodelist_lastheard") ? "Show NL - Last Heard" : "Hide NL - Last Heard";
optionsEnumArray[options++] = nodelist_lastheard;
optionsArray[options] = screen->isFrameHidden("nodelist_hopsignal") ? "Show NL - Hops/Signal" : "Hide NL - Hops/Signal";
optionsEnumArray[options++] = nodelist_hopsignal;
optionsArray[options] = screen->isFrameHidden("nodelist_distance") ? "Show NL - Distance" : "Hide NL - Distance";
optionsEnumArray[options++] = nodelist_distance;
#endif
#if HAS_GPS
optionsArray[options] = screen->isFrameHidden("nodelist_bearings") ? "Show Bearings" : "Hide Bearings";
optionsEnumArray[options++] = nodelist_bearings;
optionsArray[options] = screen->isFrameHidden("gps") ? "Show Position" : "Hide Position";
optionsEnumArray[options++] = gps;
#endif
optionsArray[options] = screen->isFrameHidden("lora") ? "Show LoRa" : "Hide LoRa";
optionsEnumArray[options++] = lora;
optionsArray[options] = screen->isFrameHidden("clock") ? "Show Clock" : "Hide Clock";
optionsEnumArray[options++] = clock;
optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
optionsEnumArray[options++] = show_favorites;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Show/Hide Frames";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.InitialSelected = lastSelectedIndex; // Use index, not enum value
bannerOptions.bannerCallback = [optionsEnumArray, options](int selected) mutable -> void {
// Find the index of selected in optionsEnumArray
int idx = 0;
for (; idx < options; ++idx) {
if (optionsEnumArray[idx] == selected)
break;
}
lastSelectedIndex = idx;
if (selected == Finish) {
screen->setFrames(Screen::FOCUS_DEFAULT);
} else if (selected == nodelist) {
screen->toggleFrameVisibility("nodelist");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_lastheard) {
screen->toggleFrameVisibility("nodelist_lastheard");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_hopsignal) {
screen->toggleFrameVisibility("nodelist_hopsignal");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_distance) {
screen->toggleFrameVisibility("nodelist_distance");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_bearings) {
screen->toggleFrameVisibility("nodelist_bearings");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == gps) {
screen->toggleFrameVisibility("gps");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == lora) {
screen->toggleFrameVisibility("lora");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == clock) {
screen->toggleFrameVisibility("clock");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == show_favorites) {
screen->toggleFrameVisibility("show_favorites");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::handleMenuSwitch(OLEDDisplay *display) void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{ {
if (menuQueue != menu_none) if (menuQueue != menu_none)
@@ -1242,6 +1380,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case power_menu: case power_menu:
powerMenu(); powerMenu();
break; break;
case FrameToggles:
FrameToggles_menu();
break;
case throttle_message: case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000); screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break; break;
+3
View File
@@ -39,11 +39,13 @@ class menuHandler
key_verification_final_prompt, key_verification_final_prompt,
trace_route_menu, trace_route_menu,
throttle_message, throttle_message,
FrameToggles
}; };
static screenMenus menuQueue; static screenMenus menuQueue;
static void OnboardMessage(); static void OnboardMessage();
static void LoraRegionPicker(uint32_t duration = 30000); static void LoraRegionPicker(uint32_t duration = 30000);
static void loraMenu();
static void handleMenuSwitch(OLEDDisplay *display); static void handleMenuSwitch(OLEDDisplay *display);
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm); static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
static void clockMenu(); static void clockMenu();
@@ -76,6 +78,7 @@ class menuHandler
static void notificationsMenu(); static void notificationsMenu();
static void screenOptionsMenu(); static void screenOptionsMenu();
static void powerMenu(); static void powerMenu();
static void FrameToggles_menu();
private: private:
static void saveUIConfig(); static void saveUIConfig();
+39 -19
View File
@@ -7,10 +7,18 @@
#include "graphics/ScreenFonts.h" #include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h" #include "graphics/SharedUIDisplay.h"
#include "graphics/images.h" #include "graphics/images.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
#if HAS_BUTTON
#include "input/ButtonThread.h"
#endif
#include "main.h" #include "main.h"
#include <algorithm> #include <algorithm>
#include <string> #include <string>
#include <vector> #include <vector>
#if HAS_TRACKBALL
#include "input/TrackballInterruptImpl1.h"
#endif
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
@@ -18,6 +26,11 @@
using namespace meshtastic; using namespace meshtastic;
#if HAS_BUTTON
// Global button thread pointer defined in main.cpp
extern ::ButtonThread *UserButtonThread;
#endif
// External references to global variables from Screen.cpp // External references to global variables from Screen.cpp
extern std::vector<std::string> functionSymbol; extern std::vector<std::string> functionSymbol;
extern std::string functionSymbolString; extern std::string functionSymbolString;
@@ -288,12 +301,9 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) { if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) {
std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name); std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name);
strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1); strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1);
} else { } else {
snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF)); snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF));
} }
// make temp buffer for name
// fi
if (i == curSelected) { if (i == curSelected) {
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num; selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
if (isHighResolution) { if (isHighResolution) {
@@ -307,7 +317,8 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
} }
scratchLineBuffer[scratchLineNum][39] = '\0'; scratchLineBuffer[scratchLineNum][39] = '\0';
} else { } else {
strncpy(scratchLineBuffer[scratchLineNum], temp_name, 36); strncpy(scratchLineBuffer[scratchLineNum], temp_name, 39);
scratchLineBuffer[scratchLineNum][39] = '\0';
} }
linePointers[linesShown] = scratchLineBuffer[scratchLineNum++]; linePointers[linesShown] = scratchLineBuffer[scratchLineNum++];
} }
@@ -623,59 +634,68 @@ void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiStat
return; return;
} }
// Handle input events for virtual keyboard navigation
if (inEvent.inputEvent != INPUT_BROKER_NONE) { if (inEvent.inputEvent != INPUT_BROKER_NONE) {
if (inEvent.inputEvent == INPUT_BROKER_UP) { if (inEvent.inputEvent == INPUT_BROKER_UP) {
virtualKeyboard->moveCursorUp(); // high frequency for move cursor left/right than up/down with encoders
extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorLeft();
} else {
virtualKeyboard->moveCursorUp();
}
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN) { } else if (inEvent.inputEvent == INPUT_BROKER_DOWN) {
virtualKeyboard->moveCursorDown(); extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorRight();
} else {
virtualKeyboard->moveCursorDown();
}
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) { } else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
virtualKeyboard->moveCursorLeft(); virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_RIGHT) { } else if (inEvent.inputEvent == INPUT_BROKER_RIGHT) {
virtualKeyboard->moveCursorRight(); virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_UP_LONG) {
virtualKeyboard->moveCursorUp();
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) {
virtualKeyboard->moveCursorDown();
} else if (inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
// Long press UP = move left
virtualKeyboard->moveCursorLeft(); virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) { } else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
// Long press DOWN = move right
virtualKeyboard->moveCursorRight(); virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
virtualKeyboard->handlePress(); virtualKeyboard->handlePress();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT_LONG) { } else if (inEvent.inputEvent == INPUT_BROKER_SELECT_LONG) {
virtualKeyboard->handleLongPress(); virtualKeyboard->handleLongPress();
} else if (inEvent.inputEvent == INPUT_BROKER_CANCEL) { } else if (inEvent.inputEvent == INPUT_BROKER_CANCEL) {
// Cancel virtual keyboard - call callback with empty string auto callback = textInputCallback;
auto callback = textInputCallback; // Store callback before clearing
// Clean up first to prevent re-entry
delete virtualKeyboard; delete virtualKeyboard;
virtualKeyboard = nullptr; virtualKeyboard = nullptr;
textInputCallback = nullptr; textInputCallback = nullptr;
resetBanner(); resetBanner();
// Call callback after cleanup
if (callback) { if (callback) {
callback(""); callback("");
} }
// Restore normal overlays
if (screen) { if (screen) {
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
} }
return; return;
} }
// Reset input event after processing // Consume the event after processing for virtual keyboard
inEvent.inputEvent = INPUT_BROKER_NONE; inEvent.inputEvent = INPUT_BROKER_NONE;
} }
// Clear the display and draw virtual keyboard // Clear the screen to avoid overlapping with underlying frames or overlays
display->setColor(BLACK); display->setColor(BLACK);
display->fillRect(0, 0, display->getWidth(), display->getHeight()); display->fillRect(0, 0, display->getWidth(), display->getHeight());
display->setColor(WHITE); display->setColor(WHITE);
// Draw the virtual keyboard
virtualKeyboard->draw(display, 0, 0); virtualKeyboard->draw(display, 0, 0);
} else { } else {
// If virtualKeyboard is null, reset the banner to avoid getting stuck // If virtualKeyboard is null, reset the banner to avoid getting stuck
LOG_INFO("Virtual keyboard is null - resetting banner");
resetBanner(); resetBanner();
} }
} }
+1 -1
View File
@@ -194,7 +194,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
} }
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \ defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
if (isHighResolution) { if (isHighResolution) {
+4 -3
View File
@@ -27,7 +27,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03
0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f};
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || ARCH_PORTDUINO) && \ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \
ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS) !defined(DISPLAY_FORCE_SMALL_FONTS)
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
@@ -117,8 +118,8 @@ const uint8_t icon_radio[] PROGMEM = {
0xA9 // Row 7: #..#.#.# 0xA9 // Row 7: #..#.#.#
}; };
// 🪙 Memory Icon // 🪙 System Icon
const uint8_t icon_memory[] PROGMEM = { const uint8_t icon_system[] PROGMEM = {
0x24, // Row 0: ..#..#.. 0x24, // Row 0: ..#..#..
0x3C, // Row 1: ..####.. 0x3C, // Row 1: ..####..
0xC3, // Row 2: ##....## 0xC3, // Row 2: ##....##
@@ -0,0 +1,68 @@
#include "./ZJY122250_0213BAAMFGN.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
using namespace NicheGraphics::Drivers;
// Map the display controller IC's output to the connected panel
void ZJY122250_0213BAAMFGN::configScanning()
{
// "Driver output control"
// Scan gates from 0 to 249 (vertical resolution 250px)
sendCommand(0x01);
sendData(0xF9);
sendData(0x00);
sendData(0x00);
}
// Specify which information is used to control the sequence of voltages applied to move the pixels
// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from
// the controller IC's OTP memory, when the update procedure begins.
void ZJY122250_0213BAAMFGN::configWaveform()
{
switch (updateType) {
case FAST:
sendCommand(0x3C); // Border waveform:
sendData(0x80); // VCOM
break;
case FULL:
default:
sendCommand(0x3C); // Border waveform:
sendData(0x01); // Follow LUT 1 (blink same as white pixels)
break;
}
sendCommand(0x18); // Temperature sensor:
sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform
}
void ZJY122250_0213BAAMFGN::configUpdateSequence()
{
switch (updateType) {
case FAST:
sendCommand(0x22); // Set "update sequence"
sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh"
break;
case FULL:
default:
sendCommand(0x22); // Set "update sequence"
sendData(0xF7); // Will load LUT from OTP memory
break;
}
}
// Once the refresh operation has been started,
// begin periodically polling the display to check for completion, using the normal Meshtastic threading code
// Only used when refresh is "async"
void ZJY122250_0213BAAMFGN::detachFromUpdate()
{
switch (updateType) {
case FAST:
return beginPolling(50, 500); // At least 500ms for fast refresh
case FULL:
default:
return beginPolling(100, 2000); // At least 2 seconds for full refresh
}
}
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -0,0 +1,42 @@
/*
E-Ink display driver
- ZJY122250_0213BAAMFGN
- Manufacturer: Zhongjingyuan
- Size: 2.13 inch
- Resolution: 250px x 122px
- Flex connector marking (not a unique identifier): FPC-A002
*/
#pragma once
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "configuration.h"
#include "./SSD16XX.h"
namespace NicheGraphics::Drivers
{
class ZJY122250_0213BAAMFGN : public SSD16XX
{
// Display properties
private:
static constexpr uint32_t width = 122;
static constexpr uint32_t height = 250;
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
public:
ZJY122250_0213BAAMFGN() : SSD16XX(width, height, supported) {}
protected:
virtual void configScanning() override;
virtual void configWaveform() override;
virtual void configUpdateSequence() override;
void detachFromUpdate() override;
};
} // namespace NicheGraphics::Drivers
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
@@ -7,12 +7,7 @@ using namespace NicheGraphics;
// Timing for "maintenance" // Timing for "maintenance"
// Paying off full-refresh debt with unprovoked updates, if the display is not very active // Paying off full-refresh debt with unprovoked updates, if the display is not very active
#ifdef SEEED_WIO_TRACKER_L1
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 5 * 1000UL;
#else
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL; static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL;
#endif
static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL; static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL;
InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator") InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator")
+3
View File
@@ -76,6 +76,9 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
return digitalRead(buttonPin); // Most buttons are active low by default return digitalRead(buttonPin); // Most buttons are active low by default
} }
// Returns true while this thread's button is physically held down
bool isHeld() { return isButtonPressed(_pinNum); }
// Disconnect and reconnect interrupts for light sleep // Disconnect and reconnect interrupts for light sleep
#ifdef ARCH_ESP32 #ifdef ARCH_ESP32
int beforeLightSleep(void *unused); int beforeLightSleep(void *unused);
+3 -1
View File
@@ -4,7 +4,9 @@
enum input_broker_event { enum input_broker_event {
INPUT_BROKER_NONE = 0, INPUT_BROKER_NONE = 0,
INPUT_BROKER_SELECT = 10, INPUT_BROKER_SELECT = 10,
INPUT_BROKER_SELECT_LONG, INPUT_BROKER_SELECT_LONG = 11,
INPUT_BROKER_UP_LONG = 12,
INPUT_BROKER_DOWN_LONG = 13,
INPUT_BROKER_UP = 17, INPUT_BROKER_UP = 17,
INPUT_BROKER_DOWN = 18, INPUT_BROKER_DOWN = 18,
INPUT_BROKER_LEFT = 19, INPUT_BROKER_LEFT = 19,
+76
View File
@@ -0,0 +1,76 @@
#ifdef T_LORA_PAGER
#include "RotaryEncoderImpl.h"
#include "InputBroker.h"
#include "RotaryEncoder.h"
#define ORIGIN_NAME "RotaryEncoder"
RotaryEncoderImpl *rotaryEncoderImpl;
RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME)
{
rotary = nullptr;
}
bool RotaryEncoderImpl::init()
{
if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 ||
moduleConfig.canned_message.inputbroker_pin_b == 0) {
// Input device is disabled.
disable();
return false;
}
eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw);
eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b,
moduleConfig.canned_message.inputbroker_pin_press);
rotary->resetButton();
inputBroker->registerSource(this);
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
eventPressed);
return true;
}
int32_t RotaryEncoderImpl::runOnce()
{
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
e.source = this->originName;
static uint32_t lastPressed = millis();
if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) {
if (lastPressed + 200 < millis()) {
LOG_DEBUG("Rotary event Press");
lastPressed = millis();
e.inputEvent = this->eventPressed;
}
} else {
switch (rotary->process()) {
case RotaryEncoder::DIRECTION_CW:
LOG_DEBUG("Rotary event CW");
e.inputEvent = this->eventCw;
break;
case RotaryEncoder::DIRECTION_CCW:
LOG_DEBUG("Rotary event CCW");
e.inputEvent = this->eventCcw;
break;
default:
break;
}
}
if (e.inputEvent != INPUT_BROKER_NONE) {
this->notifyObservers(&e);
}
return 20;
}
#endif
+28
View File
@@ -0,0 +1,28 @@
#pragma once
// This is a non-interrupt version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library)
#include "InputBroker.h"
#include "concurrency/OSThread.h"
#include "mesh/NodeDB.h"
class RotaryEncoder;
class RotaryEncoderImpl : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
RotaryEncoderImpl();
bool init(void);
protected:
virtual int32_t runOnce() override;
input_broker_event eventCw = INPUT_BROKER_NONE;
input_broker_event eventCcw = INPUT_BROKER_NONE;
input_broker_event eventPressed = INPUT_BROKER_NONE;
RotaryEncoder *rotary;
const char *originName;
};
extern RotaryEncoderImpl *rotaryEncoderImpl;
+52 -12
View File
@@ -8,24 +8,35 @@ RotaryEncoderInterruptBase::RotaryEncoderInterruptBase(const char *name) : concu
void RotaryEncoderInterruptBase::init( void RotaryEncoderInterruptBase::init(
uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw, uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw,
input_broker_event eventPressed, input_broker_event eventPressed, input_broker_event eventPressedLong,
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress) : // std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress) :
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)()) void (*onIntA)(), void (*onIntB)(), void (*onIntPress)())
{ {
this->_pinA = pinA; this->_pinA = pinA;
this->_pinB = pinB; this->_pinB = pinB;
this->_pinPress = pinPress;
this->_eventCw = eventCw; this->_eventCw = eventCw;
this->_eventCcw = eventCcw; this->_eventCcw = eventCcw;
this->_eventPressed = eventPressed; this->_eventPressed = eventPressed;
this->_eventPressedLong = eventPressedLong;
pinMode(pinPress, INPUT_PULLUP); bool isRAK = false;
pinMode(this->_pinA, INPUT_PULLUP); #ifdef RAK_4631
pinMode(this->_pinB, INPUT_PULLUP); isRAK = true;
#endif
// attachInterrupt(pinPress, onIntPress, RISING); if (!isRAK || pinPress != 0) {
attachInterrupt(pinPress, onIntPress, RISING); pinMode(pinPress, INPUT_PULLUP);
attachInterrupt(this->_pinA, onIntA, CHANGE); attachInterrupt(pinPress, onIntPress, RISING);
attachInterrupt(this->_pinB, onIntB, CHANGE); }
if (!isRAK || this->_pinA != 0) {
pinMode(this->_pinA, INPUT_PULLUP);
attachInterrupt(this->_pinA, onIntA, CHANGE);
}
if (!isRAK || this->_pinA != 0) {
pinMode(this->_pinB, INPUT_PULLUP);
attachInterrupt(this->_pinB, onIntB, CHANGE);
}
this->rotaryLevelA = digitalRead(this->_pinA); this->rotaryLevelA = digitalRead(this->_pinA);
this->rotaryLevelB = digitalRead(this->_pinB); this->rotaryLevelB = digitalRead(this->_pinB);
@@ -37,10 +48,37 @@ int32_t RotaryEncoderInterruptBase::runOnce()
InputEvent e; InputEvent e;
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName; e.source = this->_originName;
unsigned long now = millis();
// Handle press long/short detection
if (this->action == ROTARY_ACTION_PRESSED) { if (this->action == ROTARY_ACTION_PRESSED) {
LOG_DEBUG("Rotary event Press"); bool buttonPressed = !digitalRead(_pinPress);
e.inputEvent = this->_eventPressed; if (!pressDetected && buttonPressed) {
pressDetected = true;
pressStartTime = now;
}
if (pressDetected) {
uint32_t duration = now - pressStartTime;
if (!buttonPressed) {
// released -> if short press, send short, else already sent long
if (duration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) {
lastPressKeyTime = now;
LOG_DEBUG("Rotary event Press short");
e.inputEvent = this->_eventPressed;
}
pressDetected = false;
pressStartTime = 0;
lastPressLongEventTime = 0;
this->action = ROTARY_ACTION_NONE;
} else if (duration >= LONG_PRESS_DURATION && this->_eventPressedLong != INPUT_BROKER_NONE &&
lastPressLongEventTime == 0) {
// fire single-shot long press
lastPressLongEventTime = now;
LOG_DEBUG("Rotary event Press long");
e.inputEvent = this->_eventPressedLong;
}
}
} else if (this->action == ROTARY_ACTION_CW) { } else if (this->action == ROTARY_ACTION_CW) {
LOG_DEBUG("Rotary event CW"); LOG_DEBUG("Rotary event CW");
e.inputEvent = this->_eventCw; e.inputEvent = this->_eventCw;
@@ -53,7 +91,9 @@ int32_t RotaryEncoderInterruptBase::runOnce()
this->notifyObservers(&e); this->notifyObservers(&e);
} }
this->action = ROTARY_ACTION_NONE; if (!pressDetected) {
this->action = ROTARY_ACTION_NONE;
}
return INT32_MAX; return INT32_MAX;
} }
@@ -61,7 +101,7 @@ int32_t RotaryEncoderInterruptBase::runOnce()
void RotaryEncoderInterruptBase::intPressHandler() void RotaryEncoderInterruptBase::intPressHandler()
{ {
this->action = ROTARY_ACTION_PRESSED; this->action = ROTARY_ACTION_PRESSED;
setIntervalFromNow(20); // TODO: this modifies a non-volatile variable! setIntervalFromNow(20); // start checking for long/short
} }
void RotaryEncoderInterruptBase::intAHandler() void RotaryEncoderInterruptBase::intAHandler()
+13 -1
View File
@@ -13,7 +13,7 @@ class RotaryEncoderInterruptBase : public Observable<const InputEvent *>, public
public: public:
explicit RotaryEncoderInterruptBase(const char *name); explicit RotaryEncoderInterruptBase(const char *name);
void init(uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw, void init(uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw,
input_broker_event eventPressed, input_broker_event eventPressed, input_broker_event eventPressedLong,
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress); // std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress);
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)()); void (*onIntA)(), void (*onIntB)(), void (*onIntPress)());
void intPressHandler(); void intPressHandler();
@@ -33,10 +33,22 @@ class RotaryEncoderInterruptBase : public Observable<const InputEvent *>, public
volatile RotaryEncoderInterruptBaseActionType action = ROTARY_ACTION_NONE; volatile RotaryEncoderInterruptBaseActionType action = ROTARY_ACTION_NONE;
private: private:
// pins and events
uint8_t _pinA = 0; uint8_t _pinA = 0;
uint8_t _pinB = 0; uint8_t _pinB = 0;
uint8_t _pinPress = 0;
input_broker_event _eventCw = INPUT_BROKER_NONE; input_broker_event _eventCw = INPUT_BROKER_NONE;
input_broker_event _eventCcw = INPUT_BROKER_NONE; input_broker_event _eventCcw = INPUT_BROKER_NONE;
input_broker_event _eventPressed = INPUT_BROKER_NONE; input_broker_event _eventPressed = INPUT_BROKER_NONE;
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
const char *_originName; const char *_originName;
// Long press detection variables
uint32_t pressStartTime = 0;
bool pressDetected = false;
uint32_t lastPressLongEventTime = 0;
unsigned long lastPressKeyTime = 0;
static const uint32_t LONG_PRESS_DURATION = 300; // ms
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 0; // 0 = single-shot for rotary select
const unsigned long pressDebounceMs = 200; // ms
}; };
+4 -1
View File
@@ -1,5 +1,6 @@
#include "RotaryEncoderInterruptImpl1.h" #include "RotaryEncoderInterruptImpl1.h"
#include "InputBroker.h" #include "InputBroker.h"
extern bool osk_found;
RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1; RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
@@ -19,12 +20,14 @@ bool RotaryEncoderInterruptImpl1::init()
input_broker_event eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw); input_broker_event eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw);
input_broker_event eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw); input_broker_event eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
input_broker_event eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press); input_broker_event eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
// moduleConfig.canned_message.ext_notification_module_output // moduleConfig.canned_message.ext_notification_module_output
RotaryEncoderInterruptBase::init(pinA, pinB, pinPress, eventCw, eventCcw, eventPressed, RotaryEncoderInterruptBase::init(pinA, pinB, pinPress, eventCw, eventCcw, eventPressed, eventPressedLong,
RotaryEncoderInterruptImpl1::handleIntA, RotaryEncoderInterruptImpl1::handleIntB, RotaryEncoderInterruptImpl1::handleIntA, RotaryEncoderInterruptImpl1::handleIntB,
RotaryEncoderInterruptImpl1::handleIntPressed); RotaryEncoderInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this); inputBroker->registerSource(this);
osk_found = true;
return true; return true;
} }
+230
View File
@@ -0,0 +1,230 @@
#if defined(T_LORA_PAGER)
#include "TLoraPagerKeyboard.h"
#include "main.h"
#ifndef LEDC_BACKLIGHT_CHANNEL
#define LEDC_BACKLIGHT_CHANNEL 4
#endif
#ifndef LEDC_BACKLIGHT_BIT_WIDTH
#define LEDC_BACKLIGHT_BIT_WIDTH 8
#endif
#ifndef LEDC_BACKLIGHT_FREQ
#define LEDC_BACKLIGHT_FREQ 1000 // Hz
#endif
#define _TCA8418_COLS 10
#define _TCA8418_ROWS 4
#define _TCA8418_NUM_KEYS 31
#define _TCA8418_MULTI_TAP_THRESHOLD 1500
using Key = TCA8418KeyboardBase::TCA8418Key;
constexpr uint8_t modifierRightShiftKey = 29 - 1; // keynum -1
constexpr uint8_t modifierRightShift = 0b0001;
constexpr uint8_t modifierSymKey = 21 - 1;
constexpr uint8_t modifierSym = 0b0010;
// Num chars per key, Modulus for rotating through characters
static uint8_t TLoraPagerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3};
static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'},
{'w', 'W', '2'},
{'e', 'E', '3'},
{'r', 'R', '4'},
{'t', 'T', '5'},
{'y', 'Y', '6'},
{'u', 'U', '7'},
{'i', 'I', '8'},
{'o', 'O', '9'},
{'p', 'P', '0'},
{'a', 'A', '*'},
{'s', 'S', '/'},
{'d', 'D', '+'},
{'f', 'F', '-'},
{'g', 'G', '='},
{'h', 'H', ':'},
{'j', 'J', '\''},
{'k', 'K', '"'},
{'l', 'L', '@'},
{Key::SELECT, 0x00, Key::TAB},
{0x00, 0x00, 0x00},
{'z', 'Z', '_'},
{'x', 'X', '$'},
{'c', 'C', ';'},
{'v', 'V', '?'},
{'b', 'B', '!'},
{'n', 'N', ','},
{'m', 'M', '.'},
{0x00, 0x00, 0x00},
{Key::BSP, 0x00, Key::ESC},
{' ', 0x00, Key::BL_TOGGLE}};
TLoraPagerKeyboard::TLoraPagerKeyboard()
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1),
last_tap(0L), char_idx(0), tap_interval(0)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
#else
ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
ledcAttachPin(KB_BL_PIN, LEDC_BACKLIGHT_CHANNEL);
#endif
reset();
}
void TLoraPagerKeyboard::reset(void)
{
TCA8418KeyboardBase::reset();
pinMode(KB_BL_PIN, OUTPUT);
digitalWrite(KB_BL_PIN, LOW);
setBacklight(false);
}
// handle multi-key presses (shift and alt)
void TLoraPagerKeyboard::trigger()
{
uint8_t count = keyCount();
if (count == 0)
return;
for (uint8_t i = 0; i < count; ++i) {
uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i);
uint8_t key = k & 0x7F;
if (k & 0x80) {
pressed(key);
} else {
released();
state = Idle;
}
}
}
void TLoraPagerKeyboard::setBacklight(bool on)
{
toggleBacklight(!on);
}
void TLoraPagerKeyboard::pressed(uint8_t key)
{
if (state == Init || state == Busy) {
return;
}
if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_ALL_ENABLED ||
config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY) {
hapticFeedback();
}
if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) {
modifierFlag = 0;
}
uint8_t next_key = 0;
int row = (key - 1) / 10;
int col = (key - 1) % 10;
if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) {
return; // Invalid key
}
next_key = row * _TCA8418_COLS + col;
state = Held;
uint32_t now = millis();
tap_interval = now - last_tap;
updateModifierFlag(next_key);
if (isModifierKey(next_key)) {
last_modifier_time = now;
}
if (tap_interval < 0) {
last_tap = 0;
state = Busy;
return;
}
if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) {
char_idx = 0;
} else {
char_idx += 1;
}
last_key = next_key;
last_tap = now;
}
void TLoraPagerKeyboard::released()
{
if (state != Held) {
return;
}
if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) {
last_key = -1;
state = Idle;
return;
}
uint32_t now = millis();
last_tap = now;
if (TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]] == Key::BL_TOGGLE) {
toggleBacklight();
return;
}
queueEvent(TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]]);
if (isModifierKey(last_key) == false)
modifierFlag = 0;
}
void TLoraPagerKeyboard::hapticFeedback()
{
drv.setWaveform(0, 14); // strong buzz 100%
drv.setWaveform(1, 0); // end waveform
drv.go();
}
// toggle brightness of the backlight in three steps
void TLoraPagerKeyboard::toggleBacklight(bool off)
{
static uint32_t brightness = 0;
if (off) {
brightness = 0;
} else {
if (brightness == 0) {
brightness = 40;
} else if (brightness == 40) {
brightness = 127;
} else if (brightness >= 127) {
brightness = 0;
}
}
LOG_DEBUG("Toggle backlight: %d", brightness);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
ledcWrite(KB_BL_PIN, brightness);
#else
ledcWrite(LEDC_BACKLIGHT_CHANNEL, brightness);
#endif
}
void TLoraPagerKeyboard::updateModifierFlag(uint8_t key)
{
if (key == modifierRightShiftKey) {
modifierFlag ^= modifierRightShift;
} else if (key == modifierSymKey) {
modifierFlag ^= modifierSym;
}
}
bool TLoraPagerKeyboard::isModifierKey(uint8_t key)
{
return (key == modifierRightShiftKey || key == modifierSymKey);
}
#endif
+20 -3
View File
@@ -4,9 +4,26 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase
{ {
public: public:
TLoraPagerKeyboard(); TLoraPagerKeyboard();
void setBacklight(bool on) override{}; void reset(void);
void trigger(void) override;
void setBacklight(bool on) override;
virtual ~TLoraPagerKeyboard() {}
protected: protected:
void pressed(uint8_t key) override{}; void pressed(uint8_t key) override;
void released(void) override{}; void released(void) override;
void hapticFeedback(void);
void updateModifierFlag(uint8_t key);
bool isModifierKey(uint8_t key);
void toggleBacklight(bool off = false);
private:
uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed
uint32_t last_modifier_time; // Timestamp of the last modifier key press
int8_t last_key;
int8_t next_key;
uint32_t last_tap;
uint8_t char_idx;
int32_t tap_interval;
}; };
+110 -25
View File
@@ -7,26 +7,43 @@ UpDownInterruptBase::UpDownInterruptBase(const char *name) : concurrency::OSThre
} }
void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown,
input_broker_event eventUp, input_broker_event eventPressed, void (*onIntDown)(), input_broker_event eventUp, input_broker_event eventPressed, input_broker_event eventPressedLong,
input_broker_event eventUpLong, input_broker_event eventDownLong, void (*onIntDown)(),
void (*onIntUp)(), void (*onIntPress)(), unsigned long updownDebounceMs) void (*onIntUp)(), void (*onIntPress)(), unsigned long updownDebounceMs)
{ {
this->_pinDown = pinDown; this->_pinDown = pinDown;
this->_pinUp = pinUp; this->_pinUp = pinUp;
this->_pinPress = pinPress;
this->_eventDown = eventDown; this->_eventDown = eventDown;
this->_eventUp = eventUp; this->_eventUp = eventUp;
this->_eventPressed = eventPressed; this->_eventPressed = eventPressed;
this->_eventPressedLong = eventPressedLong;
this->_eventUpLong = eventUpLong;
this->_eventDownLong = eventDownLong;
pinMode(pinPress, INPUT_PULLUP); // Store debounce configuration passed by caller
pinMode(this->_pinDown, INPUT_PULLUP); this->updownDebounceMs = updownDebounceMs;
pinMode(this->_pinUp, INPUT_PULLUP); bool isRAK = false;
#ifdef RAK_4631
isRAK = true;
#endif
attachInterrupt(pinPress, onIntPress, RISING); if (!isRAK || pinPress != 0) {
attachInterrupt(this->_pinDown, onIntDown, RISING); pinMode(pinPress, INPUT_PULLUP);
attachInterrupt(this->_pinUp, onIntUp, RISING); attachInterrupt(pinPress, onIntPress, FALLING);
}
if (!isRAK || this->_pinDown != 0) {
pinMode(this->_pinDown, INPUT_PULLUP);
attachInterrupt(this->_pinDown, onIntDown, FALLING);
}
if (!isRAK || this->_pinUp != 0) {
pinMode(this->_pinUp, INPUT_PULLUP);
attachInterrupt(this->_pinUp, onIntUp, FALLING);
}
LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress); LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress);
this->setInterval(100); this->setInterval(20);
} }
int32_t UpDownInterruptBase::runOnce() int32_t UpDownInterruptBase::runOnce()
@@ -34,23 +51,88 @@ int32_t UpDownInterruptBase::runOnce()
InputEvent e; InputEvent e;
e.inputEvent = INPUT_BROKER_NONE; e.inputEvent = INPUT_BROKER_NONE;
unsigned long now = millis(); unsigned long now = millis();
if (this->action == UPDOWN_ACTION_PRESSED) {
if (now - lastPressKeyTime >= pressDebounceMs) { // Read all button states once at the beginning
lastPressKeyTime = now; bool pressButtonPressed = !digitalRead(_pinPress);
LOG_DEBUG("GPIO event Press"); bool upButtonPressed = !digitalRead(_pinUp);
e.inputEvent = this->_eventPressed; bool downButtonPressed = !digitalRead(_pinDown);
// Handle initial button press detection - only if not already detected
if (this->action == UPDOWN_ACTION_PRESSED && pressButtonPressed && !pressDetected) {
pressDetected = true;
pressStartTime = now;
} else if (this->action == UPDOWN_ACTION_UP && upButtonPressed && !upDetected) {
upDetected = true;
upStartTime = now;
} else if (this->action == UPDOWN_ACTION_DOWN && downButtonPressed && !downDetected) {
downDetected = true;
downStartTime = now;
}
// Handle long press detection for press button
if (pressDetected && pressStartTime > 0) {
uint32_t pressDuration = now - pressStartTime;
if (!pressButtonPressed) {
// Button released
if (pressDuration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) {
lastPressKeyTime = now;
e.inputEvent = this->_eventPressed;
}
// Reset state
pressDetected = false;
pressStartTime = 0;
lastPressLongEventTime = 0;
} else if (pressDuration >= LONG_PRESS_DURATION && lastPressLongEventTime == 0) {
// First long press event only - avoid repeated events causing lag
e.inputEvent = this->_eventPressedLong;
lastPressLongEventTime = now;
} }
} else if (this->action == UPDOWN_ACTION_UP) { }
if (now - lastUpKeyTime >= updownDebounceMs) {
lastUpKeyTime = now; // Handle long press detection for up button
LOG_DEBUG("GPIO event Up"); if (upDetected && upStartTime > 0) {
e.inputEvent = this->_eventUp; uint32_t upDuration = now - upStartTime;
if (!upButtonPressed) {
// Button released
if (upDuration < LONG_PRESS_DURATION && now - lastUpKeyTime >= updownDebounceMs) {
lastUpKeyTime = now;
e.inputEvent = this->_eventUp;
}
// Reset state
upDetected = false;
upStartTime = 0;
lastUpLongEventTime = 0;
} else if (upDuration >= LONG_PRESS_DURATION) {
// Auto-repeat long press events
if (lastUpLongEventTime == 0 || (now - lastUpLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
e.inputEvent = this->_eventUpLong;
lastUpLongEventTime = now;
}
} }
} else if (this->action == UPDOWN_ACTION_DOWN) { }
if (now - lastDownKeyTime >= updownDebounceMs) {
lastDownKeyTime = now; // Handle long press detection for down button
LOG_DEBUG("GPIO event Down"); if (downDetected && downStartTime > 0) {
e.inputEvent = this->_eventDown; uint32_t downDuration = now - downStartTime;
if (!downButtonPressed) {
// Button released
if (downDuration < LONG_PRESS_DURATION && now - lastDownKeyTime >= updownDebounceMs) {
lastDownKeyTime = now;
e.inputEvent = this->_eventDown;
}
// Reset state
downDetected = false;
downStartTime = 0;
lastDownLongEventTime = 0;
} else if (downDuration >= LONG_PRESS_DURATION) {
// Auto-repeat long press events
if (lastDownLongEventTime == 0 || (now - lastDownLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
e.inputEvent = this->_eventDownLong;
lastDownLongEventTime = now;
}
} }
} }
@@ -60,8 +142,11 @@ int32_t UpDownInterruptBase::runOnce()
this->notifyObservers(&e); this->notifyObservers(&e);
} }
this->action = UPDOWN_ACTION_NONE; if (!pressDetected && !upDetected && !downDetected) {
return 100; this->action = UPDOWN_ACTION_NONE;
}
return 20; // This will control how the input frequency
} }
void UpDownInterruptBase::intPressHandler() void UpDownInterruptBase::intPressHandler()
+28 -2
View File
@@ -8,7 +8,8 @@ class UpDownInterruptBase : public Observable<const InputEvent *>, public concur
public: public:
explicit UpDownInterruptBase(const char *name); explicit UpDownInterruptBase(const char *name);
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp, void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp,
input_broker_event eventPressed, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(), input_broker_event eventPressed, input_broker_event eventPressedLong, input_broker_event eventUpLong,
input_broker_event eventDownLong, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(),
unsigned long updownDebounceMs = 50); unsigned long updownDebounceMs = 50);
void intPressHandler(); void intPressHandler();
void intDownHandler(); void intDownHandler();
@@ -17,16 +18,41 @@ class UpDownInterruptBase : public Observable<const InputEvent *>, public concur
int32_t runOnce() override; int32_t runOnce() override;
protected: protected:
enum UpDownInterruptBaseActionType { UPDOWN_ACTION_NONE, UPDOWN_ACTION_PRESSED, UPDOWN_ACTION_UP, UPDOWN_ACTION_DOWN }; enum UpDownInterruptBaseActionType {
UPDOWN_ACTION_NONE,
UPDOWN_ACTION_PRESSED,
UPDOWN_ACTION_PRESSED_LONG,
UPDOWN_ACTION_UP,
UPDOWN_ACTION_UP_LONG,
UPDOWN_ACTION_DOWN,
UPDOWN_ACTION_DOWN_LONG
};
volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE; volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE;
// Long press detection variables
uint32_t pressStartTime = 0;
uint32_t upStartTime = 0;
uint32_t downStartTime = 0;
bool pressDetected = false;
bool upDetected = false;
bool downDetected = false;
uint32_t lastPressLongEventTime = 0;
uint32_t lastUpLongEventTime = 0;
uint32_t lastDownLongEventTime = 0;
static const uint32_t LONG_PRESS_DURATION = 300;
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300;
private: private:
uint8_t _pinDown = 0; uint8_t _pinDown = 0;
uint8_t _pinUp = 0; uint8_t _pinUp = 0;
uint8_t _pinPress = 0;
input_broker_event _eventDown = INPUT_BROKER_NONE; input_broker_event _eventDown = INPUT_BROKER_NONE;
input_broker_event _eventUp = INPUT_BROKER_NONE; input_broker_event _eventUp = INPUT_BROKER_NONE;
input_broker_event _eventPressed = INPUT_BROKER_NONE; input_broker_event _eventPressed = INPUT_BROKER_NONE;
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
input_broker_event _eventUpLong = INPUT_BROKER_NONE;
input_broker_event _eventDownLong = INPUT_BROKER_NONE;
const char *_originName; const char *_originName;
unsigned long lastUpKeyTime = 0; unsigned long lastUpKeyTime = 0;
+10 -4
View File
@@ -1,5 +1,6 @@
#include "UpDownInterruptImpl1.h" #include "UpDownInterruptImpl1.h"
#include "InputBroker.h" #include "InputBroker.h"
extern bool osk_found;
UpDownInterruptImpl1 *upDownInterruptImpl1; UpDownInterruptImpl1 *upDownInterruptImpl1;
@@ -17,13 +18,18 @@ bool UpDownInterruptImpl1::init()
uint8_t pinDown = moduleConfig.canned_message.inputbroker_pin_b; uint8_t pinDown = moduleConfig.canned_message.inputbroker_pin_b;
uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press; uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press;
input_broker_event eventDown = INPUT_BROKER_DOWN; input_broker_event eventDown = INPUT_BROKER_USER_PRESS; // acts like RIGHT/DOWN
input_broker_event eventUp = INPUT_BROKER_UP; input_broker_event eventUp = INPUT_BROKER_ALT_PRESS; // acts like LEFT/UP
input_broker_event eventPressed = INPUT_BROKER_SELECT; input_broker_event eventPressed = INPUT_BROKER_SELECT;
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
input_broker_event eventUpLong = INPUT_BROKER_UP_LONG;
input_broker_event eventDownLong = INPUT_BROKER_DOWN_LONG;
UpDownInterruptBase::init(pinDown, pinUp, pinPress, eventDown, eventUp, eventPressed, UpDownInterruptImpl1::handleIntDown, UpDownInterruptBase::init(pinDown, pinUp, pinPress, eventDown, eventUp, eventPressed, eventPressedLong, eventUpLong,
UpDownInterruptImpl1::handleIntUp, UpDownInterruptImpl1::handleIntPressed); eventDownLong, UpDownInterruptImpl1::handleIntDown, UpDownInterruptImpl1::handleIntUp,
UpDownInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this); inputBroker->registerSource(this);
osk_found = true;
return true; return true;
} }
+2 -2
View File
@@ -12,8 +12,8 @@ void CardKbI2cImpl::init()
#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN) #if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN)
if (cardkb_found.address == 0x00) { if (cardkb_found.address == 0x00) {
LOG_DEBUG("Rescan for I2C keyboard"); LOG_DEBUG("Rescan for I2C keyboard");
uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, XPOWERS_AXP192_AXP2101_ADDRESS}; uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, TCA8418_KB_ADDR};
uint8_t i2caddr_asize = 5; uint8_t i2caddr_asize = sizeof(i2caddr_scan) / sizeof(i2caddr_scan[0]);
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire()); auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
#if WIRE_INTERFACES_COUNT == 2 #if WIRE_INTERFACES_COUNT == 2
+43 -8
View File
@@ -135,8 +135,9 @@ AccelerometerThread *accelerometerThread = nullptr;
AudioThread *audioThread = nullptr; AudioThread *audioThread = nullptr;
#endif #endif
#ifdef USE_PCA9557 #ifdef USE_XL9555
PCA9557 IOEXP; #include "ExtensionIOXL9555.hpp"
ExtensionIOXL9555 io;
#endif #endif
#if HAS_TFT #if HAS_TFT
@@ -203,7 +204,7 @@ ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE,
/// The I2C address of our Air Quality Indicator (if found) /// The I2C address of our Air Quality Indicator (if found)
ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE; ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE;
#ifdef T_WATCH_S3 #if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
Adafruit_DRV2605 drv; Adafruit_DRV2605 drv;
#endif #endif
@@ -361,6 +362,30 @@ void setup()
digitalWrite(SDCARD_CS, HIGH); digitalWrite(SDCARD_CS, HIGH);
pinMode(PIN_EINK_CS, OUTPUT); pinMode(PIN_EINK_CS, OUTPUT);
digitalWrite(PIN_EINK_CS, HIGH); digitalWrite(PIN_EINK_CS, HIGH);
#elif defined(T_LORA_PAGER)
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
pinMode(SDCARD_CS, OUTPUT);
digitalWrite(SDCARD_CS, HIGH);
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH);
// io expander
io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL);
io.pinMode(EXPANDS_DRV_EN, OUTPUT);
io.digitalWrite(EXPANDS_DRV_EN, HIGH);
io.pinMode(EXPANDS_AMP_EN, OUTPUT);
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
io.pinMode(EXPANDS_LORA_EN, OUTPUT);
io.digitalWrite(EXPANDS_LORA_EN, HIGH);
io.pinMode(EXPANDS_GPS_EN, OUTPUT);
io.digitalWrite(EXPANDS_GPS_EN, HIGH);
io.pinMode(EXPANDS_KB_EN, OUTPUT);
io.digitalWrite(EXPANDS_KB_EN, HIGH);
io.pinMode(EXPANDS_SD_EN, OUTPUT);
io.digitalWrite(EXPANDS_SD_EN, HIGH);
io.pinMode(EXPANDS_GPIO_EN, OUTPUT);
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
io.pinMode(EXPANDS_SD_PULLEN, INPUT);
#endif #endif
concurrency::hasBeenSetup = true; concurrency::hasBeenSetup = true;
@@ -396,7 +421,7 @@ void setup()
struct timeval tv; struct timeval tv;
tv.tv_sec = time(NULL); tv.tv_sec = time(NULL);
tv.tv_usec = 0; tv.tv_usec = 0;
perhapsSetRTC(RTCQualityNTP, &tv); perhapsSetRTC(RTCQualityDevice, &tv);
#endif #endif
powerMonInit(); powerMonInit();
@@ -406,6 +431,16 @@ void setup()
initDeepSleep(); initDeepSleep();
#if defined(MODEM_POWER_EN)
pinMode(MODEM_POWER_EN, OUTPUT);
digitalWrite(MODEM_POWER_EN, LOW);
#endif
#if defined(MODEM_PWRKEY)
pinMode(MODEM_PWRKEY, OUTPUT);
digitalWrite(MODEM_PWRKEY, LOW);
#endif
#if defined(LORA_TCXO_GPIO) #if defined(LORA_TCXO_GPIO)
pinMode(LORA_TCXO_GPIO, OUTPUT); pinMode(LORA_TCXO_GPIO, OUTPUT);
digitalWrite(LORA_TCXO_GPIO, HIGH); digitalWrite(LORA_TCXO_GPIO, HIGH);
@@ -797,7 +832,7 @@ void setup()
#endif #endif
#endif #endif
#ifdef T_WATCH_S3 #if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
drv.begin(); drv.begin();
drv.selectLibrary(1); drv.selectLibrary(1);
// I2C trigger by sending 'go' command // I2C trigger by sending 'go' command
@@ -843,7 +878,7 @@ void setup()
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry); screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) && if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
@@ -1106,7 +1141,7 @@ void setup()
// Don't call screen setup until after nodedb is setup (because we need // Don't call screen setup until after nodedb is setup (because we need
// the current region name) // the current region name)
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ #if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
if (screen) if (screen)
screen->setup(); screen->setup();
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)
@@ -1496,7 +1531,7 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
#if ((!HAS_SCREEN || NO_EXT_GPIO) || MESHTASTIC_EXCLUDE_CANNEDMESSAGES) && !defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS) #if ((!HAS_SCREEN || NO_EXT_GPIO) || MESHTASTIC_EXCLUDE_CANNEDMESSAGES) && !defined(MESHTASTIC_INCLUDE_NICHE_GRAPHICS)
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_CANNEDMSG_CONFIG; deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_CANNEDMSG_CONFIG;
#endif #endif
#if NO_EXT_GPIO #if NO_EXT_GPIO || MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_EXTNOTIF_CONFIG; deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_EXTNOTIF_CONFIG;
#endif #endif
// Only edge case here is if we apply this a device with built in Accelerometer and want to detect interrupts // Only edge case here is if we apply this a device with built in Accelerometer and want to detect interrupts
+1 -1
View File
@@ -42,7 +42,7 @@ extern bool eink_found;
extern bool pmu_found; extern bool pmu_found;
extern bool isUSBPowered; extern bool isUSBPowered;
#ifdef T_WATCH_S3 #if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
#include <Adafruit_DRV2605.h> #include <Adafruit_DRV2605.h>
extern Adafruit_DRV2605 drv; extern Adafruit_DRV2605 drv;
#endif #endif
+3 -2
View File
@@ -368,7 +368,7 @@ const char *Channels::getName(size_t chIndex)
// Per mesh.proto spec, if bandwidth is specified we must ignore modemPreset enum, we assume that in that case // Per mesh.proto spec, if bandwidth is specified we must ignore modemPreset enum, we assume that in that case
// the app effed up and forgot to set channelSettings.name // the app effed up and forgot to set channelSettings.name
if (config.lora.use_preset) { if (config.lora.use_preset) {
channelName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); channelName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
} else { } else {
channelName = "Custom"; channelName = "Custom";
} }
@@ -382,7 +382,8 @@ bool Channels::isDefaultChannel(ChannelIndex chIndex)
const auto &ch = getByIndex(chIndex); const auto &ch = getByIndex(chIndex);
if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) { if (ch.settings.psk.size == 1 && ch.settings.psk.bytes[0] == 1) {
const char *name = getName(chIndex); const char *name = getName(chIndex);
const char *presetName = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false); const char *presetName =
DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
// Check if the name is the default derived from the modem preset // Check if the name is the default derived from the modem preset
if (strcmp(name, presetName) == 0) if (strcmp(name, presetName) == 0)
return true; return true;
+2 -9
View File
@@ -85,11 +85,8 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e
return r; return r;
} }
void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src, const char *specificModule) void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
{ {
if (specificModule) {
LOG_DEBUG("Calling specific module: %s", specificModule);
}
// LOG_DEBUG("In call modules"); // LOG_DEBUG("In call modules");
bool moduleFound = false; bool moduleFound = false;
@@ -103,15 +100,11 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src, const char
// Was this message directed to us specifically? Will be false if we are sniffing someone elses packets // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets
auto ourNodeNum = nodeDB->getNodeNum(); auto ourNodeNum = nodeDB->getNodeNum();
bool toUs = isBroadcast(mp.to) || isToUs(&mp); bool toUs = isBroadcast(mp.to) || isToUs(&mp);
bool fromUs = mp.from == ourNodeNum;
for (auto i = modules->begin(); i != modules->end(); ++i) { for (auto i = modules->begin(); i != modules->end(); ++i) {
auto &pi = **i; auto &pi = **i;
// If specificModule is provided, only call that specific module
if (specificModule && (!pi.name || strcmp(pi.name, specificModule) != 0)) {
continue;
}
pi.currentRequest = &mp; pi.currentRequest = &mp;
/// We only call modules that are interested in the packet (and the message is destined to us or we are promiscious) /// We only call modules that are interested in the packet (and the message is destined to us or we are promiscious)
+1 -1
View File
@@ -73,7 +73,7 @@ class MeshModule
/** For use only by MeshService /** For use only by MeshService
*/ */
static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO, const char *specificModule = nullptr); static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO);
static std::vector<MeshModule *> GetMeshModulesWithUIFrames(int startIndex); static std::vector<MeshModule *> GetMeshModulesWithUIFrames(int startIndex);
static void observeUIEvents(Observer<const UIFrameEvent *> *observer); static void observeUIEvents(Observer<const UIFrameEvent *> *observer);
+14 -5
View File
@@ -663,7 +663,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.bluetooth.fixed_pin = defaultBLEPin; config.bluetooth.fixed_pin = defaultBLEPin;
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ #if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
bool hasScreen = true; bool hasScreen = true;
#ifdef HELTEC_MESH_NODE_T114 #ifdef HELTEC_MESH_NODE_T114
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET); uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);
@@ -830,6 +830,15 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.alert_message = true; moduleConfig.external_notification.alert_message = true;
moduleConfig.external_notification.output_ms = 1000; moduleConfig.external_notification.output_ms = 1000;
moduleConfig.external_notification.nag_timeout = 60; moduleConfig.external_notification.nag_timeout = 60;
#endif
#ifdef T_LORA_PAGER
moduleConfig.canned_message.updown1_enabled = true;
moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A;
moduleConfig.canned_message.inputbroker_pin_b = ROTARY_B;
moduleConfig.canned_message.inputbroker_pin_press = ROTARY_PRESS;
moduleConfig.canned_message.inputbroker_event_cw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(28);
moduleConfig.canned_message.inputbroker_event_ccw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(29);
moduleConfig.canned_message.inputbroker_event_press = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
#endif #endif
moduleConfig.has_canned_message = true; moduleConfig.has_canned_message = true;
#if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT #if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT
@@ -1702,10 +1711,10 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
{ {
// if (mp.from == getNodeNum()) { if (mp.from == getNodeNum()) {
// LOG_DEBUG("Ignore update from self"); LOG_DEBUG("Ignore update from self");
// return; return;
// } }
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) { if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time); LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);
+7 -3
View File
@@ -32,9 +32,12 @@ const RegionInfo regions[] = {
RDEF(US, 902.0f, 928.0f, 100, 0, 30, true, false, false), RDEF(US, 902.0f, 928.0f, 100, 0, 30, true, false, false),
/* /*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf EN300220 ETSI V3.2.1 [Table B.1, Item H, p. 21]
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.02.01_60/en_30022002v030201p.pdf
FIXME: https://github.com/meshtastic/firmware/issues/3371
*/ */
RDEF(EU_433, 433.0f, 434.0f, 10, 0, 12, true, false, false), RDEF(EU_433, 433.0f, 434.0f, 10, 0, 10, true, false, false),
/* /*
https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/ https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/
@@ -586,7 +589,8 @@ void RadioInterface::applyModemConfig()
// Check if we use the default frequency slot // Check if we use the default frequency slot
RadioInterface::uses_default_frequency_slot = RadioInterface::uses_default_frequency_slot =
channel_num == hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false)) % numChannels; channel_num ==
hash(DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset)) % numChannels;
// Old frequency selection formula // Old frequency selection formula
// float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num); // float freq = myRegion->freqStart + ((((myRegion->freqEnd - myRegion->freqStart) / numChannels) / 2) * channel_num);
+2 -1
View File
@@ -422,7 +422,8 @@ void RadioLibInterface::handleReceiveInterrupt()
} }
#endif #endif
if (state != RADIOLIB_ERR_NONE) { if (state != RADIOLIB_ERR_NONE) {
LOG_ERROR("Ignore received packet due to error=%d", state); LOG_ERROR("Ignore received packet due to error=%d (maybe to=0x%08x, from=0x%08x, flags=0x%02x)", state,
radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags);
rxBad++; rxBad++;
airTime->logAirtime(RX_ALL_LOG, xmitMsec); airTime->logAirtime(RX_ALL_LOG, xmitMsec);
+6 -5
View File
@@ -529,8 +529,9 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
#endif #endif
// Don't use PKC with Ham mode // Don't use PKC with Ham mode
!owner.is_licensed && !owner.is_licensed &&
// Don't use PKC if it's not explicitly requested and a non-primary channel is requested // Don't use PKC on 'serial' or 'gpio' channels unless explicitly requested
!(p->pki_encrypted != true && p->channel > 0) && !(p->pki_encrypted != true && (strcasecmp(channels.getName(chIndex), Channels::serialChannel) == 0 ||
strcasecmp(channels.getName(chIndex), Channels::gpioChannel) == 0)) &&
// Check for valid keys and single node destination // Check for valid keys and single node destination
config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr && config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr &&
// Check for a known public key for the destination // Check for a known public key for the destination
@@ -561,7 +562,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
// Now that we are encrypting the packet channel should be the hash (no longer the index) // Now that we are encrypting the packet channel should be the hash (no longer the index)
p->channel = hash; p->channel = hash;
if (hash < 0) { if (hash < 0) {
// No suitable channel could be found for sending // No suitable channel could be found for
return meshtastic_Routing_Error_NO_CHANNEL; return meshtastic_Routing_Error_NO_CHANNEL;
} }
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
@@ -577,7 +578,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
// Now that we are encrypting the packet channel should be the hash (no longer the index) // Now that we are encrypting the packet channel should be the hash (no longer the index)
p->channel = hash; p->channel = hash;
if (hash < 0) { if (hash < 0) {
// No suitable channel could be found for sending // No suitable channel could be found for
return meshtastic_Routing_Error_NO_CHANNEL; return meshtastic_Routing_Error_NO_CHANNEL;
} }
crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes);
@@ -670,7 +671,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
mqtt->onSend(*p_encrypted, *p, p->channel); mqtt->onSend(*p_encrypted, *p, p->channel);
#endif #endif
} else if (p->from == nodeDB->getNodeNum() && !skipHandle) { } else if (p->from == nodeDB->getNodeNum() && !skipHandle) {
MeshModule::callModules(*p, src, ROUTING_MODULE); MeshModule::callModules(*p, src);
} }
packetPool.release(p_encrypted); // Release the encrypted packet packetPool.release(p_encrypted); // Release the encrypted packet
+89 -15
View File
@@ -16,6 +16,95 @@ int32_t StreamAPI::runOncePart()
return result; return result;
} }
int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen)
{
auto result = readStream(buf, bufLen);
writeStream();
checkConnectionTimeout();
return result;
}
/**
* Read any rx chars from the link and call handleRecStream
*/
int32_t StreamAPI::readStream(char *buf, uint16_t bufLen)
{
if (bufLen < 1) {
// Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time
bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000);
return recentRx ? 5 : 250;
} else {
handleRecStream(buf, bufLen);
// we had bytes available this time, so assume we might have them next time also
lastRxMsec = millis();
return 0;
}
}
/**
* call getFromRadio() and deliver encapsulated packets to the Stream
*/
void StreamAPI::writeStream()
{
if (canWrite) {
uint32_t len;
do {
// Send every packet we can
len = getFromRadio(txBuf + HEADER_LEN);
emitTxBuffer(len);
} while (len);
}
}
int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen)
{
uint16_t index = 0;
while (bufLen > index) { // Currently we never want to block
int cInt = buf[index++];
if (cInt < 0)
break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit
// arduino
uint8_t c = (uint8_t)cInt;
// Use the read pointer for a little state machine, first look for framing, then length bytes, then payload
size_t ptr = rxPtr;
rxPtr++; // assume we will probably advance the rxPtr
rxBuf[ptr] = c; // store all bytes (including framing)
// console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c);
if (ptr == 0) { // looking for START1
if (c != START1)
rxPtr = 0; // failed to find framing
} else if (ptr == 1) { // looking for START2
if (c != START2)
rxPtr = 0; // failed to find framing
} else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing
uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing
// console->printf("len %d\n", len);
if (ptr == HEADER_LEN - 1) {
// we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid
// protobuf also)
if (len > MAX_TO_FROM_RADIO_SIZE)
rxPtr = 0; // length is bogus, restart search for framing
}
if (rxPtr != 0) // Is packet still considered 'good'?
if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload?
rxPtr = 0; // start over again on the next packet
// If we didn't just fail the packet and we now have the right # of bytes, parse it
handleToRadio(rxBuf + HEADER_LEN, len);
}
}
}
return 0;
}
/** /**
* Read any rx chars from the link and call handleToRadio * Read any rx chars from the link and call handleToRadio
*/ */
@@ -76,21 +165,6 @@ int32_t StreamAPI::readStream()
} }
} }
/**
* call getFromRadio() and deliver encapsulated packets to the Stream
*/
void StreamAPI::writeStream()
{
if (canWrite) {
uint32_t len;
do {
// Send every packet we can
len = getFromRadio(txBuf + HEADER_LEN);
emitTxBuffer(len);
} while (len);
}
}
/** /**
* Send the current txBuffer over our stream * Send the current txBuffer over our stream
*/ */
+3
View File
@@ -50,12 +50,15 @@ class StreamAPI : public PhoneAPI
* phone. * phone.
*/ */
virtual int32_t runOncePart(); virtual int32_t runOncePart();
virtual int32_t runOncePart(char *buf,uint16_t bufLen);
private: private:
/** /**
* Read any rx chars from the link and call handleToRadio * Read any rx chars from the link and call handleToRadio
*/ */
int32_t readStream(); int32_t readStream();
int32_t readStream(char *buf,uint16_t bufLen);
int32_t handleRecStream(char *buf,uint16_t bufLen);
/** /**
* call getFromRadio() and deliver encapsulated packets to the Stream * call getFromRadio() and deliver encapsulated packets to the Stream
+34 -1
View File
@@ -505,7 +505,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
if (mp.decoded.want_response && !myReply) { if (mp.decoded.want_response && !myReply) {
myReply = allocErrorResponse(meshtastic_Routing_Error_NONE, &mp); myReply = allocErrorResponse(meshtastic_Routing_Error_NONE, &mp);
} }
if (mp.pki_encrypted && myReply) {
myReply->pki_encrypted = true;
}
return handled; return handled;
} }
@@ -718,6 +720,13 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
requiresReboot = false; requiresReboot = false;
} }
#if defined(ARCH_PORTDUINO)
// If running on portduino and using SimRadio, do not require reboot
if (SimRadio::instance) {
requiresReboot = false;
}
#endif
#ifdef RF95_FAN_EN #ifdef RF95_FAN_EN
// Turn PA off if disabled by config // Turn PA off if disabled by config
if (c.payload_variant.lora.pa_fan_disabled) { if (c.payload_variant.lora.pa_fan_disabled) {
@@ -934,6 +943,9 @@ void AdminModule::handleGetOwner(const meshtastic_MeshPacket &req)
res.which_payload_variant = meshtastic_AdminMessage_get_owner_response_tag; res.which_payload_variant = meshtastic_AdminMessage_get_owner_response_tag;
setPassKey(&res); setPassKey(&res);
myReply = allocDataProtobuf(res); myReply = allocDataProtobuf(res);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
} }
} }
@@ -1005,6 +1017,9 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32
res.which_payload_variant = meshtastic_AdminMessage_get_config_response_tag; res.which_payload_variant = meshtastic_AdminMessage_get_config_response_tag;
setPassKey(&res); setPassKey(&res);
myReply = allocDataProtobuf(res); myReply = allocDataProtobuf(res);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
} }
} }
@@ -1092,6 +1107,9 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
res.which_payload_variant = meshtastic_AdminMessage_get_module_config_response_tag; res.which_payload_variant = meshtastic_AdminMessage_get_module_config_response_tag;
setPassKey(&res); setPassKey(&res);
myReply = allocDataProtobuf(res); myReply = allocDataProtobuf(res);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
} }
} }
@@ -1116,6 +1134,9 @@ void AdminModule::handleGetNodeRemoteHardwarePins(const meshtastic_MeshPacket &r
} }
setPassKey(&r); setPassKey(&r);
myReply = allocDataProtobuf(r); myReply = allocDataProtobuf(r);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
} }
void AdminModule::handleGetDeviceMetadata(const meshtastic_MeshPacket &req) void AdminModule::handleGetDeviceMetadata(const meshtastic_MeshPacket &req)
@@ -1125,6 +1146,9 @@ void AdminModule::handleGetDeviceMetadata(const meshtastic_MeshPacket &req)
r.which_payload_variant = meshtastic_AdminMessage_get_device_metadata_response_tag; r.which_payload_variant = meshtastic_AdminMessage_get_device_metadata_response_tag;
setPassKey(&r); setPassKey(&r);
myReply = allocDataProtobuf(r); myReply = allocDataProtobuf(r);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
} }
void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &req) void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &req)
@@ -1193,6 +1217,9 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
r.which_payload_variant = meshtastic_AdminMessage_get_device_connection_status_response_tag; r.which_payload_variant = meshtastic_AdminMessage_get_device_connection_status_response_tag;
setPassKey(&r); setPassKey(&r);
myReply = allocDataProtobuf(r); myReply = allocDataProtobuf(r);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
} }
void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t channelIndex) void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t channelIndex)
@@ -1204,6 +1231,9 @@ void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t ch
r.which_payload_variant = meshtastic_AdminMessage_get_channel_response_tag; r.which_payload_variant = meshtastic_AdminMessage_get_channel_response_tag;
setPassKey(&r); setPassKey(&r);
myReply = allocDataProtobuf(r); myReply = allocDataProtobuf(r);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
} }
} }
@@ -1213,6 +1243,9 @@ void AdminModule::handleGetDeviceUIConfig(const meshtastic_MeshPacket &req)
r.which_payload_variant = meshtastic_AdminMessage_get_ui_config_response_tag; r.which_payload_variant = meshtastic_AdminMessage_get_ui_config_response_tag;
r.get_ui_config_response = uiconfig; r.get_ui_config_response = uiconfig;
myReply = allocDataProtobuf(r); myReply = allocDataProtobuf(r);
if (req.pki_encrypted) {
myReply->pki_encrypted = true;
}
} }
void AdminModule::reboot(int32_t seconds) void AdminModule::reboot(int32_t seconds)
+1 -2
View File
@@ -692,10 +692,10 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo
// Normal canned message selection // Normal canned message selection
if (runState == CANNED_MESSAGE_RUN_STATE_INACTIVE || runState == CANNED_MESSAGE_RUN_STATE_DISABLED) { if (runState == CANNED_MESSAGE_RUN_STATE_INACTIVE || runState == CANNED_MESSAGE_RUN_STATE_DISABLED) {
} else { } else {
#if CANNED_MESSAGE_ADD_CONFIRMATION
// Show confirmation dialog before sending canned message // Show confirmation dialog before sending canned message
NodeNum destNode = dest; NodeNum destNode = dest;
ChannelIndex chan = channel; ChannelIndex chan = channel;
#if CANNED_MESSAGE_ADD_CONFIRMATION
graphics::menuHandler::showConfirmationBanner("Send message?", [this, destNode, chan, current]() { graphics::menuHandler::showConfirmationBanner("Send message?", [this, destNode, chan, current]() {
this->sendText(destNode, chan, current, false); this->sendText(destNode, chan, current, false);
payload = runState; payload = runState;
@@ -1119,7 +1119,6 @@ int32_t CannedMessageModule::runOnce()
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
} }
} }
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
this->currentMessageIndex = -1; this->currentMessageIndex = -1;
this->freetext = ""; this->freetext = "";
this->cursor = 0; this->cursor = 0;
+2 -1
View File
@@ -364,9 +364,10 @@ ExternalNotificationModule::ExternalNotificationModule()
// moduleConfig.external_notification.alert_message_buzzer = true; // moduleConfig.external_notification.alert_message_buzzer = true;
if (moduleConfig.external_notification.enabled) { if (moduleConfig.external_notification.enabled) {
#if !defined(MESHTASTIC_EXCLUDE_INPUTBROKER)
if (inputBroker) // put our callback in the inputObserver list if (inputBroker) // put our callback in the inputObserver list
inputObserver.observe(inputBroker); inputObserver.observe(inputBroker);
#endif
if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig), if (nodeDB->loadProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, sizeof(meshtastic_RTTTLConfig),
&meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) { &meshtastic_RTTTLConfig_msg, &rtttlConfig) != LoadFileResult::LOAD_SUCCESS) {
memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone)); memset(rtttlConfig.ringtone, 0, sizeof(rtttlConfig.ringtone));
+13 -6
View File
@@ -3,6 +3,7 @@
#include "buzz/BuzzerFeedbackThread.h" #include "buzz/BuzzerFeedbackThread.h"
#include "input/ExpressLRSFiveWay.h" #include "input/ExpressLRSFiveWay.h"
#include "input/InputBroker.h" #include "input/InputBroker.h"
#include "input/RotaryEncoderImpl.h"
#include "input/RotaryEncoderInterruptImpl1.h" #include "input/RotaryEncoderInterruptImpl1.h"
#include "input/SerialKeyboardImpl.h" #include "input/SerialKeyboardImpl.h"
#include "input/TrackballInterruptImpl1.h" #include "input/TrackballInterruptImpl1.h"
@@ -87,7 +88,7 @@
#include "modules/StoreForwardModule.h" #include "modules/StoreForwardModule.h"
#endif #endif
#endif #endif
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
#include "modules/ExternalNotificationModule.h" #include "modules/ExternalNotificationModule.h"
#endif #endif
@@ -97,7 +98,6 @@
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_SERIAL #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_SERIAL
#include "modules/SerialModule.h" #include "modules/SerialModule.h"
#endif #endif
#endif
#if !MESHTASTIC_EXCLUDE_DROPZONE #if !MESHTASTIC_EXCLUDE_DROPZONE
#include "modules/DropzoneModule.h" #include "modules/DropzoneModule.h"
@@ -170,11 +170,20 @@ void setupModules()
delete rotaryEncoderInterruptImpl1; delete rotaryEncoderInterruptImpl1;
rotaryEncoderInterruptImpl1 = nullptr; rotaryEncoderInterruptImpl1 = nullptr;
} }
#ifdef T_LORA_PAGER
// use a special FSM based rotary encoder version for T-LoRa Pager
rotaryEncoderImpl = new RotaryEncoderImpl();
if (!rotaryEncoderImpl->init()) {
delete rotaryEncoderImpl;
rotaryEncoderImpl = nullptr;
}
#else
upDownInterruptImpl1 = new UpDownInterruptImpl1(); upDownInterruptImpl1 = new UpDownInterruptImpl1();
if (!upDownInterruptImpl1->init()) { if (!upDownInterruptImpl1->init()) {
delete upDownInterruptImpl1; delete upDownInterruptImpl1;
upDownInterruptImpl1 = nullptr; upDownInterruptImpl1 = nullptr;
} }
#endif
cardKbI2cImpl = new CardKbI2cImpl(); cardKbI2cImpl = new CardKbI2cImpl();
cardKbI2cImpl->init(); cardKbI2cImpl->init();
#ifdef INPUTBROKER_MATRIX_TYPE #ifdef INPUTBROKER_MATRIX_TYPE
@@ -236,8 +245,8 @@ void setupModules()
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
new PowerTelemetryModule(); new PowerTelemetryModule();
#endif #endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \
!defined(CONFIG_IDF_TARGET_ESP32C3) !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#if !MESHTASTIC_EXCLUDE_SERIAL #if !MESHTASTIC_EXCLUDE_SERIAL
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
new SerialModule(); new SerialModule();
@@ -258,13 +267,11 @@ void setupModules()
storeForwardModule = new StoreForwardModule(); storeForwardModule = new StoreForwardModule();
#endif #endif
#endif #endif
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION #if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
externalNotificationModule = new ExternalNotificationModule(); externalNotificationModule = new ExternalNotificationModule();
#endif #endif
#if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_RANGETEST && !MESHTASTIC_EXCLUDE_GPS
new RangeTestModule(); new RangeTestModule();
#endif
#endif #endif
} else { } else {
#if !MESHTASTIC_EXCLUDE_ADMIN #if !MESHTASTIC_EXCLUDE_ADMIN
+9 -8
View File
@@ -105,14 +105,15 @@ void NeighborInfoModule::sendNeighborInfo(NodeNum dest, bool wantReplies)
{ {
meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero; meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero;
collectNeighborInfo(&neighborInfo); collectNeighborInfo(&neighborInfo);
meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo); // only send neighbours if we have some to send
// send regardless of whether or not we have neighbors in our DB, if (neighborInfo.neighbors_count > 0) {
// because we want to get neighbors for the next cycle meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo);
p->to = dest; p->to = dest;
p->decoded.want_response = wantReplies; p->decoded.want_response = wantReplies;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
printNeighborInfo("SENDING", &neighborInfo); printNeighborInfo("SENDING", &neighborInfo);
service->sendToMesh(p, RX_SRC_LOCAL, true); service->sendToMesh(p, RX_SRC_LOCAL, true);
}
} }
/* /*
+2 -2
View File
@@ -31,7 +31,7 @@ uint32_t packetSequence = 0;
int32_t RangeTestModule::runOnce() int32_t RangeTestModule::runOnce()
{ {
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) #if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_STM32WL) || defined(ARCH_PORTDUINO)
/* /*
Uncomment the preferences below if you want to use the module Uncomment the preferences below if you want to use the module
@@ -135,7 +135,7 @@ void RangeTestModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket &mp) ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket &mp)
{ {
#if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) #if defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_STM32WL) || defined(ARCH_PORTDUINO)
if (moduleConfig.range_test.enabled) { if (moduleConfig.range_test.enabled) {
+1 -1
View File
@@ -73,7 +73,7 @@ uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit
return Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); // Use the default hop limit return Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); // Use the default hop limit
} }
RoutingModule::RoutingModule() : ProtobufModule(ROUTING_MODULE, meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg) RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg)
{ {
isPromiscuous = true; isPromiscuous = true;
-2
View File
@@ -2,8 +2,6 @@
#include "Channels.h" #include "Channels.h"
#include "ProtobufModule.h" #include "ProtobufModule.h"
static const char *ROUTING_MODULE = "routing";
/** /**
* Routing module for router control messages * Routing module for router control messages
*/ */
+41 -11
View File
@@ -45,9 +45,12 @@
*/ */
#ifdef HELTEC_MESH_SOLAR
#include "meshSolarApp.h"
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \
!defined(CONFIG_IDF_TARGET_ESP32C3) !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
#define RX_BUFFER 256 #define RX_BUFFER 256
#define TIMEOUT 250 #define TIMEOUT 250
@@ -60,11 +63,11 @@
SerialModule *serialModule; SerialModule *serialModule;
SerialModuleRadio *serialModuleRadio; SerialModuleRadio *serialModuleRadio;
#if defined(TTGO_T_ECHO) || defined(T_ECHO_LITE) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ #if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \
defined(ELECROW_ThinkNode_M5) defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE)
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {} SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {}
static Print *serialPrint = &Serial; static Print *serialPrint = &Serial;
#elif defined(CONFIG_IDF_TARGET_ESP32C6) #elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172)
SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") {} SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") {}
static Print *serialPrint = &Serial1; static Print *serialPrint = &Serial1;
#else #else
@@ -78,7 +81,8 @@ size_t serialPayloadSize;
bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config) bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config)
{ {
if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA, if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA,
meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO)) { meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO,
meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) {
const char *warning = const char *warning =
"Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes."; "Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes.";
LOG_ERROR(warning); LOG_ERROR(warning);
@@ -169,7 +173,18 @@ int32_t SerialModule::runOnce()
Serial.begin(baud); Serial.begin(baud);
Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
} }
#elif defined(ARCH_STM32WL)
#ifndef RAK3172
HardwareSerial *serialInstance = &Serial2;
#else
HardwareSerial *serialInstance = &Serial1;
#endif
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
serialInstance->setTx(moduleConfig.serial.txd);
serialInstance->setRx(moduleConfig.serial.rxd);
}
serialInstance->begin(baud);
serialInstance->setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT);
#elif defined(ARCH_ESP32) #elif defined(ARCH_ESP32)
if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { if (moduleConfig.serial.rxd && moduleConfig.serial.txd) {
@@ -241,13 +256,28 @@ int32_t SerialModule::runOnce()
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) {
processWXSerial(); processWXSerial();
} else { }
#if defined(HELTEC_MESH_SOLAR)
else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) {
serialPayloadSize = Serial.readBytes(serialBytes, sizeof(serialBytes) - 1);
// If the parsing fails, the following parsing will be performed.
if ((serialPayloadSize > 0) && (meshSolarCmdHandle(serialBytes) != 0)) {
return runOncePart(serialBytes, serialPayloadSize);
}
}
#endif
else {
#if defined(CONFIG_IDF_TARGET_ESP32C6) #if defined(CONFIG_IDF_TARGET_ESP32C6)
while (Serial1.available()) { while (Serial1.available()) {
serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
#else #else
while (Serial2.available()) { #ifndef RAK3172
serialPayloadSize = Serial2.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); HardwareSerial *serialInstance = &Serial2;
#else
HardwareSerial *serialInstance = &Serial1;
#endif
while (serialInstance->available()) {
serialPayloadSize = serialInstance->readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
#endif #endif
serialModuleRadio->sendPayload(); serialModuleRadio->sendPayload();
} }
@@ -497,7 +527,7 @@ ParsedLine parseLine(const char *line)
void SerialModule::processWXSerial() void SerialModule::processWXSerial()
{ {
#if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \ #if !defined(TTGO_T_ECHO) && !defined(T_ECHO_LITE) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && \
!defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) && !defined(ARCH_STM32WL)
static unsigned int lastAveraged = 0; static unsigned int lastAveraged = 0;
static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded.
static double dir_sum_sin = 0; static double dir_sum_sin = 0;
+2 -2
View File
@@ -8,8 +8,8 @@
#include <Arduino.h> #include <Arduino.h>
#include <functional> #include <functional>
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)) && \
!defined(CONFIG_IDF_TARGET_ESP32C3) !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
class SerialModule : public StreamAPI, private concurrency::OSThread class SerialModule : public StreamAPI, private concurrency::OSThread
{ {
+15
View File
@@ -45,6 +45,9 @@
#ifndef HAS_CUSTOM_CRYPTO_ENGINE #ifndef HAS_CUSTOM_CRYPTO_ENGINE
#define HAS_CUSTOM_CRYPTO_ENGINE 1 #define HAS_CUSTOM_CRYPTO_ENGINE 1
#endif #endif
#ifndef HAS_32768HZ
#define HAS_32768HZ 0
#endif
#if defined(HAS_AXP192) || defined(HAS_AXP2101) #if defined(HAS_AXP192) || defined(HAS_AXP2101)
#define HAS_PMU #define HAS_PMU
@@ -192,6 +195,8 @@
#define HW_VENDOR meshtastic_HardwareModel_LINK_32 #define HW_VENDOR meshtastic_HardwareModel_LINK_32
#elif defined(T_DECK_PRO) #elif defined(T_DECK_PRO)
#define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO #define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO
#elif defined(T_LORA_PAGER)
#define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER
#endif #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -215,3 +220,13 @@
#endif #endif
#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc. #define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc.
// Setup flag, which indicates if our device supports power management
#ifdef CONFIG_PM_ENABLE
#define HAS_ESP32_PM_SUPPORT 1
#endif
// Setup flag, which indicates if our device supports dynamic light sleep
#if defined(HAS_ESP32_PM_SUPPORT) && defined(CONFIG_FREERTOS_USE_TICKLESS_IDLE)
#define HAS_ESP32_DYNAMIC_LIGHT_SLEEP 1
#endif
+5 -5
View File
@@ -64,7 +64,7 @@ void getMacAddr(uint8_t *dmac)
#endif #endif
} }
#ifdef HAS_32768HZ #if HAS_32768HZ
#define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk) #define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk)
static uint32_t calibrate_one(rtc_cal_sel_t cal_clk, const char *name) static uint32_t calibrate_one(rtc_cal_sel_t cal_clk, const char *name)
@@ -86,17 +86,17 @@ void enableSlowCLK()
uint32_t cal_32k = CALIBRATE_ONE(RTC_CAL_32K_XTAL); uint32_t cal_32k = CALIBRATE_ONE(RTC_CAL_32K_XTAL);
if (cal_32k == 0) { if (cal_32k == 0) {
LOG_DEBUG("32K XTAL OSC has not started up"); LOG_DEBUG("32k XTAL OSC has not started up");
} else { } else {
rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL); rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL);
LOG_DEBUG("Switch RTC Source to 32.768Khz succeeded, using 32K XTAL"); LOG_DEBUG("Switch RTC Source to 32.768kHz succeeded, using 32k XTAL");
CALIBRATE_ONE(RTC_CAL_RTC_MUX); CALIBRATE_ONE(RTC_CAL_RTC_MUX);
CALIBRATE_ONE(RTC_CAL_32K_XTAL); CALIBRATE_ONE(RTC_CAL_32K_XTAL);
} }
CALIBRATE_ONE(RTC_CAL_RTC_MUX); CALIBRATE_ONE(RTC_CAL_RTC_MUX);
CALIBRATE_ONE(RTC_CAL_32K_XTAL); CALIBRATE_ONE(RTC_CAL_32K_XTAL);
if (rtc_clk_slow_freq_get() != RTC_SLOW_FREQ_32K_XTAL) { if (rtc_clk_slow_freq_get() != RTC_SLOW_FREQ_32K_XTAL) {
LOG_WARN("Failed to switch 32K XTAL RTC source to 32.768Khz !!! "); LOG_WARN("Failed to switch 32K XTAL RTC source to 32.768kHz !!! ");
return; return;
} }
} }
@@ -182,7 +182,7 @@ void esp32Setup()
res = esp_task_wdt_add(NULL); res = esp_task_wdt_add(NULL);
assert(res == ESP_OK); assert(res == ESP_OK);
#ifdef HAS_32768HZ #if HAS_32768HZ
enableSlowCLK(); enableSlowCLK();
#endif #endif
} }
@@ -0,0 +1,27 @@
#include "configuration.h"
#ifdef T_LORA_PAGER
#include "AudioBoard.h"
DriverPins PinsAudioBoardES8311;
AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311);
// TLora Pager specific init
void lateInitVariant()
{
// AudioDriverLogger.begin(Serial, AudioDriverLogLevel::Debug);
// I2C: function, scl, sda
PinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire);
// I2S: function, mclk, bck, ws, data_out, data_in
PinsAudioBoardES8311.addI2S(PinFunction::CODEC, DAC_I2S_MCLK, DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT, DAC_I2S_DIN);
// configure codec
CodecConfig cfg;
cfg.input_device = ADC_INPUT_LINE1;
cfg.output_device = DAC_OUTPUT_ALL;
cfg.i2s.bits = BIT_LENGTH_16BITS;
cfg.i2s.rate = RATE_44K;
board.begin(cfg);
}
#endif
+2
View File
@@ -98,6 +98,8 @@
#define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK
#elif defined(SEEED_WIO_TRACKER_L1) #elif defined(SEEED_WIO_TRACKER_L1)
#define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1
#elif defined(HELTEC_MESH_SOLAR)
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR
#else #else
#define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN #define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN
#endif #endif
+1 -1
View File
@@ -323,7 +323,7 @@ void cpuDeepSleep(uint32_t msecToWake)
#endif #endif
#endif #endif
#ifdef HELTEC_MESH_NODE_T114 #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_MESH_SOLAR)
nrf_gpio_cfg_default(PIN_GPS_PPS); nrf_gpio_cfg_default(PIN_GPS_PPS);
detachInterrupt(PIN_GPS_PPS); detachInterrupt(PIN_GPS_PPS);
detachInterrupt(PIN_BUTTON1); detachInterrupt(PIN_BUTTON1);
+2
View File
@@ -128,6 +128,8 @@ class Power : private concurrency::OSThread
bool lipoInit(); bool lipoInit();
/// Setup a Lipo charger /// Setup a Lipo charger
bool lipoChargerInit(); bool lipoChargerInit();
/// Setup a meshSolar battery sensor
bool meshSolarInit();
private: private:
void shutdown(); void shutdown();
+36
View File
@@ -32,6 +32,16 @@ esp_sleep_source_t wakeCause; // the reason we booted this time
#endif #endif
#include "Throttle.h" #include "Throttle.h"
#ifdef USE_XL9555
#include "ExtensionIOXL9555.hpp"
extern ExtensionIOXL9555 io;
#endif
#ifdef HAS_PPM
#include <XPowersLib.h>
extern XPowersPPM *PPM;
#endif
#ifndef INCLUDE_vTaskSuspend #ifndef INCLUDE_vTaskSuspend
#define INCLUDE_vTaskSuspend 0 #define INCLUDE_vTaskSuspend 0
#endif #endif
@@ -297,6 +307,14 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
#endif #endif
#endif #endif
#ifdef HAS_PPM
if (PPM) {
LOG_INFO("PMM shutdown");
console->flush();
PPM->shutdown();
}
#endif
#ifdef HAS_PMU #ifdef HAS_PMU
if (pmu_found && PMU) { if (pmu_found && PMU) {
// Obsolete comment: from back when we we used to receive lora packets while CPU was in deep sleep. // Obsolete comment: from back when we we used to receive lora packets while CPU was in deep sleep.
@@ -412,6 +430,16 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
if (pmu_found) if (pmu_found)
gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_LOW_LEVEL); // pmu irq
#endif #endif
#ifdef T_LORA_PAGER
LOG_DEBUG("power down XL9555 io");
io.digitalWrite(EXPANDS_DRV_EN, LOW);
io.digitalWrite(EXPANDS_AMP_EN, LOW);
io.digitalWrite(EXPANDS_KB_EN, LOW);
io.digitalWrite(EXPANDS_SD_EN, LOW);
io.digitalWrite(EXPANDS_GPIO_EN, LOW);
#endif
auto res = esp_sleep_enable_gpio_wakeup(); auto res = esp_sleep_enable_gpio_wakeup();
if (res != ESP_OK) { if (res != ESP_OK) {
LOG_ERROR("esp_sleep_enable_gpio_wakeup result %d", res); LOG_ERROR("esp_sleep_enable_gpio_wakeup result %d", res);
@@ -452,6 +480,14 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
gpio_wakeup_disable((gpio_num_t)RF95_IRQ); gpio_wakeup_disable((gpio_num_t)RF95_IRQ);
} }
#endif #endif
#ifdef T_LORA_PAGER
LOG_DEBUG("power up XL9555 io");
io.digitalWrite(EXPANDS_DRV_EN, HIGH);
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
io.digitalWrite(EXPANDS_KB_EN, HIGH);
io.digitalWrite(EXPANDS_SD_EN, HIGH);
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
#endif
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here
@@ -1,20 +1,32 @@
#include "../test_helpers.h" #include "../test_helpers.h"
// Test encrypted packet serialization // test data initialization
void test_encrypted_packet_serialization() const int from = 0x11223344;
const int to = 0x55667788;
const int id = 0x9999;
// Helper function to create a test encrypted packet
meshtastic_MeshPacket create_test_encrypted_packet(uint32_t from, uint32_t to, uint32_t id, const char *data)
{ {
meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero; meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_zero;
packet.from = 0x11223344; packet.from = from;
packet.to = 0x55667788; packet.to = to;
packet.id = 0x9999; packet.id = id;
packet.which_payload_variant = meshtastic_MeshPacket_encrypted_tag; packet.which_payload_variant = meshtastic_MeshPacket_encrypted_tag;
// Add some dummy encrypted data if (data) {
const char *encrypted_data = "encrypted_payload_data"; packet.encrypted.size = strlen(data);
packet.encrypted.size = strlen(encrypted_data); memcpy(packet.encrypted.bytes, data, packet.encrypted.size);
memcpy(packet.encrypted.bytes, encrypted_data, packet.encrypted.size); }
std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet); return packet;
}
// Comprehensive helper function for all encrypted packet assertions
void assert_encrypted_packet(const std::string &json, uint32_t expected_from, uint32_t expected_to, uint32_t expected_id,
size_t expected_size)
{
// Parse and validate JSON
TEST_ASSERT_TRUE(json.length() > 0); TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str()); JSONValue *root = JSON::Parse(json.c_str());
@@ -23,28 +35,48 @@ void test_encrypted_packet_serialization()
JSONObject jsonObj = root->AsObject(); JSONObject jsonObj = root->AsObject();
// Check basic packet fields // Assert basic packet fields
TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end()); TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end());
TEST_ASSERT_EQUAL(0x11223344, (uint32_t)jsonObj["from"]->AsNumber()); TEST_ASSERT_EQUAL(expected_from, (uint32_t)jsonObj.at("from")->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end()); TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end());
TEST_ASSERT_EQUAL(0x55667788, (uint32_t)jsonObj["to"]->AsNumber()); TEST_ASSERT_EQUAL(expected_to, (uint32_t)jsonObj.at("to")->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end()); TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end());
TEST_ASSERT_EQUAL(0x9999, (uint32_t)jsonObj["id"]->AsNumber()); TEST_ASSERT_EQUAL(expected_id, (uint32_t)jsonObj.at("id")->AsNumber());
// Check that it has encrypted data fields (not "payload" but "bytes" and "size") // Assert encrypted data fields
TEST_ASSERT_TRUE(jsonObj.find("bytes") != jsonObj.end()); TEST_ASSERT_TRUE(jsonObj.find("bytes") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["bytes"]->IsString()); TEST_ASSERT_TRUE(jsonObj.at("bytes")->IsString());
TEST_ASSERT_TRUE(jsonObj.find("size") != jsonObj.end()); TEST_ASSERT_TRUE(jsonObj.find("size") != jsonObj.end());
TEST_ASSERT_EQUAL(22, (int)jsonObj["size"]->AsNumber()); // strlen("encrypted_payload_data") = 22 TEST_ASSERT_EQUAL(expected_size, (int)jsonObj.at("size")->AsNumber());
// The encrypted data should be hex-encoded // Assert hex encoding
std::string encrypted_hex = jsonObj["bytes"]->AsString(); std::string encrypted_hex = jsonObj["bytes"]->AsString();
TEST_ASSERT_TRUE(encrypted_hex.length() > 0); TEST_ASSERT_EQUAL(expected_size * 2, encrypted_hex.length());
// Should be twice the size of the original data (hex encoding)
TEST_ASSERT_EQUAL(44, encrypted_hex.length()); // 22 * 2 = 44
delete root; delete root;
} }
// Test encrypted packet serialization
void test_encrypted_packet_serialization()
{
const char *data = "encrypted_payload_data";
meshtastic_MeshPacket packet = create_test_encrypted_packet(from, to, id, data);
std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet);
assert_encrypted_packet(json, from, to, id, strlen(data));
}
// Test empty encrypted packet
void test_empty_encrypted_packet()
{
const char *data = "";
meshtastic_MeshPacket packet = create_test_encrypted_packet(from, to, id, data);
std::string json = MeshPacketSerializer::JsonSerializeEncrypted(&packet);
assert_encrypted_packet(json, from, to, id, strlen(data));
}
@@ -1,42 +1,105 @@
#include "../test_helpers.h" #include "../test_helpers.h"
#include <memory>
// Helper function to test common packet fields and structure
void verify_text_message_packet_structure(const std::string &json, const char *expected_text)
{
TEST_ASSERT_TRUE(json.length() > 0);
// Use smart pointer for automatic memory management
std::unique_ptr<JSONValue> root(JSON::Parse(json.c_str()));
TEST_ASSERT_NOT_NULL(root.get());
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check basic packet fields - use helper function to reduce duplication
auto check_field = [&](const char *field, uint32_t expected_value) {
auto it = jsonObj.find(field);
TEST_ASSERT_TRUE(it != jsonObj.end());
TEST_ASSERT_EQUAL(expected_value, (uint32_t)it->second->AsNumber());
};
check_field("from", 0x11223344);
check_field("to", 0x55667788);
check_field("id", 0x9999);
// Check message type
auto type_it = jsonObj.find("type");
TEST_ASSERT_TRUE(type_it != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("text", type_it->second->AsString().c_str());
// Check payload
auto payload_it = jsonObj.find("payload");
TEST_ASSERT_TRUE(payload_it != jsonObj.end());
TEST_ASSERT_TRUE(payload_it->second->IsObject());
JSONObject payload = payload_it->second->AsObject();
auto text_it = payload.find("text");
TEST_ASSERT_TRUE(text_it != payload.end());
TEST_ASSERT_EQUAL_STRING(expected_text, text_it->second->AsString().c_str());
// No need for manual delete with smart pointer
}
// Test TEXT_MESSAGE_APP port // Test TEXT_MESSAGE_APP port
void test_text_message_serialization() void test_text_message_serialization()
{ {
const char *test_text = "Hello Meshtastic!"; const char *test_text = "Hello Meshtastic!";
meshtastic_MeshPacket packet = meshtastic_MeshPacket packet =
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, (const uint8_t *)test_text, strlen(test_text)); create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast<const uint8_t *>(test_text), strlen(test_text));
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false); std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0); verify_text_message_packet_structure(json, test_text);
}
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root); // Test with nullptr to check robustness
TEST_ASSERT_TRUE(root->IsObject()); void test_text_message_serialization_null()
{
JSONObject jsonObj = root->AsObject(); meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, nullptr, 0);
// Check basic packet fields std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end()); verify_text_message_packet_structure(json, "");
TEST_ASSERT_EQUAL(0x11223344, (uint32_t)jsonObj["from"]->AsNumber()); }
TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end()); // Test TEXT_MESSAGE_APP port with very long message (boundary testing)
TEST_ASSERT_EQUAL(0x55667788, (uint32_t)jsonObj["to"]->AsNumber()); void test_text_message_serialization_long_text()
{
TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end()); // Test with actual message size limits
TEST_ASSERT_EQUAL(0x9999, (uint32_t)jsonObj["id"]->AsNumber()); constexpr size_t MAX_MESSAGE_SIZE = 200; // Typical LoRa payload limit
std::string long_text(MAX_MESSAGE_SIZE, 'A');
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end()); meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP,
TEST_ASSERT_EQUAL_STRING("text", jsonObj["type"]->AsString().c_str()); reinterpret_cast<const uint8_t *>(long_text.c_str()), long_text.length());
// Check payload std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end()); verify_text_message_packet_structure(json, long_text.c_str());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject()); }
JSONObject payload = jsonObj["payload"]->AsObject(); // Test with message over size limit (should fail)
TEST_ASSERT_TRUE(payload.find("text") != payload.end()); void test_text_message_serialization_oversized()
TEST_ASSERT_EQUAL_STRING("Hello Meshtastic!", payload["text"]->AsString().c_str()); {
constexpr size_t OVERSIZED_MESSAGE = 250; // Over the limit
delete root; std::string oversized_text(OVERSIZED_MESSAGE, 'B');
meshtastic_MeshPacket packet = create_test_packet(
meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast<const uint8_t *>(oversized_text.c_str()), oversized_text.length());
// Should fail or return empty/error
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
// Should only verify first 234 characters for oversized messages
std::string expected_text = oversized_text.substr(0, 234);
verify_text_message_packet_structure(json, expected_text.c_str());
}
// Add test for malformed UTF-8 sequences
void test_text_message_serialization_invalid_utf8()
{
const uint8_t invalid_utf8[] = {0xFF, 0xFE, 0xFD, 0x00}; // Invalid UTF-8
meshtastic_MeshPacket packet =
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, invalid_utf8, sizeof(invalid_utf8) - 1);
// Should not crash, may produce replacement characters
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
} }
@@ -4,6 +4,10 @@
// Forward declarations for test functions // Forward declarations for test functions
void test_text_message_serialization(); void test_text_message_serialization();
void test_text_message_serialization_null();
void test_text_message_serialization_long_text();
void test_text_message_serialization_oversized();
void test_text_message_serialization_invalid_utf8();
void test_position_serialization(); void test_position_serialization();
void test_nodeinfo_serialization(); void test_nodeinfo_serialization();
void test_waypoint_serialization(); void test_waypoint_serialization();
@@ -14,6 +18,7 @@ void test_telemetry_environment_metrics_missing_fields();
void test_telemetry_environment_metrics_complete_coverage(); void test_telemetry_environment_metrics_complete_coverage();
void test_telemetry_environment_metrics_unset_fields(); void test_telemetry_environment_metrics_unset_fields();
void test_encrypted_packet_serialization(); void test_encrypted_packet_serialization();
void test_empty_encrypted_packet();
void setup() void setup()
{ {
@@ -21,6 +26,10 @@ void setup()
// Text message tests // Text message tests
RUN_TEST(test_text_message_serialization); RUN_TEST(test_text_message_serialization);
RUN_TEST(test_text_message_serialization_null);
RUN_TEST(test_text_message_serialization_long_text);
RUN_TEST(test_text_message_serialization_oversized);
RUN_TEST(test_text_message_serialization_invalid_utf8);
// Position tests // Position tests
RUN_TEST(test_position_serialization); RUN_TEST(test_position_serialization);
@@ -41,6 +50,7 @@ void setup()
// Encrypted packet test // Encrypted packet test
RUN_TEST(test_encrypted_packet_serialization); RUN_TEST(test_encrypted_packet_serialization);
RUN_TEST(test_empty_encrypted_packet);
UNITY_END(); UNITY_END();
} }
@@ -19,8 +19,6 @@ build_flags = ${esp32s3_base.build_flags} -Os
-D MESHTASTIC_EXCLUDE_SERIAL=1 -D MESHTASTIC_EXCLUDE_SERIAL=1
-D MESHTASTIC_EXCLUDE_SOCKETAPI=1 -D MESHTASTIC_EXCLUDE_SOCKETAPI=1
-D MESHTASTIC_EXCLUDE_SCREEN=1 -D MESHTASTIC_EXCLUDE_SCREEN=1
-D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
-D HAS_TELEMETRY=0
-D CONFIG_DISABLE_HAL_LOCKS=1 -D CONFIG_DISABLE_HAL_LOCKS=1
-D USE_PIN_BUZZER -D USE_PIN_BUZZER
-D HAS_SCREEN=0 -D HAS_SCREEN=0
+2
View File
@@ -40,3 +40,5 @@
#define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8 #define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define HAS_32768HZ 1
+9
View File
@@ -92,3 +92,12 @@
#define SX126X_DIO3_TCXO_VOLTAGE 2.4 #define SX126X_DIO3_TCXO_VOLTAGE 2.4
// Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface // Internally the TTGO module hooks the SX1262-DIO2 in to control the TX/RX switch (which is the default for the sx1262interface
// code) // code)
#define MODEM_POWER_EN 41
#define MODEM_PWRKEY 40
#define MODEM_RST 9
#define MODEM_RI 7
#define MODEM_DTR 8
#define MODEM_RX 10
#define MODEM_TX 11
+1 -1
View File
@@ -62,6 +62,6 @@
// #define PCF8563_RTC 0x51 //Putting definitions in variant. h does not compile correctly // #define PCF8563_RTC 0x51 //Putting definitions in variant. h does not compile correctly
// has 32768 Hz crystal // has 32768 Hz crystal
#define HAS_32768HZ #define HAS_32768HZ 1
#define USE_SH1106 #define USE_SH1106
@@ -0,0 +1,19 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#define USB_VID 0x303a
#define USB_PID 0x1001
// used for keyboard, battery gauge, charger and haptic driver
static const uint8_t SDA = 3;
static const uint8_t SCL = 2;
// Default SPI will be mapped to Radio
static const uint8_t SS = 36;
static const uint8_t MOSI = 34;
static const uint8_t MISO = 33;
static const uint8_t SCK = 35;
#endif /* Pins_Arduino_h */
@@ -0,0 +1,71 @@
; LilyGo T-Lora-Pager
[env:tlora-pager]
extends = esp32s3_base
board = t-deck-pro ; same as T-Deck Pro
board_check = true
board_build.partitions = default_16MB.csv
upload_protocol = esptool
build_flags = ${esp32s3_base.build_flags}
-I variants/esp32s3/tlora-pager
-D T_LORA_PAGER
-D BOARD_HAS_PSRAM
-D GPS_POWER_TOGGLE
-D HAS_SDCARD
-D SDCARD_USE_SPI1
-D ENABLE_ROTARY_PULLUP
-D ENABLE_BUTTON_PULLUP
-D HALF_STEP
lib_deps = ${esp32s3_base.lib_deps}
lovyan03/LovyanGFX@1.2.7
earlephilhower/ESP8266Audio@1.9.9
earlephilhower/ESP8266SAM@1.0.1
adafruit/Adafruit DRV2605 Library@1.2.4
lewisxhe/PCF8563_Library@1.0.1
lewisxhe/SensorLib@0.3.1
https://github.com/pschatzmann/arduino-audio-driver/archive/refs/tags/v0.1.3.zip
https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip
https://github.com/mverch67/RotaryEncoder
[env:tlora-pager-tft]
board_level = extra
extends = env:tlora-pager
build_flags =
${env:tlora-pager.build_flags}
-D CONFIG_DISABLE_HAL_LOCKS=1
-D INPUTDRIVER_ROTARY_TYPE=1
-D INPUTDRIVER_ROTARY_UP=40
-D INPUTDRIVER_ROTARY_DOWN=41
-D INPUTDRIVER_ROTARY_BTN=7
-D INPUTDRIVER_BUTTON_TYPE=0
-D HAS_SCREEN=1
-D HAS_TFT=1
-D USE_I2S_BUZZER
-D RAM_SIZE=5120
-D LV_LVGL_H_INCLUDE_SIMPLE
-D LV_CONF_INCLUDE_SIMPLE
-D LV_COMP_CONF_INCLUDE_SIMPLE
-D LV_USE_SYSMON=0
-D LV_USE_PROFILER=0
-D LV_USE_PERF_MONITOR=0
-D LV_USE_MEM_MONITOR=0
-D LV_USE_LOG=0
-D USE_LOG_DEBUG
-D LOG_DEBUG_INC=\"DebugConfiguration.h\"
-D RADIOLIB_SPI_PARANOID=0
-D LGFX_SCREEN_WIDTH=222
-D LGFX_SCREEN_HEIGHT=480
-D DISPLAY_SIZE=480x222 ; landscape mode
-D DISPLAY_SET_RESOLUTION
-D LGFX_DRIVER=LGFX_TLORA_PAGER
-D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_T_LORA_PAGER.h\"
; -D LVGL_DRIVER=LVGL_T_LORA_PAGER
; -D LV_USE_ST7796=1
-D VIEW_480x222
-D USE_PACKET_API
-D MAP_FULL_REDRAW
lib_deps =
${env:tlora-pager.lib_deps}
${device-ui_base.lib_deps}
+125
View File
@@ -0,0 +1,125 @@
// ST7796 TFT LCD
#define TFT_CS 38
#define ST7796_CS TFT_CS
#define ST7796_RS 37 // DC
#define ST7796_SDA MOSI // MOSI
#define ST7796_SCK SCK
#define ST7796_RESET -1
#define ST7796_MISO MISO
#define ST7796_BUSY -1
#define ST7796_BL 42
#define ST7796_SPI_HOST SPI2_HOST
#define TFT_BL 42
#define SPI_FREQUENCY 75000000
#define SPI_READ_FREQUENCY 16000000
#define TFT_HEIGHT 480
#define TFT_WIDTH 222
#define TFT_OFFSET_X 49
#define TFT_OFFSET_Y 0
#define TFT_OFFSET_ROTATION 3
#define SCREEN_ROTATE
#define SCREEN_TRANSITION_FRAMERATE 5
#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness
#define I2C_SDA SDA
#define I2C_SCL SCL
#define USE_POWERSAVE
#define SLEEP_TIME 120
// GNNS
#define HAS_GPS 1
#define GPS_BAUDRATE 38400
#define GPS_RX_PIN 4
#define GPS_TX_PIN 12
#define PIN_GPS_PPS 13
// PCF8563 RTC Module
#if __has_include("pcf8563.h")
#include "pcf8563.h"
#endif
#define PCF8563_RTC 0x51
#define HAS_RTC 1
// Rotary
#define ROTARY_A (40)
#define ROTARY_B (41)
#define ROTARY_PRESS (7)
#define BUTTON_PIN 0
// SPI interface SD card slot
#define SPI_MOSI MOSI
#define SPI_SCK SCK
#define SPI_MISO MISO
#define SPI_CS 21
#define SDCARD_CS SPI_CS
#define SD_SPI_FREQUENCY 75000000U
// TCA8418 keyboard
#define I2C_NO_RESCAN
#define KB_BL_PIN 46
#define KB_INT 6
#define CANNED_MESSAGE_MODULE_ENABLE 1
// audio codec ES8311
#define HAS_I2S
#define DAC_I2S_BCK 11
#define DAC_I2S_WS 18
#define DAC_I2S_DOUT 45
#define DAC_I2S_DIN 17
#define DAC_I2S_MCLK 10
// gyroscope BHI260AP
#define HAS_BHI260AP
// battery charger BQ25896
#define HAS_PPM 1
#define XPOWERS_CHIP_BQ25896
// battery quality management BQ27220
#define HAS_BQ27220 1
#define BQ27220_I2C_SDA SDA
#define BQ27220_I2C_SCL SCL
#define BQ27220_DESIGN_CAPACITY 1500
// NFC ST25R3916
#define NFC_INT 5
#define NFC_CS 39
// External expansion chip XL9555
#define USE_XL9555
#define EXPANDS_DRV_EN (0)
#define EXPANDS_AMP_EN (1)
#define EXPANDS_KB_RST (2)
#define EXPANDS_LORA_EN (3)
#define EXPANDS_GPS_EN (4)
#define EXPANDS_NFC_EN (5)
#define EXPANDS_GPS_RST (7)
#define EXPANDS_KB_EN (8)
#define EXPANDS_GPIO_EN (9)
#define EXPANDS_SD_DET (10)
#define EXPANDS_SD_PULLEN (11)
#define EXPANDS_SD_EN (12)
// LoRa
#define USE_SX1262
#define USE_SX1268
#define LORA_SCK 35
#define LORA_MISO 33
#define LORA_MOSI 34
#define LORA_CS 36
#define LORA_DIO0 -1 // a No connect on the SX1262 module
#define LORA_RESET 47
#define LORA_DIO1 14 // SX1262 IRQ
#define LORA_DIO2 48 // SX1262 BUSY
#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled
#define SX126X_CS LORA_CS
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY LORA_DIO2
#define SX126X_RESET LORA_RESET
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 3.0
+1 -2
View File
@@ -32,6 +32,7 @@ lib_deps = ${esp32s3_base.lib_deps}
[env:unphone-tft] [env:unphone-tft]
board_level = extra
extends = env:unphone extends = env:unphone
build_flags = build_flags =
${env:unphone.build_flags} ${env:unphone.build_flags}
@@ -52,8 +53,6 @@ build_flags =
-D LV_USE_PERF_MONITOR=0 -D LV_USE_PERF_MONITOR=0
-D LV_USE_MEM_MONITOR=0 -D LV_USE_MEM_MONITOR=0
-D LV_USE_LOG=0 -D LV_USE_LOG=0
-D USE_LOG_DEBUG
-D LOG_DEBUG_INC=\"DebugConfiguration.h\"
-D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_WIDTH=320
-D LGFX_SCREEN_HEIGHT=480 -D LGFX_SCREEN_HEIGHT=480
-D DISPLAY_SIZE=320x480 ; portrait mode -D DISPLAY_SIZE=320x480 ; portrait mode
+4 -1
View File
@@ -3,7 +3,10 @@ extends = portduino_base
build_flags = ${portduino_base.build_flags} -I variants/native/portduino build_flags = ${portduino_base.build_flags} -I variants/native/portduino
-I /usr/include -I /usr/include
board = cross_platform board = cross_platform
lib_deps = ${portduino_base.lib_deps} lib_deps =
${portduino_base.lib_deps}
melopero/Melopero RV3028@^1.1.0
build_src_filter = ${portduino_base.build_src_filter} build_src_filter = ${portduino_base.build_src_filter}
[env:native] [env:native]
+3
View File
@@ -5,3 +5,6 @@
#define HAS_GPS 1 #define HAS_GPS 1
#define MAX_RX_TOPHONE settingsMap[maxtophone] #define MAX_RX_TOPHONE settingsMap[maxtophone]
#define MAX_NUM_NODES settingsMap[maxnodes] #define MAX_NUM_NODES settingsMap[maxnodes]
// RAK12002 RTC Module
#define RV3028_RTC (uint8_t)0b1010010
@@ -208,7 +208,7 @@ No longer populated on PCB
#undef AREF_VOLTAGE #undef AREF_VOLTAGE
#define AREF_VOLTAGE 3.0 #define AREF_VOLTAGE 3.0
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER (4.90F) #define ADC_MULTIPLIER (4.99F)
#define HAS_RTC 0 #define HAS_RTC 0
#ifdef __cplusplus #ifdef __cplusplus
@@ -0,0 +1,19 @@
; First prototype nrf52840/sx1262 device
[env:heltec-mesh-solar]
extends = nrf52840_base
board = heltec_mesh_solar
board_level = pr
debug_tool = jlink
# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.
build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/heltec_mesh_solar
-DGPS_POWER_TOGGLE
-DHELTEC_MESH_SOLAR
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_solar>
lib_deps =
${nrf52840_base.lib_deps}
https://github.com/NMIoT/meshsolar/archive/dfc5330dad443982e6cdd37a61d33fc7252f468b.zip
lewisxhe/PCF8563_Library@^1.0.1
ArduinoJson@6.21.4
@@ -0,0 +1,36 @@
/*
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(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT);
}
@@ -0,0 +1,157 @@
/*
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
Copyright (c) 2016 Sandeep Mistry All right reserved.
Copyright (c) 2018, Adafruit Industries (adafruit.com)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _VARIANT_HELTEC_NRF_
#define _VARIANT_HELTEC_NRF_
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
#define USE_LFXO // Board uses 32khz crystal for LF
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "WVariant.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Number of pins defined in PinDescription array
#define PINS_COUNT (48)
#define NUM_DIGITAL_PINS (48)
#define NUM_ANALOG_INPUTS (1)
#define NUM_ANALOG_OUTPUTS (0)
#define PIN_LED1 (0 + 12) // green (confirmed on 1.0 board)
#define LED_BLUE PIN_LED1 // fake for bluefruit library
#define LED_GREEN PIN_LED1
#define LED_BUILTIN LED_GREEN
#define LED_STATE_ON 0 // State when LED is lit
#define HAS_NEOPIXEL // Enable the use of neopixels
#define NEOPIXEL_COUNT 1 // How many neopixels are connected
#define NEOPIXEL_DATA (32+15) // gpio pin used to send data to the neopixels
#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use
/*
* Buttons
*/
#define PIN_BUTTON1 (32 + 10)
// #define PIN_BUTTON2 (0 + 18) // 0.18 is labeled on the board as RESET but we configure it in the bootloader as a regular
// GPIO
/*
No longer populated on PCB
*/
#define PIN_SERIAL2_RX (0 + 9)
#define PIN_SERIAL2_TX (0 + 10)
// #define PIN_SERIAL2_EN (0 + 17)
/*
* I2C
*/
#define WIRE_INTERFACES_COUNT 2
// I2C bus 0
// Routed to footprint for PCF8563TS RTC
// Not populated on T114 V1, maybe in future?
#define PIN_WIRE_SDA (0 + 6) // P0.26
#define PIN_WIRE_SCL (0 + 26) // P0.26
// I2C bus 1
// Available on header pins, for general use
#define PIN_WIRE1_SDA (0 + 30) // P0.30
#define PIN_WIRE1_SCL (0 + 5) // P0.13
/*
* Lora radio
*/
#define USE_SX1262
// #define USE_SX1268
#define SX126X_CS (0 + 24) // FIXME - we really should define LORA_CS instead
#define LORA_CS (0 + 24)
#define SX126X_DIO1 (0 + 20)
// Note DIO2 is attached internally to the module to an analog switch for TX/RX switching
// #define SX1262_DIO3 (0 + 21)
// This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the
// main
// CPU?
#define SX126X_BUSY (0 + 17)
#define SX126X_RESET (0 + 25)
// Not really an E22 but TTGO seems to be trying to clone that
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
/*
* GPS pins
*/
#define GPS_L76K
// #define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K
// #define GPS_RESET_MODE LOW
// #define PIN_GPS_EN (21)
#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing
#define VEXT_ON_VALUE HIGH
// #define GPS_EN_ACTIVE HIGH
#define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake
#define PIN_GPS_PPS (32 + 4)
// Seems to be missing on this new board
// #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS
#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU
#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS
#define GPS_THREAD_INTERVAL 50
#define PIN_SERIAL1_RX GPS_TX_PIN
#define PIN_SERIAL1_TX GPS_RX_PIN
/*
* SPI Interfaces
*/
#define SPI_INTERFACES_COUNT 1
// For LORA, spi 0
#define PIN_SPI_MISO (0 + 23)
#define PIN_SPI_MOSI (0 + 22)
#define PIN_SPI_SCK (0 + 19)
// #define PIN_PWR_EN (0 + 6)
// To debug via the segger JLINK console rather than the CDC-ACM serial device
// #define USE_SEGGER
#define BQ4050_SDA_PIN (32+1) // I2C data line pin
#define BQ4050_SCL_PIN (32+0) // I2C clock line pin
#define BQ4050_EMERGENCY_SHUTDOWN_PIN (32+3) // Emergency shutdown pin
#define HAS_RTC 0
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif
+1 -9
View File
@@ -4,15 +4,7 @@ extends = nrf52840_base
board = meshtiny board = meshtiny
board_level = extra board_level = extra
build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/meshtiny -D MESHTINY build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/meshtiny -D MESHTINY
-DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. -D USE_PIN_BUZZER
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1
-DRADIOLIB_EXCLUDE_LR11X0=1
-D INPUTDRIVER_ENCODER_TYPE=2
-D INPUTDRIVER_ENCODER_UP=4
-D INPUTDRIVER_ENCODER_DOWN=26
-D INPUTDRIVER_ENCODER_BTN=28
-D USE_PIN_BUZZER=PIN_BUZZER
-D MESHTASTIC_EXCLUDE_GPS=1 -D MESHTASTIC_EXCLUDE_GPS=1
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshtiny> build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshtiny>
lib_deps = lib_deps =
+3 -6
View File
@@ -21,8 +21,6 @@
#define MESHTINY #define MESHTINY
// #define RAK4630
/** Master clock frequency */ /** Master clock frequency */
#define VARIANT_MCK (64000000ul) #define VARIANT_MCK (64000000ul)
@@ -76,11 +74,10 @@ extern "C" {
* Buttons * Buttons
*/ */
#define PIN_BUTTON1 9 #define CANCEL_BUTTON_PIN 9
#define BUTTON_NEED_PULLUP #define BUTTON_NEED_PULLUP
#define PIN_BUTTON2 12 #define CANCEL_BUTTON_ACTIVE_LOW true
#define PIN_BUTTON3 24 #define CANCEL_BUTTON_ACTIVE_PULLUP false
#define PIN_BUTTON4 25
/* /*
* Analog pins * Analog pins
@@ -18,16 +18,9 @@
// Shared NicheGraphics components // Shared NicheGraphics components
// -------------------------------- // --------------------------------
#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h" #include "graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h"
#include "graphics/niche/Drivers/EInk/GDEY0213B74.h"
#include "graphics/niche/Inputs/TwoButton.h" #include "graphics/niche/Inputs/TwoButton.h"
// Special case - fix T-Echo's touch button
// ----------------------------------------
// On a handful of T-Echos, LoRa TX triggers the capacitive touch
// To avoid this, we lockout the button during TX
#include "mesh/RadioLibInterface.h"
void setupNicheGraphics() void setupNicheGraphics()
{ {
using namespace NicheGraphics; using namespace NicheGraphics;
@@ -41,7 +34,7 @@ void setupNicheGraphics()
// E-Ink Driver // E-Ink Driver
// ----------------------------- // -----------------------------
Drivers::EInk *driver = new Drivers::GDEY0213B74; Drivers::EInk *driver = new Drivers::ZJY122250_0213BAAMFGN;
driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES); driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES);
// InkHUD // InkHUD
@@ -53,8 +46,7 @@ void setupNicheGraphics()
inkhud->setDriver(driver); inkhud->setDriver(driver);
// Set how many FAST updates per FULL update // Set how many FAST updates per FULL update
// Set how unhealthy additional FAST updates beyond this number are inkhud->setDisplayResilience(15);
inkhud->setDisplayResilience(7, 1.5);
// Select fonts // Select fonts
InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252;
@@ -62,16 +54,10 @@ void setupNicheGraphics()
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
// Customize default settings // Customize default settings
inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side inkhud->persistence->settings.rotation = 1; // 90 degrees clockwise
// 270 degrees clockwise
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
inkhud->persistence->settings.optionalMenuItems.backlight = true; // Until proves capacitive button works by touching it inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side
// Setup backlight controller
// Note: AUX button attached further down
Drivers::LatchingBacklight *backlight = Drivers::LatchingBacklight::getInstance();
backlight->setPin(PIN_EINK_EN);
// Pick applets // Pick applets
// Note: order of applets determines priority of "auto-show" feature // Note: order of applets determines priority of "auto-show" feature
@@ -83,11 +69,9 @@ void setupNicheGraphics()
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // -
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0
inkhud->persistence->settings.rotation = 1;
// inkhud->persistence->printSettings(&inkhud->persistence->settings);
// Start running InkHUD // Start running InkHUD
inkhud->begin(); inkhud->begin();
// inkhud->persistence->printSettings(&inkhud->persistence->settings);
// Buttons // Buttons
// -------------------------- // --------------------------

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