Merge branch 'develop' of https://github.com/meshtastic/firmware into clear-rangetest-results
This commit is contained in:
+7
-7
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
Vendored
+6
-2
@@ -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
@@ -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
-1
Submodule protobufs updated: 8985852d75...4c4427c4a7
@@ -8,6 +8,7 @@
|
|||||||
"replacements:all",
|
"replacements:all",
|
||||||
"workarounds:all"
|
"workarounds:all"
|
||||||
],
|
],
|
||||||
|
"baseBranchPatterns": ["master"],
|
||||||
"forkProcessing": "enabled",
|
"forkProcessing": "enabled",
|
||||||
"ignoreDeps": [
|
"ignoreDeps": [
|
||||||
"protobufs"
|
"protobufs"
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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 it’s available in FOCUS_PRESERVE block
|
// Declare this early so it’s 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
@@ -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();
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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;
|
||||||
@@ -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,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
|
||||||
};
|
};
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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;
|
||||||
};
|
};
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 = ∓
|
pi.currentRequest = ∓
|
||||||
|
|
||||||
/// 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)
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -214,4 +215,4 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
|
|||||||
neighbors.push_back(new_nbr);
|
neighbors.push_back(new_nbr);
|
||||||
}
|
}
|
||||||
return &neighbors.back();
|
return &neighbors.back();
|
||||||
}
|
}
|
||||||
@@ -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) {
|
||||||
|
|
||||||
|
|||||||
@@ -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,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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -4,4 +4,7 @@
|
|||||||
#define CANNED_MESSAGE_MODULE_ENABLE 1
|
#define CANNED_MESSAGE_MODULE_ENABLE 1
|
||||||
#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
|
||||||
@@ -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 =
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user