mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-07 04:30:04 +10:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36af332622 | ||
|
|
eb9fa6f4fe | ||
|
|
c06e152e59 | ||
|
|
8b35fefdc7 | ||
|
|
d15666611b |
@@ -34,6 +34,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
libxcb-xkb-dev \
|
||||
libxkbcommon-dev \
|
||||
libinput-dev \
|
||||
libnotify-dev \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN pipx install platformio
|
||||
|
||||
@@ -75,11 +75,11 @@ body:
|
||||
- type: checkboxes
|
||||
id: mui
|
||||
attributes:
|
||||
label: Is this bug report about any UI (https://meshtastic.org/docs/configuration/device-uis/) component firmware?
|
||||
label: Is this bug report about any UI component firmware like InkHUD or Meshtatic UI (MUI)?
|
||||
options:
|
||||
- label: Meshtastic UI aka MUI
|
||||
- label: InkHUD
|
||||
- label: BaseUI
|
||||
- label: Meshtastic UI aka MUI colorTFT
|
||||
- label: InkHUD ePaper
|
||||
- label: OLED slide UI on any display
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
|
||||
@@ -11,4 +11,4 @@ runs:
|
||||
- name: Install libs needed for native build
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev
|
||||
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev libnotify-dev
|
||||
|
||||
@@ -86,13 +86,7 @@ jobs:
|
||||
run: sed -i 's/-DBUILD_EPOCH=$UNIX_TIME/#-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
|
||||
|
||||
- name: PlatformIO Tests
|
||||
run: |
|
||||
set -o pipefail
|
||||
# Filter out SKIPPED summary rows for hardware variants that can't run on the
|
||||
# native host. They flood the log and make it harder to spot real failures.
|
||||
# The JUnit XML is written directly to testreport.xml before the pipe, so
|
||||
# the test artifact is unaffected.
|
||||
platformio test -e coverage -v --junit-output-path testreport.xml 2>&1 | grep -v "[[:space:]]SKIPPED$"
|
||||
run: platformio test -e coverage -v --junit-output-path testreport.xml
|
||||
|
||||
- name: Save test results
|
||||
if: always() # run this step even if previous step failed
|
||||
|
||||
+3
-3
@@ -9,9 +9,9 @@ plugins:
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.524
|
||||
- renovate@43.139.6
|
||||
- renovate@43.132.1
|
||||
- prettier@3.8.3
|
||||
- trufflehog@3.95.2
|
||||
- trufflehog@3.94.3
|
||||
- yamllint@1.38.0
|
||||
- bandit@1.9.4
|
||||
- trivy@0.70.0
|
||||
@@ -19,7 +19,7 @@ lint:
|
||||
- ruff@0.15.11
|
||||
- isort@8.0.1
|
||||
- markdownlint@0.48.0
|
||||
- oxipng@10.1.1
|
||||
- oxipng@10.1.0
|
||||
- svgo@4.0.1
|
||||
- actionlint@1.7.12
|
||||
- flake8@7.3.0
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
curl wget g++ zip git ca-certificates pkg-config \
|
||||
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
|
||||
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev libnotify-dev \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& pip install --no-cache-dir -U platformio \
|
||||
&& mkdir /tmp/firmware
|
||||
@@ -53,7 +53,7 @@ USER root
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install \
|
||||
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libi2c0 libuv1t64 libusb-1.0-0-dev \
|
||||
liborcania2.3 libulfius2.7t64 libssl3t64 \
|
||||
libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 \
|
||||
libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 libnotify4 \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
g++ git ca-certificates pkg-config \
|
||||
libgpiod-dev libyaml-cpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
|
||||
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev libnotify-dev \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
&& pip install --no-cache-dir platformio==6.1.19 \
|
||||
&& useradd --create-home --shell /usr/sbin/nologin meshtastic
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ RUN apk --no-cache add \
|
||||
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
|
||||
libgpiod-dev yaml-cpp-dev bluez-dev \
|
||||
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
|
||||
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-dev sqlite-dev sdl2-dev libnotify-dev \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& pip install --no-cache-dir -U platformio \
|
||||
&& mkdir /tmp/firmware
|
||||
@@ -42,7 +42,7 @@ USER root
|
||||
|
||||
RUN apk --no-cache add \
|
||||
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
|
||||
i2c-tools libuv libx11 libinput libxkbcommon sdl2 \
|
||||
i2c-tools libuv libx11 libinput libxkbcommon sdl2 libnotify \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
|
||||
Vendored
+2
-1
@@ -27,7 +27,8 @@ Build-Depends: debhelper-compat (= 13),
|
||||
libinput-dev,
|
||||
libxkbcommon-x11-dev,
|
||||
libsqlite3-dev,
|
||||
libsdl2-dev
|
||||
libsdl2-dev,
|
||||
libnotify-dev
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://github.com/meshtastic/firmware
|
||||
Rules-Requires-Root: no
|
||||
|
||||
Vendored
+2
@@ -7,3 +7,5 @@ bin/meshtasticd.service lib/systemd/system
|
||||
bin/meshtasticd-start.sh usr/bin
|
||||
|
||||
web/* usr/share/meshtasticd/web
|
||||
|
||||
bin/org.meshtastic.meshtasticd.svg usr/share/icons/hicolor/scalable/apps
|
||||
|
||||
@@ -50,6 +50,7 @@ BuildRequires: pkgconfig(x11)
|
||||
BuildRequires: pkgconfig(libinput)
|
||||
BuildRequires: pkgconfig(xkbcommon-x11)
|
||||
BuildRequires: pkgconfig(sdl2)
|
||||
BuildRequires: pkgconfig(libnotify)
|
||||
|
||||
# libbsd is needed on older Fedora/RHEL to provide 'strlcpy'
|
||||
%if 0%{?fedora} >= 39 || 0%{?rhel} >= 10
|
||||
@@ -112,6 +113,10 @@ cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
|
||||
# Install default SSL storage directory (for web)
|
||||
mkdir -p %{buildroot}%{_sysconfdir}/meshtasticd/ssl
|
||||
|
||||
# Install desktop icon
|
||||
mkdir -p %{buildroot}%{_datadir}/icons/hicolor/scalable/apps
|
||||
install -m 0644 bin/org.meshtastic.meshtasticd.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/org.meshtastic.meshtasticd.svg
|
||||
|
||||
%pre
|
||||
# create spi group (for udev rules)
|
||||
getent group spi > /dev/null || groupadd -r spi
|
||||
@@ -171,6 +176,7 @@ fi
|
||||
%dir %{_datadir}/meshtasticd/web
|
||||
%{_datadir}/meshtasticd/web/*
|
||||
%dir %{_sysconfdir}/meshtasticd/ssl
|
||||
%{_datadir}/icons/hicolor/scalable/apps/org.meshtastic.meshtasticd.svg
|
||||
|
||||
%changelog
|
||||
%autochangelog
|
||||
|
||||
+1
-1
Submodule protobufs updated: d004f503bb...4d5b500df5
+67
-133
@@ -94,31 +94,16 @@ static const adc_atten_t atten = ADC_ATTENUATION;
|
||||
#endif
|
||||
#endif // BATTERY_PIN && ARCH_ESP32
|
||||
|
||||
#ifdef EXT_PWR_DETECT
|
||||
#ifndef EXT_PWR_DETECT_MODE
|
||||
#define EXT_PWR_DETECT_MODE INPUT
|
||||
// If using internal pull resistors, we can infer EXT_PWR_DETECT_VALUE
|
||||
#elif EXT_PWR_DETECT_MODE == INPUT_PULLUP
|
||||
#define EXT_PWR_DETECT_VALUE LOW
|
||||
#elif EXT_PWR_DETECT_MODE == INPUT_PULLDOWN
|
||||
#define EXT_PWR_DETECT_VALUE HIGH
|
||||
#endif
|
||||
#ifndef EXT_PWR_DETECT_VALUE
|
||||
#define EXT_PWR_DETECT_VALUE HIGH
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
#ifndef EXT_CHRG_DETECT_MODE
|
||||
#define EXT_CHRG_DETECT_MODE INPUT
|
||||
// If using internal pull resistors, we can infer EXT_CHRG_DETECT_VALUE
|
||||
#elif EXT_CHRG_DETECT_MODE == INPUT_PULLUP
|
||||
#define EXT_CHRG_DETECT_VALUE LOW
|
||||
#elif EXT_CHRG_DETECT_MODE == INPUT_PULLDOWN
|
||||
#define EXT_CHRG_DETECT_VALUE HIGH
|
||||
static const uint8_t ext_chrg_detect_mode = INPUT;
|
||||
#else
|
||||
static const uint8_t ext_chrg_detect_mode = EXT_CHRG_DETECT_MODE;
|
||||
#endif
|
||||
#ifndef EXT_CHRG_DETECT_VALUE
|
||||
#define EXT_CHRG_DETECT_VALUE HIGH
|
||||
static const uint8_t ext_chrg_detect_value = HIGH;
|
||||
#else
|
||||
static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -484,14 +469,28 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; }
|
||||
#endif
|
||||
|
||||
// Detect if an external power source is connected if we don’t have a PMIC;
|
||||
// Firstly prefer EXT_PWR_DETECT GPIO if available,
|
||||
// secondly try an nRF52-specific routine on some variants,
|
||||
// lastly provide a fallback to indicate external power when fully charged.
|
||||
/// If we see a battery voltage higher than physics allows - assume charger is
|
||||
/// pumping in power On some boards we don't have the power management chip
|
||||
/// (like AXPxxxx) so we use EXT_PWR_DETECT GPIO pin to detect external power
|
||||
/// source
|
||||
virtual bool isVbusIn() override
|
||||
{
|
||||
#ifdef EXT_PWR_DETECT
|
||||
return digitalRead(EXT_PWR_DETECT) == EXT_PWR_DETECT_VALUE;
|
||||
#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
|
||||
// if external powered that pin will be pulled down
|
||||
if (digitalRead(EXT_PWR_DETECT) == LOW) {
|
||||
return true;
|
||||
}
|
||||
// if it's not LOW - check the battery
|
||||
#else
|
||||
// if external powered that pin will be pulled up
|
||||
if (digitalRead(EXT_PWR_DETECT) == HIGH) {
|
||||
return true;
|
||||
}
|
||||
// if it's not HIGH - check the battery
|
||||
#endif
|
||||
// If we have an EXT_PWR_DETECT pin and it indicates no external power, believe it.
|
||||
return false;
|
||||
|
||||
// technically speaking this should work for all(?) NRF52 boards
|
||||
// but needs testing across multiple devices. NRF52 USB would not even work if
|
||||
@@ -512,9 +511,9 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
}
|
||||
#endif
|
||||
#if defined(ELECROW_ThinkNode_M6)
|
||||
return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE || isVbusIn();
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value || isVbusIn();
|
||||
#elif EXT_CHRG_DETECT
|
||||
return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE;
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||
#elif defined(BATTERY_CHARGING_INV)
|
||||
return !digitalRead(BATTERY_CHARGING_INV);
|
||||
#else
|
||||
@@ -647,10 +646,14 @@ Power::Power() : OSThread("Power")
|
||||
bool Power::analogInit()
|
||||
{
|
||||
#ifdef EXT_PWR_DETECT
|
||||
pinMode(EXT_PWR_DETECT, EXT_PWR_DETECT_MODE);
|
||||
#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
|
||||
pinMode(EXT_PWR_DETECT, INPUT_PULLUP);
|
||||
#else
|
||||
pinMode(EXT_PWR_DETECT, INPUT);
|
||||
#endif
|
||||
#endif
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
pinMode(EXT_CHRG_DETECT, EXT_CHRG_DETECT_MODE);
|
||||
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
|
||||
#endif
|
||||
|
||||
#ifdef BATTERY_PIN
|
||||
@@ -743,17 +746,37 @@ bool Power::setup()
|
||||
found = true;
|
||||
#endif
|
||||
}
|
||||
attachPowerInterrupts();
|
||||
#ifdef EXT_PWR_DETECT
|
||||
attachInterrupt(
|
||||
EXT_PWR_DETECT,
|
||||
[]() {
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
#ifdef BATTERY_CHARGING_INV
|
||||
attachInterrupt(
|
||||
BATTERY_CHARGING_INV,
|
||||
[]() {
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
attachInterrupt(
|
||||
EXT_CHRG_DETECT,
|
||||
[]() {
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
enabled = found;
|
||||
low_voltage_counter = 0;
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Register callbacks for before and after lightsleep
|
||||
// Used to detach and reattach interrupts
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
#endif
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
@@ -1032,97 +1055,6 @@ int32_t Power::runOnce()
|
||||
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
|
||||
}
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
|
||||
// Detach our class' interrupts before lightsleep
|
||||
// Allows sleep.cpp to configure its own interrupts, which wake the device on user-button press
|
||||
int Power::beforeLightSleep(void *unused)
|
||||
{
|
||||
LOG_WARN("Detaching power interrupts for sleep");
|
||||
detachPowerInterrupts();
|
||||
return 0; // Indicates success
|
||||
}
|
||||
|
||||
// Reconfigure our interrupts
|
||||
// Our class' interrupts were disconnected during sleep, to allow the user button to wake the device from sleep
|
||||
int Power::afterLightSleep(esp_sleep_wakeup_cause_t cause)
|
||||
{
|
||||
attachPowerInterrupts();
|
||||
return 0; // Indicates success
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Attach (or re-attach) hardware interrupts for power management
|
||||
* Public method. Used outside class when waking from MCU sleep
|
||||
*/
|
||||
void Power::attachPowerInterrupts()
|
||||
{
|
||||
#ifdef EXT_PWR_DETECT
|
||||
attachInterrupt(
|
||||
EXT_PWR_DETECT,
|
||||
[]() {
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
#ifdef BATTERY_CHARGING_INV
|
||||
attachInterrupt(
|
||||
BATTERY_CHARGING_INV,
|
||||
[]() {
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
attachInterrupt(
|
||||
EXT_CHRG_DETECT,
|
||||
[]() {
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
},
|
||||
CHANGE);
|
||||
#endif
|
||||
#ifdef PMU_IRQ
|
||||
if (PMU) {
|
||||
attachInterrupt(
|
||||
PMU_IRQ,
|
||||
[] {
|
||||
pmu_irq = true;
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
},
|
||||
FALLING);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Detach the "normal" button interrupts.
|
||||
* Public method. Used before attaching a "wake-on-button" interrupt for MCU sleep
|
||||
*/
|
||||
void Power::detachPowerInterrupts()
|
||||
{
|
||||
#ifdef EXT_PWR_DETECT
|
||||
detachInterrupt(EXT_PWR_DETECT);
|
||||
#endif
|
||||
#ifdef BATTERY_CHARGING_INV
|
||||
detachInterrupt(BATTERY_CHARGING_INV);
|
||||
#endif
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
detachInterrupt(EXT_CHRG_DETECT);
|
||||
#endif
|
||||
#ifdef PMU_IRQ
|
||||
if (PMU) {
|
||||
detachInterrupt(PMU_IRQ);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the power manager chip
|
||||
*
|
||||
@@ -1400,6 +1332,8 @@ bool Power::axpChipInit()
|
||||
}
|
||||
|
||||
pinMode(PMU_IRQ, INPUT);
|
||||
attachInterrupt(
|
||||
PMU_IRQ, [] { pmu_irq = true; }, FALLING);
|
||||
|
||||
// we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ
|
||||
// because it occurs repeatedly while there is no battery also it could cause
|
||||
@@ -1872,7 +1806,7 @@ class SerialBatteryLevel : public HasBatteryLevel
|
||||
{
|
||||
#if defined(EXT_CHRG_DETECT)
|
||||
|
||||
return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE;
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||
|
||||
#endif
|
||||
return false;
|
||||
@@ -1881,7 +1815,7 @@ class SerialBatteryLevel : public HasBatteryLevel
|
||||
virtual bool isCharging() override
|
||||
{
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
return digitalRead(EXT_CHRG_DETECT) == EXT_CHRG_DETECT_VALUE;
|
||||
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
|
||||
|
||||
#endif
|
||||
// by default, we check the battery voltage only
|
||||
@@ -1903,10 +1837,10 @@ SerialBatteryLevel serialBatteryLevel;
|
||||
bool Power::serialBatteryInit()
|
||||
{
|
||||
#ifdef EXT_PWR_DETECT
|
||||
pinMode(EXT_PWR_DETECT, EXT_PWR_DETECT_MODE);
|
||||
pinMode(EXT_PWR_DETECT, INPUT);
|
||||
#endif
|
||||
#ifdef EXT_CHRG_DETECT
|
||||
pinMode(EXT_CHRG_DETECT, EXT_CHRG_DETECT_MODE);
|
||||
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
|
||||
#endif
|
||||
|
||||
bool result = serialBatteryLevel.runOnce();
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@
|
||||
|
||||
TX_LOG + RX_LOG = Total air time for a particular meshtastic channel.
|
||||
|
||||
TX_LOG + RX_ALL_LOG = Total air time for a particular meshtastic channel, including
|
||||
other lora radios.
|
||||
TX_LOG + RX_LOG = Total air time for a particular meshtastic channel, including
|
||||
other lora radios.
|
||||
|
||||
RX_ALL_LOG - RX_LOG = Other lora radios on our frequency channel.
|
||||
*/
|
||||
|
||||
@@ -499,7 +499,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define MESHTASTIC_EXCLUDE_PKI 1
|
||||
#define MESHTASTIC_EXCLUDE_POWER_FSM 1
|
||||
#define MESHTASTIC_EXCLUDE_TZ 1
|
||||
#define MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH 1
|
||||
#endif
|
||||
|
||||
// Turn off all optional modules
|
||||
|
||||
@@ -275,12 +275,9 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
||||
|
||||
int nameMaxWidth = getNodeNameMaxWidth(columnWidth, columnWidth - 25);
|
||||
int barsOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 20 : 24) : (isLeftCol ? 15 : 19);
|
||||
constexpr int kBarCount = 4;
|
||||
constexpr int kBarWidth = 2;
|
||||
constexpr int kBarGap = 1;
|
||||
int hopOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 21 : 29) : (isLeftCol ? 13 : 17);
|
||||
|
||||
int barsXOffset = columnWidth - barsOffset;
|
||||
int barsRightEdge = x + barsXOffset + ((kBarCount - 1) * (kBarWidth + kBarGap)) + kBarWidth;
|
||||
|
||||
const int nameX = x + ((currentResolution == ScreenResolution::High) ? 6 : 3);
|
||||
char nodeName[96];
|
||||
@@ -307,35 +304,28 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
||||
}
|
||||
}
|
||||
|
||||
const bool isZeroHop = node->has_hops_away && node->hops_away == 0;
|
||||
// Draw signal strength bars
|
||||
int bars = (node->snr > 5) ? 4 : (node->snr > 0) ? 3 : (node->snr > -5) ? 2 : (node->snr > -10) ? 1 : 0;
|
||||
int barWidth = 2;
|
||||
int barStartX = x + barsXOffset;
|
||||
int barStartY = y + 1 + (FONT_HEIGHT_SMALL / 2) + 2;
|
||||
|
||||
// Show signal only for direct neighbors (0 hops)
|
||||
if (isZeroHop) {
|
||||
int bars = (node->snr > 5) ? 4 : (node->snr > 0) ? 3 : (node->snr > -5) ? 2 : (node->snr > -10) ? 1 : 0;
|
||||
int barStartX = x + barsXOffset;
|
||||
int barStartY = y + 1 + (FONT_HEIGHT_SMALL / 2) + 2;
|
||||
|
||||
for (int b = 0; b < kBarCount; b++) {
|
||||
if (b < bars) {
|
||||
int height = (b * 2);
|
||||
display->fillRect(barStartX + (b * (kBarWidth + kBarGap)), barStartY - height, kBarWidth, height);
|
||||
}
|
||||
for (int b = 0; b < 4; b++) {
|
||||
if (b < bars) {
|
||||
int height = (b * 2);
|
||||
display->fillRect(barStartX + (b * (barWidth + 1)), barStartY - height, barWidth, height);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw hop count + hop icon
|
||||
if (node->has_hops_away && node->hops_away > 0) {
|
||||
char hopCount[6];
|
||||
snprintf(hopCount, sizeof(hopCount), "%d", node->hops_away);
|
||||
// Draw hop count
|
||||
char hopStr[6] = "";
|
||||
if (node->has_hops_away && node->hops_away > 0)
|
||||
snprintf(hopStr, sizeof(hopStr), "[%d]", node->hops_away);
|
||||
|
||||
const int hopCountWidth = display->getStringWidth(hopCount);
|
||||
const int gap = 1;
|
||||
const int totalWidth = hopCountWidth + gap + hop_width;
|
||||
const int hopX = barsRightEdge - totalWidth;
|
||||
const int iconY = y + (FONT_HEIGHT_SMALL - hop_height) / 2;
|
||||
|
||||
display->drawString(hopX, y, hopCount);
|
||||
display->drawXbm(hopX + hopCountWidth + gap, iconY, hop_width, hop_height, hop);
|
||||
if (hopStr[0] != '\0') {
|
||||
int rightEdge = x + columnWidth - hopOffset;
|
||||
int textWidth = display->getStringWidth(hopStr);
|
||||
display->drawString(rightEdge - textWidth, y, hopStr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -405,10 +405,10 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
}
|
||||
#endif
|
||||
|
||||
// === 2. Signal/Hops line (if available) ===
|
||||
// === 2. Signal and Hops (combined on one line, if available) ===
|
||||
char signalHopsStr[32] = "";
|
||||
bool haveSignal = false;
|
||||
int bars = 0;
|
||||
const char *qualityLabel = nullptr;
|
||||
|
||||
// Helper to get SNR limit based on modem preset
|
||||
auto getSnrLimit = [](meshtastic_Config_LoRaConfig_ModemPreset preset) -> float {
|
||||
@@ -429,52 +429,81 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate signal grade using modem preset and SNR only
|
||||
float snrLimit = getSnrLimit(config.lora.modem_preset);
|
||||
float snr = node->snr;
|
||||
|
||||
// Determine signal quality label and bars using SNR-only grading
|
||||
const char *qualityLabel = nullptr;
|
||||
|
||||
if (snr > snrLimit + 10) {
|
||||
qualityLabel = "Good";
|
||||
bars = 4;
|
||||
} else if (snr > snrLimit + 6) {
|
||||
qualityLabel = "Good";
|
||||
bars = 3;
|
||||
} else if (snr > snrLimit + 2) {
|
||||
qualityLabel = "Good";
|
||||
bars = 2;
|
||||
} else if (snr > snrLimit - 4) {
|
||||
qualityLabel = "Fair";
|
||||
bars = 1;
|
||||
} else {
|
||||
qualityLabel = "Bad";
|
||||
bars = 1;
|
||||
}
|
||||
|
||||
// Add extra spacing on the left if we have an API connection to account for the common footer icons
|
||||
const char *leftSideSpacing =
|
||||
graphics::isAPIConnected(service->api_state) ? (currentResolution == ScreenResolution::High ? " " : " ") : " ";
|
||||
const bool isZeroHop = node->has_hops_away && node->hops_away == 0;
|
||||
|
||||
// Signal text/bars are only for direct (zero-hop) nodes with valid SNR.
|
||||
if (isZeroHop) {
|
||||
float snr = node->snr;
|
||||
if (snr > -100 && snr != 0) {
|
||||
float snrLimit = getSnrLimit(config.lora.modem_preset);
|
||||
// Determine signal quality label and bars using SNR-only grading.
|
||||
if (snr > snrLimit + 10) {
|
||||
qualityLabel = "Good";
|
||||
bars = 4;
|
||||
} else if (snr > snrLimit + 6) {
|
||||
qualityLabel = "Good";
|
||||
bars = 3;
|
||||
} else if (snr > snrLimit + 2) {
|
||||
qualityLabel = "Good";
|
||||
bars = 2;
|
||||
} else if (snr > snrLimit - 4) {
|
||||
qualityLabel = "Fair";
|
||||
bars = 1;
|
||||
} else {
|
||||
qualityLabel = "Bad";
|
||||
bars = 1;
|
||||
}
|
||||
// --- Build the Signal/Hops line ---
|
||||
// Only show signal if we have valid SNR
|
||||
if (snr > -100 && snr != 0) {
|
||||
snprintf(signalHopsStr, sizeof(signalHopsStr), "%sSig:%s", leftSideSpacing, qualityLabel);
|
||||
haveSignal = true;
|
||||
}
|
||||
|
||||
haveSignal = true;
|
||||
if (node->hops_away > 0) {
|
||||
size_t len = strlen(signalHopsStr);
|
||||
if (haveSignal) {
|
||||
snprintf(signalHopsStr + len, sizeof(signalHopsStr) - len, " [#]");
|
||||
} else {
|
||||
snprintf(signalHopsStr, sizeof(signalHopsStr), "[#]");
|
||||
}
|
||||
}
|
||||
|
||||
const bool showHops = node->has_hops_away && node->hops_away > 0;
|
||||
|
||||
if (haveSignal || showHops) {
|
||||
if (signalHopsStr[0]) {
|
||||
int yPos = getTextPositions(display)[line++];
|
||||
int curX = x + display->getStringWidth(leftSideSpacing);
|
||||
int curX = x;
|
||||
|
||||
// Draw signal quality text for zero-hop nodes when present.
|
||||
if (haveSignal && qualityLabel) {
|
||||
char signalLabel[20];
|
||||
snprintf(signalLabel, sizeof(signalLabel), "Sig:%s", qualityLabel);
|
||||
display->drawString(curX, yPos, signalLabel);
|
||||
curX += display->getStringWidth(signalLabel) + 4;
|
||||
// Split combined string into signal text and hop suffix
|
||||
char sigPart[20] = "";
|
||||
const char *hopPart = nullptr;
|
||||
|
||||
char *bracket = strchr(signalHopsStr, '[');
|
||||
if (bracket) {
|
||||
size_t n = (size_t)(bracket - signalHopsStr);
|
||||
if (n >= sizeof(sigPart))
|
||||
n = sizeof(sigPart) - 1;
|
||||
memcpy(sigPart, signalHopsStr, n);
|
||||
sigPart[n] = '\0';
|
||||
|
||||
// Trim trailing spaces
|
||||
while (strlen(sigPart) && sigPart[strlen(sigPart) - 1] == ' ') {
|
||||
sigPart[strlen(sigPart) - 1] = '\0';
|
||||
}
|
||||
|
||||
hopPart = bracket; // "[n Hop(s)]"
|
||||
} else {
|
||||
strncpy(sigPart, signalHopsStr, sizeof(sigPart) - 1);
|
||||
sigPart[sizeof(sigPart) - 1] = '\0';
|
||||
}
|
||||
|
||||
// Draw signal quality text
|
||||
display->drawString(curX, yPos, sigPart);
|
||||
curX += display->getStringWidth(sigPart) + 4;
|
||||
|
||||
// Draw signal bars (skip on UltraLow, text only)
|
||||
if (currentResolution != ScreenResolution::UltraLow && haveSignal && bars > 0) {
|
||||
const int kMaxBars = 4;
|
||||
@@ -512,12 +541,12 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
curX += (kMaxBars * barWidth) + ((kMaxBars - 1) * barGap) + 2;
|
||||
}
|
||||
|
||||
// Draw hops for non-zero-hop nodes as: number + hop icon.
|
||||
// This path is mutually exclusive with the zero-hop signal-bars path above.
|
||||
if (showHops) {
|
||||
// hop label
|
||||
display->drawString(curX, yPos, "Hop:");
|
||||
curX += display->getStringWidth("Hop:") + 2;
|
||||
// Draw hops AFTER the bars as: [ number + hop icon ]
|
||||
if (hopPart && node->hops_away > 0) {
|
||||
|
||||
// open bracket
|
||||
display->drawString(curX, yPos, "[");
|
||||
curX += display->getStringWidth("[") + 1;
|
||||
|
||||
// hop count
|
||||
char hopCount[6];
|
||||
@@ -529,6 +558,9 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
const int iconY = yPos + (FONT_HEIGHT_SMALL - hop_height) / 2;
|
||||
display->drawXbm(curX, iconY, hop_width, hop_height, hop);
|
||||
curX += hop_width + 1;
|
||||
|
||||
// closing bracket
|
||||
display->drawString(curX, yPos, "]");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
|
||||
NicheGraphics parallel E-Ink driver for the LilyGo T5-S3-ePaper-Pro (ED047TC1).
|
||||
|
||||
InkHUD buffer format : 1bpp, horizontal bytes, MSB = leftmost pixel, 1 = white
|
||||
FastEPD buffer format: 1bpp, horizontal bytes, MSB = leftmost pixel, 1 = white
|
||||
|
||||
Both formats share the same pixel layout and polarity (1 = white, 0 = black).
|
||||
The InkHUD safe-area buffer (944×523) is copied into the centre of the physical
|
||||
960×540 FastEPD buffer so content clears the panel's inactive edge border.
|
||||
See ED047TC1.h for the H_OFFSET_BYTES / V_OFFSET_TOP / V_OFFSET_BOTTOM constants.
|
||||
|
||||
*/
|
||||
|
||||
// Ruler diagnostic — uncomment to draw calibration lines at each physical edge.
|
||||
// #define EINK_EDGE_LINES
|
||||
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
#ifdef T5_S3_EPAPER_PRO
|
||||
|
||||
#include "./ED047TC1.h"
|
||||
|
||||
#include "FastEPD.h"
|
||||
#include "configuration.h"
|
||||
|
||||
using namespace NicheGraphics::Drivers;
|
||||
|
||||
void ED047TC1::begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst)
|
||||
{
|
||||
// Parallel display — SPI parameters are not used
|
||||
(void)spi;
|
||||
(void)pin_dc;
|
||||
(void)pin_cs;
|
||||
(void)pin_busy;
|
||||
(void)pin_rst;
|
||||
|
||||
epaper = new FASTEPD;
|
||||
|
||||
#if defined(T5_S3_EPAPER_PRO_V1)
|
||||
epaper->initPanel(BB_PANEL_LILYGO_T5PRO, 28000000);
|
||||
#elif defined(T5_S3_EPAPER_PRO_V2)
|
||||
epaper->initPanel(BB_PANEL_LILYGO_T5PRO_V2, 28000000);
|
||||
// Initialize all PCA9535 port-0 pins as outputs / HIGH
|
||||
for (int i = 0; i < 8; i++) {
|
||||
epaper->ioPinMode(i, OUTPUT);
|
||||
epaper->ioWrite(i, HIGH);
|
||||
}
|
||||
#else
|
||||
#error "ED047TC1 driver: unsupported variant — define T5_S3_EPAPER_PRO_V1 or T5_S3_EPAPER_PRO_V2"
|
||||
#endif
|
||||
|
||||
epaper->setMode(BB_MODE_1BPP);
|
||||
epaper->clearWhite();
|
||||
epaper->fullUpdate(true); // Blocking initial clear
|
||||
}
|
||||
|
||||
void ED047TC1::update(uint8_t *imageData, UpdateTypes type)
|
||||
{
|
||||
if (!epaper)
|
||||
return;
|
||||
|
||||
// InkHUD renders into a DISPLAY_WIDTH × DISPLAY_HEIGHT safe-area buffer.
|
||||
// We need to place that into the centre of the physical 960×540 FastEPD buffer,
|
||||
// leaving blank margins at every edge to avoid the panel's inactive border.
|
||||
const uint32_t srcRowBytes = (DISPLAY_WIDTH + 7) / 8; // bytes per row in InkHUD buffer (118)
|
||||
const uint32_t dstRowBytes = (960 + 7) / 8; // bytes per row in physical buffer (120)
|
||||
const uint32_t dstTotalRows = 540;
|
||||
|
||||
uint8_t *cur = epaper->currentBuffer();
|
||||
|
||||
// Fill physical buffer with white (0xFF = white in FastEPD 1bpp)
|
||||
memset(cur, 0xFF, dstRowBytes * dstTotalRows);
|
||||
|
||||
// Copy each InkHUD row into the physical buffer with horizontal + vertical offsets
|
||||
for (uint32_t row = 0; row < DISPLAY_HEIGHT; row++) {
|
||||
const uint8_t *srcRow = imageData + row * srcRowBytes;
|
||||
uint8_t *dstRow = cur + (row + V_OFFSET_TOP) * dstRowBytes + H_OFFSET_BYTES;
|
||||
memcpy(dstRow, srcRow, srcRowBytes);
|
||||
}
|
||||
|
||||
#ifdef EINK_EDGE_LINES
|
||||
// Draw a 1px black box at the exact boundary of the safe area within the
|
||||
// physical buffer. If the margins are correct, all 4 lines should be
|
||||
// fully visible and right at the edge of the usable display area.
|
||||
|
||||
auto setPixelBlack = [&](uint32_t col, uint32_t row) { cur[row * dstRowBytes + col / 8] &= ~(0x80 >> (col % 8)); };
|
||||
|
||||
const uint32_t safeX = H_OFFSET_BYTES * 8;
|
||||
const uint32_t safeY = V_OFFSET_TOP;
|
||||
const uint32_t safeW = DISPLAY_WIDTH;
|
||||
const uint32_t safeH = DISPLAY_HEIGHT;
|
||||
|
||||
// Top edge: horizontal line at safeY
|
||||
for (uint32_t col = safeX; col < safeX + safeW; col++)
|
||||
setPixelBlack(col, safeY);
|
||||
|
||||
// Bottom edge: horizontal line at safeY + safeH - 1
|
||||
for (uint32_t col = safeX; col < safeX + safeW; col++)
|
||||
setPixelBlack(col, safeY + safeH - 1);
|
||||
|
||||
// Left edge: vertical line at safeX
|
||||
for (uint32_t row = safeY; row < safeY + safeH; row++)
|
||||
setPixelBlack(safeX, row);
|
||||
|
||||
// Right edge: vertical line at safeX + safeW - 1
|
||||
for (uint32_t row = safeY; row < safeY + safeH; row++)
|
||||
setPixelBlack(safeX + safeW - 1, row);
|
||||
#endif
|
||||
|
||||
if (type == FULL) {
|
||||
epaper->fullUpdate(CLEAR_SLOW, false);
|
||||
epaper->backupPlane(); // Sync pPrevious so next partialUpdate has a correct baseline
|
||||
} else {
|
||||
// FAST: true partial update — compares pCurrent vs pPrevious and only applies
|
||||
// the update waveform to rows that actually changed. Unchanged rows get a neutral
|
||||
// signal (no visible effect). partialUpdate() updates pPrevious internally.
|
||||
epaper->partialUpdate(false, 0, dstTotalRows - 1);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // T5_S3_EPAPER_PRO
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
|
||||
E-Ink display driver adapter
|
||||
- ED047TC1 (via FastEPD library)
|
||||
- Manufacturer: E Ink / used in LilyGo T5-E-Paper-S3-Pro
|
||||
- Size: 4.7 inch
|
||||
- Physical resolution: 960px x 540px
|
||||
- Interface: 8-bit parallel (NOT SPI)
|
||||
|
||||
Unlike the other NicheGraphics EInk drivers, this one drives a parallel e-paper
|
||||
panel via the FastEPD library. SPI parameters passed to begin() are ignored.
|
||||
|
||||
The ED047TC1 panel has an inactive pixel border on all four edges (~4–8 physical
|
||||
pixels). DISPLAY_WIDTH / DISPLAY_HEIGHT expose a reduced "safe area" to InkHUD so
|
||||
that content is never drawn into this dead zone. The update() method copies the
|
||||
InkHUD frame buffer into the centre of the larger physical 960×540 buffer, using
|
||||
H_OFFSET_BYTES (horizontal, whole bytes = 8 pixels per byte),
|
||||
V_OFFSET_TOP and V_OFFSET_BOTTOM (vertical, pixel rows) to position it.
|
||||
|
||||
Changing these constants shifts content inward from each physical edge:
|
||||
H_OFFSET_BYTES = 1 → 8px left margin, 8px right margin (960 – 8 – 8 = 944)
|
||||
V_OFFSET_TOP = 9 → 9px top margin (asymmetric: top ≠ bottom)
|
||||
V_OFFSET_BOTTOM = 8 → 8px bottom margin (540 – 9 – 8 = 523)
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
#include "./EInk.h"
|
||||
|
||||
// Forward declare to avoid pulling FastEPD into all translation units
|
||||
class FASTEPD;
|
||||
|
||||
namespace NicheGraphics::Drivers
|
||||
{
|
||||
|
||||
class ED047TC1 : public EInk
|
||||
{
|
||||
// Safe-area dimensions exposed to InkHUD (physical panel is 960×540).
|
||||
//
|
||||
// The ED047TC1 has an inactive pixel border on all physical edges.
|
||||
// The physical buffer coordinates do NOT directly match the visual orientation
|
||||
// due to FastEPD's portrait scan direction and InkHUD's rotation=3 (270° CW):
|
||||
//
|
||||
// Physical buffer Visual on device (rotation=3)
|
||||
// ───────────────── ──────────────────────────────
|
||||
// Physical LEFT cols → Visual TOP edge
|
||||
// Physical RIGHT cols → Visual BOTTOM edge
|
||||
// Physical TOP rows → Visual RIGHT edge
|
||||
// Physical BOTTOM rows → Visual LEFT edge
|
||||
//
|
||||
// Offset constants shift the InkHUD safe-area away from each physical dead zone:
|
||||
// H_OFFSET_BYTES : whole bytes from physical left (8px per byte, affects visual TOP)
|
||||
// Physical right margin = 960 − H_OFFSET_BYTES×8 − DISPLAY_WIDTH (affects visual BOTTOM)
|
||||
// V_OFFSET_TOP : pixel rows from physical top (affects visual RIGHT)
|
||||
// V_OFFSET_BOTTOM: pixel rows from physical bottom (affects visual LEFT)
|
||||
//
|
||||
// Calibrated by flashing a 1px border box and adjusting until all 4 sides are visible.
|
||||
|
||||
static constexpr uint16_t DISPLAY_WIDTH = 944; // 960 − H_OFFSET_BYTES×8 − right_margin (8+8 = 16px)
|
||||
static constexpr uint16_t DISPLAY_HEIGHT = 523; // 540 − V_OFFSET_TOP − V_OFFSET_BOTTOM (9+8 = 17px)
|
||||
|
||||
static constexpr uint8_t H_OFFSET_BYTES = 1; // visual TOP : 8px physical left margin
|
||||
// visual BOTTOM: 960−8−944=8px physical right margin
|
||||
static constexpr uint8_t V_OFFSET_TOP = 9; // visual RIGHT : CONFIRMED OK
|
||||
static constexpr uint8_t V_OFFSET_BOTTOM = 8; // visual LEFT : 8px physical bottom margin
|
||||
|
||||
static constexpr UpdateTypes supported = static_cast<UpdateTypes>(FULL | FAST);
|
||||
|
||||
public:
|
||||
ED047TC1() : EInk(DISPLAY_WIDTH, DISPLAY_HEIGHT, supported) {}
|
||||
|
||||
// EInk interface — SPI params are not used for this parallel display
|
||||
void begin(SPIClass *spi, uint8_t pin_dc, uint8_t pin_cs, uint8_t pin_busy, uint8_t pin_rst = 0xFF) override;
|
||||
void update(uint8_t *imageData, UpdateTypes type) override;
|
||||
|
||||
protected:
|
||||
bool isUpdateDone() override { return true; } // FastEPD updates are blocking
|
||||
|
||||
private:
|
||||
FASTEPD *epaper = nullptr;
|
||||
};
|
||||
|
||||
} // namespace NicheGraphics::Drivers
|
||||
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -88,12 +88,8 @@ class AppletFont
|
||||
|
||||
// Greek
|
||||
#include "graphics/niche/Fonts/FreeSans12pt_Win1253.h"
|
||||
#include "graphics/niche/Fonts/FreeSans18pt_Win1253.h"
|
||||
#include "graphics/niche/Fonts/FreeSans24pt_Win1253.h"
|
||||
#include "graphics/niche/Fonts/FreeSans6pt_Win1253.h"
|
||||
#include "graphics/niche/Fonts/FreeSans9pt_Win1253.h"
|
||||
#define FREESANS_24PT_WIN1253 InkHUD::AppletFont(FreeSans24pt_Win1253, InkHUD::AppletFont::WINDOWS_1253, -5, 3)
|
||||
#define FREESANS_18PT_WIN1253 InkHUD::AppletFont(FreeSans18pt_Win1253, InkHUD::AppletFont::WINDOWS_1253, -4, 2)
|
||||
#define FREESANS_12PT_WIN1253 InkHUD::AppletFont(FreeSans12pt_Win1253, InkHUD::AppletFont::WINDOWS_1253, -3, 1)
|
||||
#define FREESANS_9PT_WIN1253 InkHUD::AppletFont(FreeSans9pt_Win1253, InkHUD::AppletFont::WINDOWS_1253, -2, -1)
|
||||
#define FREESANS_6PT_WIN1253 InkHUD::AppletFont(FreeSans6pt_Win1253, InkHUD::AppletFont::WINDOWS_1253, -1, -2)
|
||||
|
||||
@@ -143,10 +143,6 @@ int32_t ButtonThread::runOnce()
|
||||
leadUpSequenceActive = false;
|
||||
resetLeadUpSequence();
|
||||
}
|
||||
#ifdef INPUT_DEBUG
|
||||
if (buttonCurrentlyPressed)
|
||||
LOG_WARN("Button held for %u ms", millis() - buttonPressStartTime);
|
||||
#endif
|
||||
|
||||
// Progressive lead-up sound system
|
||||
if (!_suppressLeadUp && buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
|
||||
@@ -315,8 +311,7 @@ int32_t ButtonThread::runOnce()
|
||||
void ButtonThread::attachButtonInterrupts()
|
||||
{
|
||||
// Interrupt for user button, during normal use. Improves responsiveness.
|
||||
if (_intRoutine != nullptr)
|
||||
attachInterrupt(_pinNum, _intRoutine, CHANGE);
|
||||
attachInterrupt(_pinNum, _intRoutine, CHANGE);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -325,8 +320,7 @@ void ButtonThread::attachButtonInterrupts()
|
||||
*/
|
||||
void ButtonThread::detachButtonInterrupts()
|
||||
{
|
||||
if (_intRoutine != nullptr)
|
||||
detachInterrupt(_pinNum);
|
||||
detachInterrupt(_pinNum);
|
||||
}
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
|
||||
@@ -29,8 +29,7 @@ void TouchScreenImpl1::init()
|
||||
return;
|
||||
#else
|
||||
TouchScreenBase::init(true);
|
||||
if (inputBroker)
|
||||
inputBroker->registerSource(this);
|
||||
inputBroker->registerSource(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+129
@@ -340,9 +340,138 @@ void setup()
|
||||
|
||||
#ifdef BLE_LED
|
||||
pinMode(BLE_LED, OUTPUT);
|
||||
#ifdef BLE_LED_INVERTED
|
||||
digitalWrite(BLE_LED, HIGH);
|
||||
#else
|
||||
digitalWrite(BLE_LED, LOW);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(T_DECK)
|
||||
// GPIO10 manages all peripheral power supplies
|
||||
// Turn on peripheral power immediately after MUC starts.
|
||||
// If some boards are turned on late, ESP32 will reset due to low voltage.
|
||||
// ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) ,
|
||||
// TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder)
|
||||
pinMode(KB_POWERON, OUTPUT);
|
||||
digitalWrite(KB_POWERON, HIGH);
|
||||
// T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus
|
||||
// We need to initialize all CS pins in advance otherwise there will be SPI communication issues
|
||||
// e.g. when detecting the SD card
|
||||
pinMode(LORA_CS, OUTPUT);
|
||||
digitalWrite(LORA_CS, HIGH);
|
||||
pinMode(SDCARD_CS, OUTPUT);
|
||||
digitalWrite(SDCARD_CS, HIGH);
|
||||
pinMode(TFT_CS, OUTPUT);
|
||||
digitalWrite(TFT_CS, HIGH);
|
||||
delay(100);
|
||||
#elif defined(T_DECK_PRO)
|
||||
pinMode(LORA_EN, OUTPUT);
|
||||
digitalWrite(LORA_EN, HIGH);
|
||||
pinMode(LORA_CS, OUTPUT);
|
||||
digitalWrite(LORA_CS, HIGH);
|
||||
pinMode(SDCARD_CS, OUTPUT);
|
||||
digitalWrite(SDCARD_CS, HIGH);
|
||||
pinMode(PIN_EINK_CS, OUTPUT);
|
||||
digitalWrite(PIN_EINK_CS, HIGH);
|
||||
#if PIN_EINK_RES >= 0
|
||||
pinMode(PIN_EINK_RES, OUTPUT);
|
||||
digitalWrite(PIN_EINK_RES, HIGH);
|
||||
#endif
|
||||
pinMode(CST328_PIN_RST, OUTPUT);
|
||||
digitalWrite(CST328_PIN_RST, 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);
|
||||
pinMode(KB_INT, INPUT_PULLUP);
|
||||
// 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, LOW);
|
||||
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);
|
||||
#elif defined(HACKADAY_COMMUNICATOR)
|
||||
pinMode(KB_INT, INPUT);
|
||||
digitalWrite(BLE_LED, LED_STATE_OFF);
|
||||
#endif
|
||||
|
||||
#if defined(T_DECK)
|
||||
// GPIO10 manages all peripheral power supplies
|
||||
// Turn on peripheral power immediately after MUC starts.
|
||||
// If some boards are turned on late, ESP32 will reset due to low voltage.
|
||||
// ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) ,
|
||||
// TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder)
|
||||
pinMode(KB_POWERON, OUTPUT);
|
||||
digitalWrite(KB_POWERON, HIGH);
|
||||
// T-Deck has all three SPI peripherals (TFT, SD, LoRa) attached to the same SPI bus
|
||||
// We need to initialize all CS pins in advance otherwise there will be SPI communication issues
|
||||
// e.g. when detecting the SD card
|
||||
pinMode(LORA_CS, OUTPUT);
|
||||
digitalWrite(LORA_CS, HIGH);
|
||||
pinMode(SDCARD_CS, OUTPUT);
|
||||
digitalWrite(SDCARD_CS, HIGH);
|
||||
pinMode(TFT_CS, OUTPUT);
|
||||
digitalWrite(TFT_CS, HIGH);
|
||||
delay(100);
|
||||
#elif defined(T_DECK_PRO)
|
||||
pinMode(LORA_EN, OUTPUT);
|
||||
digitalWrite(LORA_EN, HIGH);
|
||||
pinMode(LORA_CS, OUTPUT);
|
||||
digitalWrite(LORA_CS, HIGH);
|
||||
pinMode(SDCARD_CS, OUTPUT);
|
||||
digitalWrite(SDCARD_CS, HIGH);
|
||||
pinMode(PIN_EINK_CS, OUTPUT);
|
||||
digitalWrite(PIN_EINK_CS, HIGH);
|
||||
#if PIN_EINK_RES >= 0
|
||||
pinMode(PIN_EINK_RES, OUTPUT);
|
||||
digitalWrite(PIN_EINK_RES, HIGH);
|
||||
#endif
|
||||
pinMode(CST328_PIN_RST, OUTPUT);
|
||||
digitalWrite(CST328_PIN_RST, 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);
|
||||
pinMode(KB_INT, INPUT_PULLUP);
|
||||
// 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, LOW);
|
||||
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);
|
||||
#elif defined(HACKADAY_COMMUNICATOR)
|
||||
pinMode(KB_INT, INPUT);
|
||||
#endif
|
||||
|
||||
concurrency::hasBeenSetup = true;
|
||||
#if HAS_SCREEN
|
||||
meshtastic_Config_DisplayConfig_OledType screen_model =
|
||||
|
||||
@@ -101,12 +101,9 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
|
||||
if (origTx) {
|
||||
// Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came
|
||||
// directly from the destination
|
||||
// Single lookup for both relayer checks on the same (request_id, to) pair
|
||||
bool wasAlreadyRelayer = false;
|
||||
bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to);
|
||||
bool weWereSoleRelayer = false;
|
||||
bool weWereRelayer = false;
|
||||
checkRelayers(p->relay_node, ourRelayID, p->decoded.request_id, p->to, &wasAlreadyRelayer, &weWereRelayer,
|
||||
&weWereSoleRelayer);
|
||||
bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer);
|
||||
if ((weWereRelayer && wasAlreadyRelayer) || (getHopsAway(*p) == 0 && weWereSoleRelayer)) {
|
||||
if (origTx->next_hop != p->relay_node) { // Not already set
|
||||
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply (was relayer %d we were sole %d)", p->from,
|
||||
|
||||
@@ -863,6 +863,10 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
||||
#endif
|
||||
#endif
|
||||
#if ARCH_PORTDUINO
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.alert_message = true;
|
||||
#endif
|
||||
#ifdef NANO_G2_ULTRA
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.alert_message = true;
|
||||
|
||||
+14
-170
@@ -1,7 +1,6 @@
|
||||
#include "PacketHistory.h"
|
||||
#include "configuration.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "meshUtils.h"
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "platform/portduino/PortduinoGlue.h"
|
||||
@@ -24,14 +23,6 @@ PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0), recentPa
|
||||
size = PACKETHISTORY_MAX; // Use default size if invalid
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
|
||||
// Ensure capacity fits in uint16_t hash index (HASH_EMPTY = 0xFFFF is the sentinel)
|
||||
if (size >= HASH_EMPTY) {
|
||||
LOG_WARN("Packet History - Clamping size %d to %d (hash index limit)", size, HASH_EMPTY - 1);
|
||||
size = HASH_EMPTY - 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Allocate memory for the recent packets array
|
||||
recentPacketsCapacity = size;
|
||||
recentPackets = new PacketRecord[recentPacketsCapacity];
|
||||
@@ -44,20 +35,6 @@ PacketHistory::PacketHistory(uint32_t size) : recentPacketsCapacity(0), recentPa
|
||||
|
||||
// Initialize the recent packets array to zero
|
||||
memset(recentPackets, 0, sizeof(PacketRecord) * recentPacketsCapacity);
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
|
||||
// Allocate hash index with load factor <= 0.5 for short probe chains
|
||||
hashCapacity = nextPowerOf2(recentPacketsCapacity * 2);
|
||||
hashMask = hashCapacity - 1;
|
||||
hashIndex = new uint16_t[hashCapacity];
|
||||
if (!hashIndex) {
|
||||
LOG_ERROR("Packet History - Hash index allocation failed for %d entries", hashCapacity);
|
||||
hashCapacity = 0;
|
||||
hashMask = 0;
|
||||
return;
|
||||
}
|
||||
memset(hashIndex, 0xFF, sizeof(uint16_t) * hashCapacity); // Fill with HASH_EMPTY (0xFFFF)
|
||||
#endif
|
||||
}
|
||||
|
||||
PacketHistory::~PacketHistory()
|
||||
@@ -65,12 +42,6 @@ PacketHistory::~PacketHistory()
|
||||
recentPacketsCapacity = 0;
|
||||
delete[] recentPackets;
|
||||
recentPackets = NULL;
|
||||
#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
|
||||
delete[] hashIndex;
|
||||
hashIndex = NULL;
|
||||
hashCapacity = 0;
|
||||
hashMask = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Update recentPackets and return true if we have already seen this packet */
|
||||
@@ -223,78 +194,7 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
|
||||
return seenRecently;
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
|
||||
// Hash function for (sender, id) pairs. Uses xor-shift mixing for good distribution.
|
||||
uint32_t PacketHistory::hashSlot(NodeNum sender, PacketId id) const
|
||||
{
|
||||
uint32_t h = sender ^ (id * 0x9E3779B9); // Fibonacci hashing constant
|
||||
h ^= h >> 16;
|
||||
h *= 0x45d9f3b;
|
||||
h ^= h >> 16;
|
||||
return h & hashMask;
|
||||
}
|
||||
|
||||
void PacketHistory::hashInsert(NodeNum sender, PacketId id, uint16_t slotIdx)
|
||||
{
|
||||
if (!hashIndex)
|
||||
return;
|
||||
uint32_t bucket = hashSlot(sender, id);
|
||||
// Guard against infinite loop if hash table is corrupted (no HASH_EMPTY slots)
|
||||
for (uint32_t i = 0; i < hashCapacity; i++) {
|
||||
if (hashIndex[bucket] == HASH_EMPTY) {
|
||||
hashIndex[bucket] = slotIdx;
|
||||
return;
|
||||
}
|
||||
bucket = (bucket + 1) & hashMask;
|
||||
}
|
||||
LOG_ERROR("Packet History - hashInsert: table full or corrupted, rebuilding");
|
||||
hashRebuild();
|
||||
}
|
||||
|
||||
void PacketHistory::hashRemove(NodeNum sender, PacketId id)
|
||||
{
|
||||
if (!hashIndex)
|
||||
return;
|
||||
uint32_t bucket = hashSlot(sender, id);
|
||||
for (uint32_t i = 0; i < hashCapacity; i++) {
|
||||
if (hashIndex[bucket] == HASH_EMPTY)
|
||||
return;
|
||||
uint16_t idx = hashIndex[bucket];
|
||||
if (idx < recentPacketsCapacity && recentPackets[idx].sender == sender && recentPackets[idx].id == id) {
|
||||
// Found it — delete and re-insert subsequent entries to maintain probe chain integrity
|
||||
hashIndex[bucket] = HASH_EMPTY;
|
||||
uint32_t next = (bucket + 1) & hashMask;
|
||||
for (uint32_t j = 0; j < hashCapacity; j++) {
|
||||
if (hashIndex[next] == HASH_EMPTY)
|
||||
break;
|
||||
uint16_t displaced = hashIndex[next];
|
||||
hashIndex[next] = HASH_EMPTY;
|
||||
if (displaced < recentPacketsCapacity) {
|
||||
const auto &rec = recentPackets[displaced];
|
||||
hashInsert(rec.sender, rec.id, displaced);
|
||||
}
|
||||
next = (next + 1) & hashMask;
|
||||
}
|
||||
return;
|
||||
}
|
||||
bucket = (bucket + 1) & hashMask;
|
||||
}
|
||||
}
|
||||
|
||||
void PacketHistory::hashRebuild()
|
||||
{
|
||||
if (!hashIndex)
|
||||
return;
|
||||
memset(hashIndex, 0xFF, sizeof(uint16_t) * hashCapacity);
|
||||
for (uint32_t i = 0; i < recentPacketsCapacity; i++) {
|
||||
if (recentPackets[i].rxTimeMsec != 0)
|
||||
hashInsert(recentPackets[i].sender, recentPackets[i].id, (uint16_t)i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Find a packet record in history using the hash index for O(1) average lookup.
|
||||
* Falls back to linear scan if hash index is unavailable.
|
||||
/** Find a packet record in history.
|
||||
* @return pointer to PacketRecord if found, NULL if not found */
|
||||
PacketHistory::PacketRecord *PacketHistory::find(NodeNum sender, PacketId id)
|
||||
{
|
||||
@@ -305,40 +205,23 @@ PacketHistory::PacketRecord *PacketHistory::find(NodeNum sender, PacketId id)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
|
||||
// Use hash index for O(1) lookup when available
|
||||
if (hashIndex) {
|
||||
uint32_t bucket = hashSlot(sender, id);
|
||||
for (uint32_t i = 0; i < hashCapacity; i++) {
|
||||
if (hashIndex[bucket] == HASH_EMPTY)
|
||||
break;
|
||||
uint16_t idx = hashIndex[bucket];
|
||||
if (idx < recentPacketsCapacity && recentPackets[idx].id == id && recentPackets[idx].sender == sender) {
|
||||
#if VERBOSE_PACKET_HISTORY
|
||||
LOG_DEBUG("Packet History - find: s=%08x id=%08x FOUND nh=%02x rby=%02x %02x %02x age=%d slot=%d/%d",
|
||||
recentPackets[idx].sender, recentPackets[idx].id, recentPackets[idx].next_hop,
|
||||
recentPackets[idx].relayed_by[0], recentPackets[idx].relayed_by[1], recentPackets[idx].relayed_by[2],
|
||||
millis() - (recentPackets[idx].rxTimeMsec), idx, recentPacketsCapacity);
|
||||
#endif
|
||||
return &recentPackets[idx];
|
||||
}
|
||||
bucket = (bucket + 1) & hashMask;
|
||||
}
|
||||
#if VERBOSE_PACKET_HISTORY
|
||||
LOG_DEBUG("Packet History - find: s=%08x id=%08x NOT FOUND", sender, id);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Linear scan (sole path when hash excluded, fallback when hash allocation failed)
|
||||
for (PacketRecord *it = recentPackets; it < (recentPackets + recentPacketsCapacity); ++it) {
|
||||
PacketRecord *it = NULL;
|
||||
for (it = recentPackets; it < (recentPackets + recentPacketsCapacity); ++it) {
|
||||
if (it->id == id && it->sender == sender) {
|
||||
return it;
|
||||
#if VERBOSE_PACKET_HISTORY
|
||||
LOG_DEBUG("Packet History - find: s=%08x id=%08x FOUND nh=%02x rby=%02x %02x %02x age=%d slot=%d/%d", it->sender,
|
||||
it->id, it->next_hop, it->relayed_by[0], it->relayed_by[1], it->relayed_by[2], millis() - (it->rxTimeMsec),
|
||||
it - recentPackets, recentPacketsCapacity);
|
||||
#endif
|
||||
// only the first match is returned, so be careful not to create duplicate entries
|
||||
return it; // Return pointer to the found record
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
#if VERBOSE_PACKET_HISTORY
|
||||
LOG_DEBUG("Packet History - find: s=%08x id=%08x NOT FOUND", sender, id);
|
||||
#endif
|
||||
return NULL; // Not found
|
||||
}
|
||||
|
||||
/** Insert/Replace oldest PacketRecord in recentPackets. */
|
||||
@@ -444,22 +327,8 @@ void PacketHistory::insert(const PacketRecord &r)
|
||||
return; // Return early if we can't update the history
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
|
||||
// Maintain hash index: remove old entry if evicting a different packet, then insert new entry
|
||||
bool isMatchingSlot = (tu->id == r.id && tu->sender == r.sender);
|
||||
if (!isMatchingSlot && tu->rxTimeMsec != 0) {
|
||||
hashRemove(tu->sender, tu->id);
|
||||
}
|
||||
|
||||
*tu = r; // store the packet
|
||||
|
||||
if (!isMatchingSlot) {
|
||||
hashInsert(r.sender, r.id, (uint16_t)(tu - recentPackets));
|
||||
}
|
||||
#else
|
||||
*tu = r; // store the packet
|
||||
#endif
|
||||
|
||||
#if VERBOSE_PACKET_HISTORY
|
||||
LOG_DEBUG("Packet History - insert: Store slot@ %d/%d s=%08x id=%08x nh=%02x rby=%02x %02x %02x rxT=%d AFTER",
|
||||
tu - recentPackets, recentPacketsCapacity, tu->sender, tu->id, tu->next_hop, tu->relayed_by[0], tu->relayed_by[1],
|
||||
@@ -527,31 +396,6 @@ bool PacketHistory::wasRelayer(const uint8_t relayer, const PacketRecord &r, boo
|
||||
return found;
|
||||
}
|
||||
|
||||
// Check two relayers against the same packet record with a single find() call,
|
||||
// avoiding redundant O(N) lookups when both are checked for the same (id, sender) pair.
|
||||
void PacketHistory::checkRelayers(uint8_t relayer1, uint8_t relayer2, uint32_t id, NodeNum sender, bool *r1Result, bool *r2Result,
|
||||
bool *r2WasSole)
|
||||
{
|
||||
*r1Result = false;
|
||||
*r2Result = false;
|
||||
if (r2WasSole)
|
||||
*r2WasSole = false;
|
||||
|
||||
if (!initOk()) {
|
||||
LOG_ERROR("PacketHistory - checkRelayers: NOT INITIALIZED!");
|
||||
return;
|
||||
}
|
||||
|
||||
const PacketRecord *found = find(sender, id);
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
if (relayer1 != 0)
|
||||
*r1Result = wasRelayer(relayer1, *found);
|
||||
if (relayer2 != 0)
|
||||
*r2Result = wasRelayer(relayer2, *found, r2WasSole);
|
||||
}
|
||||
|
||||
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
|
||||
void PacketHistory::removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender)
|
||||
{
|
||||
|
||||
@@ -28,22 +28,6 @@ class PacketHistory
|
||||
0; // Can be set in constructor, no need to recompile. Used to allocate memory for mx_recentPackets.
|
||||
PacketRecord *recentPackets = NULL; // Simple and fixed in size. Debloat.
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_PKT_HISTORY_HASH
|
||||
// Open-addressing hash table for O(1) lookup in find(), replacing the O(N) linear scan.
|
||||
// Maps (sender, id) -> index into recentPackets[]. Uses linear probing with a load factor <= 0.5.
|
||||
// The load factor invariant holds permanently: hashCapacity = 2 * nextPowerOf2(recentPacketsCapacity),
|
||||
// and at most recentPacketsCapacity entries can ever be live (one per recentPackets[] slot).
|
||||
static constexpr uint16_t HASH_EMPTY = 0xFFFF;
|
||||
uint16_t *hashIndex = NULL;
|
||||
uint32_t hashCapacity = 0; // Always a power of 2
|
||||
uint32_t hashMask = 0; // hashCapacity - 1, for fast modular indexing
|
||||
|
||||
uint32_t hashSlot(NodeNum sender, PacketId id) const;
|
||||
void hashInsert(NodeNum sender, PacketId id, uint16_t slotIdx);
|
||||
void hashRemove(NodeNum sender, PacketId id);
|
||||
void hashRebuild();
|
||||
#endif
|
||||
|
||||
/** Find a packet record in history.
|
||||
* @param sender NodeNum
|
||||
* @param id PacketId
|
||||
@@ -86,16 +70,6 @@ class PacketHistory
|
||||
* @return true if node was indeed a relayer, false if not */
|
||||
bool wasRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender, bool *wasSole = nullptr);
|
||||
|
||||
/**
|
||||
* Check two relayers against the same packet record with a single lookup.
|
||||
* Avoids redundant find() calls when checking multiple relayers for the same (id, sender) pair.
|
||||
* @param r1Result set to true if relayer1 was a relayer
|
||||
* @param r2Result set to true if relayer2 was a relayer
|
||||
* @param r2WasSole if not nullptr, set to true if relayer2 was the sole relayer
|
||||
*/
|
||||
void checkRelayers(uint8_t relayer1, uint8_t relayer2, uint32_t id, NodeNum sender, bool *r1Result, bool *r2Result,
|
||||
bool *r2WasSole = nullptr);
|
||||
|
||||
// Remove a relayer from the list of relayers of a packet in the history given an ID and sender
|
||||
void removeRelayer(const uint8_t relayer, const uint32_t id, const NodeNum sender);
|
||||
|
||||
|
||||
@@ -470,13 +470,8 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_traffic_management_tag;
|
||||
fromRadioScratch.moduleConfig.payload_variant.traffic_management = moduleConfig.traffic_management;
|
||||
break;
|
||||
case meshtastic_ModuleConfig_tak_tag:
|
||||
LOG_DEBUG("Send module config: tak");
|
||||
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_tak_tag;
|
||||
fromRadioScratch.moduleConfig.payload_variant.tak = moduleConfig.tak;
|
||||
break;
|
||||
default:
|
||||
LOG_DEBUG("Unhandled module config type %d", config_state);
|
||||
LOG_ERROR("Unknown module config type %d", config_state);
|
||||
}
|
||||
|
||||
config_state++;
|
||||
|
||||
@@ -46,16 +46,6 @@ RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE c
|
||||
#endif
|
||||
}
|
||||
|
||||
RadioLibInterface::~RadioLibInterface()
|
||||
{
|
||||
// If the static `instance` pointer still references us, clear it.
|
||||
// A later successful init() may have replaced `instance` with a newer
|
||||
// interface — don't clobber that case.
|
||||
if (instance == this) {
|
||||
instance = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// ESP32 doesn't use that flag
|
||||
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
|
||||
|
||||
@@ -136,13 +136,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
||||
RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
|
||||
RADIOLIB_PIN_TYPE busy, PhysicalLayer *iface = NULL);
|
||||
|
||||
/**
|
||||
* Clear the static `instance` pointer if it still points at us, so callers
|
||||
* that check `RadioLibInterface::instance != nullptr` don't dereference a
|
||||
* freed object after a failed init() + unique_ptr reset.
|
||||
*/
|
||||
virtual ~RadioLibInterface();
|
||||
|
||||
virtual ErrorCode send(meshtastic_MeshPacket *p) override;
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -499,9 +499,9 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
||||
meshtastic_Data decodedtmp;
|
||||
memset(&decodedtmp, 0, sizeof(decodedtmp));
|
||||
if (!pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp)) {
|
||||
LOG_DEBUG("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)", p->id);
|
||||
LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id);
|
||||
} else if (decodedtmp.portnum == meshtastic_PortNum_UNKNOWN_APP) {
|
||||
LOG_DEBUG("Invalid portnum (bad psk?)");
|
||||
LOG_ERROR("Invalid portnum (bad psk?)!");
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
} else if (!owner.is_licensed && isToUs(p) && decodedtmp.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) {
|
||||
LOG_WARN("Rejecting legacy DM");
|
||||
|
||||
@@ -455,15 +455,6 @@ template <typename T> void SX126xInterface<T>::resetAGC()
|
||||
// RX boosted gain mode
|
||||
lora.setRxBoostedGainMode(config.lora.sx126x_rx_boosted_gain);
|
||||
|
||||
// Re-apply the undocumented 0x8B5 RX sensitivity patch that was set in init().
|
||||
// The CALIBRATE_ALL (0x7F) command above clears bit 0 of register 0x8B5, which
|
||||
// silently removes the RX sensitivity improvement introduced in #9571 / #9777.
|
||||
// Without this re-apply, every SX1262 node loses its RX boost ~60s after boot
|
||||
// and never recovers until reboot. See empirical evidence in the PR description.
|
||||
if (module.SPIsetRegValue(0x8B5, 0x01, 0, 0) != RADIOLIB_ERR_NONE) {
|
||||
LOG_WARN("SX126x resetAGC: failed to re-apply 0x8B5 RX sensitivity patch");
|
||||
}
|
||||
|
||||
// 6. Resume receiving
|
||||
startReceive();
|
||||
}
|
||||
|
||||
@@ -81,9 +81,7 @@ meshtastic_UserLite TypeConversions::ConvertToUserLite(meshtastic_User user)
|
||||
meshtastic_UserLite lite = meshtastic_UserLite_init_default;
|
||||
|
||||
strncpy(lite.long_name, user.long_name, sizeof(lite.long_name));
|
||||
lite.long_name[sizeof(lite.long_name) - 1] = '\0';
|
||||
strncpy(lite.short_name, user.short_name, sizeof(lite.short_name));
|
||||
lite.short_name[sizeof(lite.short_name) - 1] = '\0';
|
||||
lite.hw_model = user.hw_model;
|
||||
lite.role = user.role;
|
||||
lite.is_licensed = user.is_licensed;
|
||||
@@ -101,9 +99,7 @@ meshtastic_User TypeConversions::ConvertToUser(uint32_t nodeNum, meshtastic_User
|
||||
|
||||
snprintf(user.id, sizeof(user.id), "!%08x", nodeNum);
|
||||
strncpy(user.long_name, lite.long_name, sizeof(user.long_name));
|
||||
user.long_name[sizeof(user.long_name) - 1] = '\0';
|
||||
strncpy(user.short_name, lite.short_name, sizeof(user.short_name));
|
||||
user.short_name[sizeof(user.short_name) - 1] = '\0';
|
||||
user.hw_model = lite.hw_model;
|
||||
user.role = lite.role;
|
||||
user.is_licensed = lite.is_licensed;
|
||||
|
||||
@@ -285,11 +285,7 @@ typedef enum _meshtastic_Config_LoRaConfig_RegionCode {
|
||||
/* Nepal 865MHz */
|
||||
meshtastic_Config_LoRaConfig_RegionCode_NP_865 = 25,
|
||||
/* Brazil 902MHz */
|
||||
meshtastic_Config_LoRaConfig_RegionCode_BR_902 = 26,
|
||||
/* ITU Region 1 Amateur Radio 2m band (144-146 MHz) */
|
||||
meshtastic_Config_LoRaConfig_RegionCode_ITU1_2M = 27,
|
||||
/* ITU Region 2 / 3 Amateur Radio 2m band (144-148 MHz) */
|
||||
meshtastic_Config_LoRaConfig_RegionCode_ITU23_2M = 28
|
||||
meshtastic_Config_LoRaConfig_RegionCode_BR_902 = 26
|
||||
} meshtastic_Config_LoRaConfig_RegionCode;
|
||||
|
||||
/* Standard predefined channel settings
|
||||
@@ -706,8 +702,8 @@ extern "C" {
|
||||
#define _meshtastic_Config_DisplayConfig_CompassOrientation_ARRAYSIZE ((meshtastic_Config_DisplayConfig_CompassOrientation)(meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED+1))
|
||||
|
||||
#define _meshtastic_Config_LoRaConfig_RegionCode_MIN meshtastic_Config_LoRaConfig_RegionCode_UNSET
|
||||
#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_ITU23_2M
|
||||
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_ITU23_2M+1))
|
||||
#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_BR_902
|
||||
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_BR_902+1))
|
||||
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST
|
||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO
|
||||
|
||||
@@ -11,24 +11,6 @@ template <class T> constexpr const T &clamp(const T &v, const T &lo, const T &hi
|
||||
return (v < lo) ? lo : (hi < v) ? hi : v;
|
||||
}
|
||||
|
||||
/// Return the smallest power of 2 >= n (undefined for n > 2^31)
|
||||
static inline uint32_t nextPowerOf2(uint32_t n)
|
||||
{
|
||||
if (n <= 1)
|
||||
return 1;
|
||||
#if defined(__GNUC__)
|
||||
return 1U << (32 - __builtin_clz(n - 1));
|
||||
#else
|
||||
n--;
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
n |= n >> 16;
|
||||
return n + 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAS_SCREEN
|
||||
#define IF_SCREEN(X) \
|
||||
if (screen) { \
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
#include "mesh/generated/meshtastic/rtttl.pb.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#if HAS_LIBNOTIFY
|
||||
#include <libnotify/notify.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAS_RGB_LED)
|
||||
#include "AmbientLightingThread.h"
|
||||
uint8_t red = 0;
|
||||
@@ -408,6 +412,9 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
|
||||
if (genericShouldAlert) {
|
||||
LOG_INFO("externalNotificationModule - Generic alert");
|
||||
setExternalState(0, true);
|
||||
#if HAS_LIBNOTIFY
|
||||
portduinoNotify(mp);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (vibraShouldAlert) {
|
||||
@@ -521,3 +528,40 @@ int ExternalNotificationModule::handleInputEvent(const InputEvent *event)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HAS_LIBNOTIFY
|
||||
void ExternalNotificationModule::portduinoNotify(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
std::string senderName;
|
||||
const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
|
||||
if (sender && sender->has_user) {
|
||||
if (sender->user.long_name[0] != '\0') {
|
||||
senderName = sender->user.long_name;
|
||||
} else {
|
||||
senderName = sender->user.short_name;
|
||||
}
|
||||
} else {
|
||||
senderName = std::to_string(mp.from);
|
||||
}
|
||||
std::string notificationSummary = "From: " + senderName;
|
||||
std::string notificationBody = std::string((char *)mp.decoded.payload.bytes, mp.decoded.payload.size);
|
||||
|
||||
if (!notify_is_initted()) {
|
||||
if (!notify_init("Meshtasticd")) {
|
||||
LOG_WARN("Failed to initialize libnotify");
|
||||
return;
|
||||
}
|
||||
}
|
||||
NotifyNotification *notification =
|
||||
notify_notification_new(notificationSummary.c_str(), notificationBody.c_str(), "org.meshtastic.meshtasticd");
|
||||
if (notification) {
|
||||
GError *error = nullptr;
|
||||
if (!notify_notification_show(notification, &error)) {
|
||||
LOG_WARN("Failed to show notification: %s", error ? error->message : "unknown error");
|
||||
if (error)
|
||||
g_error_free(error);
|
||||
}
|
||||
g_object_unref(G_OBJECT(notification));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -77,6 +77,10 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
|
||||
virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
|
||||
meshtastic_AdminMessage *request,
|
||||
meshtastic_AdminMessage *response) override;
|
||||
|
||||
#if HAS_LIBNOTIFY
|
||||
void portduinoNotify(const meshtastic_MeshPacket &mp);
|
||||
#endif
|
||||
};
|
||||
|
||||
extern ExternalNotificationModule *externalNotificationModule;
|
||||
@@ -492,24 +492,15 @@ void PositionModule::sendLostAndFoundText()
|
||||
{
|
||||
meshtastic_MeshPacket *p = allocDataPacket();
|
||||
p->to = NODENUM_BROADCAST;
|
||||
char message[128];
|
||||
int written = snprintf(message, sizeof(message), "🚨I'm lost! Lat / Lon: %f, %f\a", (lastGpsLatitude * 1e-7),
|
||||
(lastGpsLongitude * 1e-7));
|
||||
char *message = new char[60];
|
||||
sprintf(message, "🚨I'm lost! Lat / Lon: %f, %f\a", (lastGpsLatitude * 1e-7), (lastGpsLongitude * 1e-7));
|
||||
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
|
||||
p->want_ack = false;
|
||||
if (written < 0) {
|
||||
// snprintf encoding error — send an empty payload rather than uninitialized bytes.
|
||||
p->decoded.payload.size = 0;
|
||||
} else {
|
||||
// Clamp to buffer capacity (snprintf returns "would-have-written" which can exceed the buffer).
|
||||
const size_t msg_len = std::min(static_cast<size_t>(written), sizeof(message) - 1);
|
||||
p->decoded.payload.size = msg_len;
|
||||
if (msg_len > 0) {
|
||||
memcpy(p->decoded.payload.bytes, message, msg_len);
|
||||
}
|
||||
}
|
||||
p->decoded.payload.size = strlen(message);
|
||||
memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size);
|
||||
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
delete[] message;
|
||||
}
|
||||
|
||||
// Helper: return imprecise (truncated + centered) lat/lon as int32 using current precision
|
||||
@@ -589,4 +580,4 @@ void PositionModule::handleNewPosition()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -206,7 +206,7 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
|
||||
this->packetHistory[this->packetHistoryTotalCount].hop_limit = mp.hop_limit;
|
||||
this->packetHistory[this->packetHistoryTotalCount].via_mqtt = mp.via_mqtt;
|
||||
this->packetHistory[this->packetHistoryTotalCount].transport_mechanism = mp.transport_mechanism;
|
||||
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, p.payload.size);
|
||||
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
|
||||
|
||||
this->packetHistoryTotalCount++;
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
||||
/**
|
||||
* Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies)
|
||||
*/
|
||||
virtual void onNowHasData(uint32_t fromRadioNum) override
|
||||
virtual void onNowHasData(uint32_t fromRadioNum)
|
||||
{
|
||||
PhoneAPI::onNowHasData(fromRadioNum);
|
||||
|
||||
@@ -350,7 +350,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
||||
}
|
||||
|
||||
/// Check the current underlying physical link to see if the client is currently connected
|
||||
virtual bool checkIsConnected() override { return bleServer && bleServer->getConnectedCount() > 0; }
|
||||
virtual bool checkIsConnected() { return bleServer && bleServer->getConnectedCount() > 0; }
|
||||
|
||||
void requestHighThroughputConnection(uint16_t conn_handle)
|
||||
{
|
||||
@@ -412,9 +412,9 @@ static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
|
||||
class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
|
||||
{
|
||||
#ifdef NIMBLE_TWO
|
||||
virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override
|
||||
virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
|
||||
#else
|
||||
virtual void onWrite(NimBLECharacteristic *pCharacteristic) override
|
||||
virtual void onWrite(NimBLECharacteristic *pCharacteristic)
|
||||
|
||||
#endif
|
||||
{
|
||||
@@ -464,9 +464,9 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
|
||||
class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
|
||||
{
|
||||
#ifdef NIMBLE_TWO
|
||||
virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) override
|
||||
virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
|
||||
#else
|
||||
virtual void onRead(NimBLECharacteristic *pCharacteristic) override
|
||||
virtual void onRead(NimBLECharacteristic *pCharacteristic)
|
||||
#endif
|
||||
{
|
||||
// CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
|
||||
@@ -582,9 +582,9 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
|
||||
private:
|
||||
NimbleBluetooth *ble;
|
||||
|
||||
virtual uint32_t onPassKeyDisplay() override
|
||||
virtual uint32_t onPassKeyDisplay()
|
||||
#else
|
||||
virtual uint32_t onPassKeyRequest() override
|
||||
virtual uint32_t onPassKeyRequest()
|
||||
#endif
|
||||
{
|
||||
uint32_t passkey = config.bluetooth.fixed_pin;
|
||||
@@ -635,9 +635,9 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
|
||||
}
|
||||
|
||||
#ifdef NIMBLE_TWO
|
||||
virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo) override
|
||||
virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo)
|
||||
#else
|
||||
virtual void onAuthenticationComplete(ble_gap_conn_desc *desc) override
|
||||
virtual void onAuthenticationComplete(ble_gap_conn_desc *desc)
|
||||
#endif
|
||||
{
|
||||
LOG_INFO("BLE authentication complete");
|
||||
@@ -655,7 +655,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
|
||||
}
|
||||
|
||||
#ifdef NIMBLE_TWO
|
||||
virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override
|
||||
virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo)
|
||||
{
|
||||
LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str());
|
||||
|
||||
@@ -683,11 +683,11 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
|
||||
#endif
|
||||
|
||||
#ifdef NIMBLE_TWO
|
||||
virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) override
|
||||
virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason)
|
||||
{
|
||||
LOG_INFO("BLE disconnect reason: %d", reason);
|
||||
#else
|
||||
virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc) override
|
||||
virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc)
|
||||
{
|
||||
LOG_INFO("BLE disconnect");
|
||||
#endif
|
||||
@@ -989,4 +989,11 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length)
|
||||
#endif
|
||||
}
|
||||
|
||||
void clearNVS()
|
||||
{
|
||||
NimBLEDevice::deleteAllBonds();
|
||||
#ifdef ARCH_ESP32
|
||||
ESP.restart();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -24,4 +24,5 @@ class NimbleBluetooth : BluetoothApi
|
||||
#endif
|
||||
};
|
||||
|
||||
void setBluetoothEnable(bool enable);
|
||||
void setBluetoothEnable(bool enable);
|
||||
void clearNVS();
|
||||
@@ -1,144 +0,0 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#ifdef T5_S3_EPAPER_PRO
|
||||
|
||||
#include "Observer.h"
|
||||
#include "TouchDrvGT911.hpp"
|
||||
#include "Wire.h"
|
||||
#include "input/InputBroker.h"
|
||||
#include "input/TouchScreenImpl1.h"
|
||||
#include "sleep.h"
|
||||
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
#include "graphics/niche/InkHUD/InkHUD.h"
|
||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||
|
||||
// Bridges touch events from TouchScreenImpl1 directly into InkHUD,
|
||||
// bypassing the InputBroker (which is excluded in InkHUD builds).
|
||||
// Routing mirrors the mini-epaper-s3 two-way rocker pattern:
|
||||
// - Nav left/right: prevApplet/nextApplet when idle, navUp/Down when a system applet has focus (e.g. menu)
|
||||
// - Nav up/down: navUp/navDown always (menu scroll)
|
||||
// - Tap: shortpress (cycle applets / confirm in menu)
|
||||
// - Long press: longpress (open menu / back)
|
||||
class TouchInkHUDBridge : public Observer<const InputEvent *>
|
||||
{
|
||||
int onNotify(const InputEvent *e) override
|
||||
{
|
||||
auto *inkhud = NicheGraphics::InkHUD::InkHUD::getInstance();
|
||||
|
||||
// Keep alignment in sync with the current rotation so that visual-frame gestures
|
||||
// always pass through nav functions without remapping: (rotation + alignment) % 4 == 0.
|
||||
inkhud->persistence->settings.joystick.alignment = (4 - inkhud->persistence->settings.rotation) % 4;
|
||||
|
||||
// Check whether a system applet (e.g. menu) is currently handling input
|
||||
bool systemHandlingInput = false;
|
||||
for (NicheGraphics::InkHUD::SystemApplet *sa : inkhud->systemApplets) {
|
||||
if (sa->handleInput) {
|
||||
systemHandlingInput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (e->inputEvent) {
|
||||
case INPUT_BROKER_USER_PRESS:
|
||||
inkhud->shortpress();
|
||||
break;
|
||||
case INPUT_BROKER_SELECT:
|
||||
inkhud->longpress();
|
||||
break;
|
||||
case INPUT_BROKER_LEFT:
|
||||
if (systemHandlingInput)
|
||||
inkhud->navUp();
|
||||
else
|
||||
inkhud->prevApplet();
|
||||
break;
|
||||
case INPUT_BROKER_RIGHT:
|
||||
if (systemHandlingInput)
|
||||
inkhud->navDown();
|
||||
else
|
||||
inkhud->nextApplet();
|
||||
break;
|
||||
case INPUT_BROKER_UP:
|
||||
inkhud->navUp();
|
||||
break;
|
||||
case INPUT_BROKER_DOWN:
|
||||
inkhud->navDown();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
static TouchInkHUDBridge touchBridge;
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
|
||||
TouchDrvGT911 touch;
|
||||
|
||||
// Commands the GT911 into standby before the Wire bus is torn down.
|
||||
// notifyDeepSleep fires before Wire.end() in doDeepSleep(), so I2C is still available here.
|
||||
struct TouchDeepSleepObserver {
|
||||
int onDeepSleep(void *)
|
||||
{
|
||||
touch.sleep();
|
||||
return 0;
|
||||
}
|
||||
CallbackObserver<TouchDeepSleepObserver, void *> observer{this, &TouchDeepSleepObserver::onDeepSleep};
|
||||
} static touchDeepSleepObserver;
|
||||
|
||||
bool readTouch(int16_t *x, int16_t *y)
|
||||
{
|
||||
if (!digitalRead(GT911_PIN_INT)) {
|
||||
int16_t raw_x;
|
||||
int16_t raw_y;
|
||||
if (touch.getPoint(&raw_x, &raw_y)) {
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
// Transform raw GT911 axes to visual-frame coordinates for the current display rotation.
|
||||
// rotation=3 is the physical identity (device's default orientation).
|
||||
switch (NicheGraphics::InkHUD::InkHUD::getInstance()->persistence->settings.rotation) {
|
||||
default:
|
||||
case 3:
|
||||
*x = raw_x;
|
||||
*y = raw_y;
|
||||
break; // identity
|
||||
case 2:
|
||||
*x = (EPD_WIDTH - 1) - raw_y;
|
||||
*y = raw_x;
|
||||
break; // 90° CW tilt
|
||||
case 1:
|
||||
*x = (EPD_HEIGHT - 1) - raw_x;
|
||||
*y = (EPD_WIDTH - 1) - raw_y;
|
||||
break; // 180° flip
|
||||
case 0:
|
||||
*x = raw_y;
|
||||
*y = (EPD_HEIGHT - 1) - raw_x;
|
||||
break; // 90° CCW tilt
|
||||
}
|
||||
#else
|
||||
*x = raw_x;
|
||||
*y = raw_y;
|
||||
#endif
|
||||
LOG_DEBUG("touched(%d/%d)", *x, *y);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// T5-S3-ePaper Pro specific (late-) init
|
||||
void lateInitVariant(void)
|
||||
{
|
||||
touch.setPins(GT911_PIN_RST, GT911_PIN_INT);
|
||||
if (touch.begin(Wire, GT911_SLAVE_ADDRESS_H, GT911_PIN_SDA, GT911_PIN_SCL)) {
|
||||
touchDeepSleepObserver.observer.observe(¬ifyDeepSleep);
|
||||
touchScreenImpl1 = new TouchScreenImpl1(EPD_WIDTH, EPD_HEIGHT, readTouch);
|
||||
touchScreenImpl1->init();
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
touchBridge.observe(touchScreenImpl1);
|
||||
#endif
|
||||
} else {
|
||||
LOG_ERROR("Failed to find touch controller!");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+1
-18
@@ -86,7 +86,7 @@ extern RAK9154Sensor rak9154Sensor;
|
||||
extern XPowersLibInterface *PMU;
|
||||
#endif
|
||||
|
||||
class Power : public concurrency::OSThread
|
||||
class Power : private concurrency::OSThread
|
||||
{
|
||||
|
||||
public:
|
||||
@@ -100,15 +100,6 @@ class Power : public concurrency::OSThread
|
||||
virtual int32_t runOnce() override;
|
||||
void setStatusHandler(meshtastic::PowerStatus *handler) { statusHandler = handler; }
|
||||
const uint16_t OCV[11] = {OCV_ARRAY};
|
||||
bool isLowBattery() { return low_voltage_counter >= 10; };
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
int beforeLightSleep(void *unused);
|
||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
||||
#endif
|
||||
|
||||
void attachPowerInterrupts();
|
||||
void detachPowerInterrupts();
|
||||
|
||||
protected:
|
||||
meshtastic::PowerStatus *statusHandler;
|
||||
@@ -134,14 +125,6 @@ class Power : public concurrency::OSThread
|
||||
// open circuit voltage lookup table
|
||||
uint8_t low_voltage_counter;
|
||||
uint32_t lastLogTime = 0;
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Get notified when lightsleep begins and ends
|
||||
CallbackObserver<Power, void *> lsObserver = CallbackObserver<Power, void *>(this, &Power::beforeLightSleep);
|
||||
CallbackObserver<Power, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<Power, esp_sleep_wakeup_cause_t>(this, &Power::afterLightSleep);
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_HEAP
|
||||
uint32_t lastheap;
|
||||
#endif
|
||||
|
||||
+6
-5
@@ -497,12 +497,13 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
|
||||
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
|
||||
notifyLightSleepEnd.notifyObservers(cause); // Button interrupts are reattached here
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
if (cause == ESP_SLEEP_WAKEUP_GPIO) {
|
||||
LOG_INFO("Exit light sleep gpio");
|
||||
// If we woke because of a GPIO, it's possible power needs to run to handle.
|
||||
power->setIntervalFromNow(0);
|
||||
runASAP = true;
|
||||
} else {
|
||||
LOG_INFO("Exit light sleep gpio: btn=%d",
|
||||
!digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN));
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
LOG_INFO("Exit light sleep cause: %d", cause);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,834 +0,0 @@
|
||||
/*
|
||||
* Unit tests for PacketHistory — the packet deduplication engine
|
||||
* used by the mesh routing stack.
|
||||
*
|
||||
* PacketHistory maintains a fixed-size array of PacketRecords with an
|
||||
* optional hash table for O(1) lookup. It tracks which nodes relayed
|
||||
* each packet, supports LRU-style eviction, and detects fallback-to-
|
||||
* flooding and hop-limit upgrades.
|
||||
*/
|
||||
|
||||
#include "PacketHistory.h"
|
||||
|
||||
#include "TestUtil.h"
|
||||
#include <unity.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
static constexpr uint32_t OUR_NODE_NUM = 0xDEAD1234;
|
||||
static constexpr uint8_t OUR_RELAY_ID = 0x34; // getLastByteOfNodeNum(OUR_NODE_NUM)
|
||||
static constexpr uint32_t SMALL_CAPACITY = 8;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Per-test state
|
||||
// ---------------------------------------------------------------------------
|
||||
static PacketHistory *ph = nullptr;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
static meshtastic_MeshPacket makePacket(uint32_t from, uint32_t id, uint8_t hop_limit = 3,
|
||||
uint8_t next_hop = NO_NEXT_HOP_PREFERENCE, uint8_t relay_node = 0)
|
||||
{
|
||||
meshtastic_MeshPacket p = meshtastic_MeshPacket_init_zero;
|
||||
p.from = from;
|
||||
p.id = id;
|
||||
p.hop_limit = hop_limit;
|
||||
p.next_hop = next_hop;
|
||||
p.relay_node = relay_node;
|
||||
return p;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// setUp / tearDown — called before and after every test
|
||||
// ---------------------------------------------------------------------------
|
||||
void setUp(void)
|
||||
{
|
||||
myNodeInfo.my_node_num = OUR_NODE_NUM;
|
||||
ph = new PacketHistory(SMALL_CAPACITY);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
delete ph;
|
||||
ph = nullptr;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Group 1 — Initialization
|
||||
// ===========================================================================
|
||||
|
||||
void test_init_valid_size(void)
|
||||
{
|
||||
PacketHistory h(8);
|
||||
TEST_ASSERT_TRUE(h.initOk());
|
||||
}
|
||||
|
||||
void test_init_minimum_size(void)
|
||||
{
|
||||
PacketHistory h(4);
|
||||
TEST_ASSERT_TRUE(h.initOk());
|
||||
}
|
||||
|
||||
void test_init_too_small_falls_back(void)
|
||||
{
|
||||
// Sizes < 4 or > PACKETHISTORY_MAX are clamped to PACKETHISTORY_MAX inside the constructor
|
||||
PacketHistory h(2);
|
||||
TEST_ASSERT_TRUE(h.initOk());
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Group 2 — Basic Deduplication
|
||||
// ===========================================================================
|
||||
|
||||
void test_first_packet_not_seen(void)
|
||||
{
|
||||
auto p = makePacket(0x1111, 100);
|
||||
TEST_ASSERT_FALSE(ph->wasSeenRecently(&p));
|
||||
}
|
||||
|
||||
void test_same_packet_seen_twice(void)
|
||||
{
|
||||
auto p = makePacket(0x1111, 100);
|
||||
TEST_ASSERT_FALSE(ph->wasSeenRecently(&p)); // first time
|
||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p)); // duplicate
|
||||
}
|
||||
|
||||
void test_different_id_not_confused(void)
|
||||
{
|
||||
auto p1 = makePacket(0x1111, 100);
|
||||
auto p2 = makePacket(0x1111, 200);
|
||||
ph->wasSeenRecently(&p1);
|
||||
TEST_ASSERT_FALSE(ph->wasSeenRecently(&p2));
|
||||
}
|
||||
|
||||
void test_different_sender_not_confused(void)
|
||||
{
|
||||
auto p1 = makePacket(0x1111, 100);
|
||||
auto p2 = makePacket(0x2222, 100);
|
||||
ph->wasSeenRecently(&p1);
|
||||
TEST_ASSERT_FALSE(ph->wasSeenRecently(&p2));
|
||||
}
|
||||
|
||||
void test_withUpdate_false_no_insert(void)
|
||||
{
|
||||
auto p = makePacket(0x1111, 100);
|
||||
// First call with withUpdate=false: should not store
|
||||
TEST_ASSERT_FALSE(ph->wasSeenRecently(&p, /*withUpdate=*/false));
|
||||
// Second call with withUpdate=true: still not found because first didn't store
|
||||
TEST_ASSERT_FALSE(ph->wasSeenRecently(&p, /*withUpdate=*/true));
|
||||
}
|
||||
|
||||
void test_withUpdate_true_inserts(void)
|
||||
{
|
||||
auto p = makePacket(0x1111, 100);
|
||||
TEST_ASSERT_FALSE(ph->wasSeenRecently(&p, /*withUpdate=*/true));
|
||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p, /*withUpdate=*/false)); // found without inserting again
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Group 3 — LRU Eviction
|
||||
// ===========================================================================
|
||||
|
||||
void test_fill_capacity_all_found(void)
|
||||
{
|
||||
for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
ph->wasSeenRecently(&p);
|
||||
}
|
||||
// All 8 should be found
|
||||
for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p, false));
|
||||
}
|
||||
}
|
||||
|
||||
void test_eviction_oldest_replaced(void)
|
||||
{
|
||||
// Fill all 8 slots
|
||||
for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
ph->wasSeenRecently(&p);
|
||||
}
|
||||
|
||||
// Advance time so the eviction logic can distinguish "oldest" from "newest".
|
||||
// insert() uses (now_millis - rxTimeMsec) > OldtrxTimeMsec with strict >, so
|
||||
// entries with identical timestamps all have age 0 and none gets selected.
|
||||
delay(1);
|
||||
|
||||
// Insert a 9th packet — should evict the oldest
|
||||
auto p9 = makePacket(0xAAAA, 9);
|
||||
ph->wasSeenRecently(&p9);
|
||||
|
||||
// The 9th should be found
|
||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p9, false));
|
||||
|
||||
// At least one of the originals should have been evicted
|
||||
int evicted = 0;
|
||||
for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
if (!ph->wasSeenRecently(&p, false))
|
||||
evicted++;
|
||||
}
|
||||
TEST_ASSERT_TRUE(evicted > 0);
|
||||
}
|
||||
|
||||
void test_matching_slot_reused(void)
|
||||
{
|
||||
// Insert packet, then re-insert same (sender, id) — should reuse slot, not evict others
|
||||
auto p1 = makePacket(0xAAAA, 1);
|
||||
auto p2 = makePacket(0xBBBB, 2);
|
||||
ph->wasSeenRecently(&p1);
|
||||
ph->wasSeenRecently(&p2);
|
||||
|
||||
// Re-observe p1 (triggers merge path)
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Both should still be present
|
||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p1, false));
|
||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p2, false));
|
||||
}
|
||||
|
||||
void test_free_slot_preferred(void)
|
||||
{
|
||||
// Insert 4 packets into capacity-8 history — next insert should use a free slot, not evict
|
||||
for (uint32_t i = 1; i <= 4; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
ph->wasSeenRecently(&p);
|
||||
}
|
||||
auto p5 = makePacket(0xAAAA, 5);
|
||||
ph->wasSeenRecently(&p5);
|
||||
|
||||
// All 5 should be present (no eviction needed)
|
||||
for (uint32_t i = 1; i <= 5; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p, false));
|
||||
}
|
||||
}
|
||||
|
||||
void test_evict_all_old_packets(void)
|
||||
{
|
||||
// Fill with packets 1..8
|
||||
for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
ph->wasSeenRecently(&p);
|
||||
}
|
||||
|
||||
// Advance time so the replacement batch can evict the originals
|
||||
delay(1);
|
||||
|
||||
// Replace all with packets 101..108
|
||||
for (uint32_t i = 101; i <= 100 + SMALL_CAPACITY; i++) {
|
||||
auto p = makePacket(0xBBBB, i);
|
||||
ph->wasSeenRecently(&p);
|
||||
}
|
||||
// None of the originals should be found
|
||||
for (uint32_t i = 1; i <= SMALL_CAPACITY; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
TEST_ASSERT_FALSE(ph->wasSeenRecently(&p, false));
|
||||
}
|
||||
// All new ones should be found
|
||||
for (uint32_t i = 101; i <= 100 + SMALL_CAPACITY; i++) {
|
||||
auto p = makePacket(0xBBBB, i);
|
||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p, false));
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Group 4 — Relayer Tracking
|
||||
// ===========================================================================
|
||||
|
||||
void test_wasRelayer_true(void)
|
||||
{
|
||||
// Non-us relay_nodes only enter relayed_by[] through the "heard-back" merge path:
|
||||
// we must have relayed first, then observe the packet return at hop_limit-1.
|
||||
auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Heard-back from 0xCC at hop_limit=2 (ourTxHopLimit-1) triggers the merge
|
||||
auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xCC);
|
||||
ph->wasSeenRecently(&p2);
|
||||
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(0xCC, 100, 0x1111));
|
||||
}
|
||||
|
||||
void test_wasRelayer_false(void)
|
||||
{
|
||||
auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, 0xAA);
|
||||
ph->wasSeenRecently(&p);
|
||||
// 0xCC was never a relayer
|
||||
TEST_ASSERT_FALSE(ph->wasRelayer(0xCC, 100, 0x1111));
|
||||
}
|
||||
|
||||
void test_wasRelayer_zero_returns_false(void)
|
||||
{
|
||||
auto p = makePacket(0x1111, 100);
|
||||
ph->wasSeenRecently(&p);
|
||||
TEST_ASSERT_FALSE(ph->wasRelayer(0, 100, 0x1111));
|
||||
}
|
||||
|
||||
void test_wasRelayer_not_found(void)
|
||||
{
|
||||
// Packet not in history at all
|
||||
TEST_ASSERT_FALSE(ph->wasRelayer(0xAA, 999, 0x9999));
|
||||
}
|
||||
|
||||
void test_wasRelayer_wasSole_true(void)
|
||||
{
|
||||
// relay_node = ourRelayID → relayed_by[0] = ourRelayID
|
||||
auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p);
|
||||
|
||||
bool wasSole = false;
|
||||
bool result = ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111, &wasSole);
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_TRUE(wasSole);
|
||||
}
|
||||
|
||||
void test_wasRelayer_wasSole_false(void)
|
||||
{
|
||||
// First observation: we relay
|
||||
auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Second observation: different relayer adds to record
|
||||
auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
|
||||
ph->wasSeenRecently(&p2);
|
||||
|
||||
bool wasSole = true;
|
||||
bool result = ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111, &wasSole);
|
||||
TEST_ASSERT_TRUE(result);
|
||||
TEST_ASSERT_FALSE(wasSole);
|
||||
}
|
||||
|
||||
void test_wasRelayer_all_six_slots(void)
|
||||
{
|
||||
// First observation: we relay with hop_limit=3 (fills slot 0, ourTxHopLimit=3)
|
||||
auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p);
|
||||
|
||||
// Each heard-back must satisfy: hop_limit == ourTxHopLimit OR ourTxHopLimit-1.
|
||||
// Using hop_limit=2 (ourTxHopLimit-1) for all, which triggers the heard-back
|
||||
// merge path each time. Each new relay_node pushes to slot 0 and shifts existing
|
||||
// relayers right, eventually filling all NUM_RELAYERS(6) slots.
|
||||
uint8_t relayers[] = {0x11, 0x22, 0x33, 0x44, 0x55};
|
||||
for (int i = 0; i < 5; i++) {
|
||||
auto pn = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, relayers[i]);
|
||||
ph->wasSeenRecently(&pn);
|
||||
}
|
||||
|
||||
// All 6 should be detected
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
|
||||
for (int i = 0; i < 5; i++) {
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(relayers[i], 100, 0x1111));
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Group 5 — removeRelayer
|
||||
// ===========================================================================
|
||||
|
||||
void test_removeRelayer_removes(void)
|
||||
{
|
||||
auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p1);
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
|
||||
|
||||
ph->removeRelayer(OUR_RELAY_ID, 100, 0x1111);
|
||||
TEST_ASSERT_FALSE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
|
||||
}
|
||||
|
||||
void test_removeRelayer_compacts(void)
|
||||
{
|
||||
// We relay first
|
||||
auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p1);
|
||||
// Second relayer
|
||||
auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
|
||||
ph->wasSeenRecently(&p2);
|
||||
|
||||
// Remove us, 0xBB should still be found
|
||||
ph->removeRelayer(OUR_RELAY_ID, 100, 0x1111);
|
||||
TEST_ASSERT_FALSE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(0xBB, 100, 0x1111));
|
||||
}
|
||||
|
||||
void test_removeRelayer_nonexistent_safe(void)
|
||||
{
|
||||
auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p);
|
||||
// Removing a relayer that doesn't exist should not crash
|
||||
ph->removeRelayer(0xFF, 100, 0x1111);
|
||||
// Original should still be there
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
|
||||
}
|
||||
|
||||
void test_removeRelayer_packet_not_found_safe(void)
|
||||
{
|
||||
// Packet not in history — should not crash
|
||||
ph->removeRelayer(0xAA, 999, 0x9999);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Group 6 — checkRelayers
|
||||
// ===========================================================================
|
||||
|
||||
void test_checkRelayers_both_found(void)
|
||||
{
|
||||
// We relay first
|
||||
auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p1);
|
||||
// Second relayer
|
||||
auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
|
||||
ph->wasSeenRecently(&p2);
|
||||
|
||||
bool r1 = false, r2 = false;
|
||||
ph->checkRelayers(OUR_RELAY_ID, 0xBB, 100, 0x1111, &r1, &r2);
|
||||
TEST_ASSERT_TRUE(r1);
|
||||
TEST_ASSERT_TRUE(r2);
|
||||
}
|
||||
|
||||
void test_checkRelayers_one_found(void)
|
||||
{
|
||||
auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p);
|
||||
|
||||
bool r1 = false, r2 = false;
|
||||
ph->checkRelayers(OUR_RELAY_ID, 0xCC, 100, 0x1111, &r1, &r2);
|
||||
TEST_ASSERT_TRUE(r1);
|
||||
TEST_ASSERT_FALSE(r2);
|
||||
}
|
||||
|
||||
void test_checkRelayers_r2WasSole(void)
|
||||
{
|
||||
auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p);
|
||||
|
||||
bool r1 = false, r2 = false, r2Sole = false;
|
||||
// relayer1=0xCC (not found), relayer2=OUR_RELAY_ID (sole relayer)
|
||||
ph->checkRelayers(0xCC, OUR_RELAY_ID, 100, 0x1111, &r1, &r2, &r2Sole);
|
||||
TEST_ASSERT_FALSE(r1);
|
||||
TEST_ASSERT_TRUE(r2);
|
||||
TEST_ASSERT_TRUE(r2Sole);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Group 7 — wasSeenRecently Merge Logic
|
||||
// ===========================================================================
|
||||
|
||||
void test_merge_preserves_original_next_hop(void)
|
||||
{
|
||||
// First observation with next_hop=0x55
|
||||
auto p1 = makePacket(0x1111, 100, 3, 0x55, 0xAA);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Re-observation with different next_hop
|
||||
auto p2 = makePacket(0x1111, 100, 2, 0x77, 0xBB);
|
||||
ph->wasSeenRecently(&p2);
|
||||
|
||||
// The stored next_hop should still be 0x55 (the original)
|
||||
// We verify via weWereNextHop: if we set original next_hop to ourRelayID, it should detect it
|
||||
auto p3 = makePacket(0x1111, 200, 3, OUR_RELAY_ID, 0xAA);
|
||||
ph->wasSeenRecently(&p3);
|
||||
auto p4 = makePacket(0x1111, 200, 2, 0x99, 0xBB);
|
||||
bool weWereNextHop = false;
|
||||
ph->wasSeenRecently(&p4, true, nullptr, &weWereNextHop);
|
||||
TEST_ASSERT_TRUE(weWereNextHop);
|
||||
}
|
||||
|
||||
void test_merge_preserves_highest_hop_limit(void)
|
||||
{
|
||||
// First observation with hop_limit=5
|
||||
auto p1 = makePacket(0x1111, 100, 5);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Re-observation with hop_limit=2 (lower)
|
||||
auto p2 = makePacket(0x1111, 100, 2);
|
||||
ph->wasSeenRecently(&p2);
|
||||
|
||||
// Third observation with hop_limit=3 should not trigger upgrade (highest was 5)
|
||||
bool wasUpgraded = true;
|
||||
auto p3 = makePacket(0x1111, 100, 3);
|
||||
ph->wasSeenRecently(&p3, true, nullptr, nullptr, &wasUpgraded);
|
||||
TEST_ASSERT_FALSE(wasUpgraded);
|
||||
}
|
||||
|
||||
void test_merge_no_duplicate_relayers(void)
|
||||
{
|
||||
// Observe with relayer 0xAA (stored via relay_node, but only slot 0 for ourRelayID)
|
||||
// We need to use ourRelayID for the first observation to get it into slot 0
|
||||
auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Re-observe with same relay_node=ourRelayID — should not create duplicates
|
||||
auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p2);
|
||||
|
||||
// ourRelayID should appear exactly once — wasSole should still be true
|
||||
bool wasSole = false;
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111, &wasSole));
|
||||
TEST_ASSERT_TRUE(wasSole);
|
||||
}
|
||||
|
||||
void test_merge_adds_new_relayer(void)
|
||||
{
|
||||
auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
|
||||
ph->wasSeenRecently(&p2);
|
||||
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(0xBB, 100, 0x1111));
|
||||
}
|
||||
|
||||
void test_merge_we_relay_sets_slot_zero(void)
|
||||
{
|
||||
// When relay_node == ourRelayID, relayed_by[0] should be set to ourRelayID
|
||||
auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p);
|
||||
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
|
||||
}
|
||||
|
||||
void test_merge_heard_back_stores_relay_node(void)
|
||||
{
|
||||
// First: we relay (hop_limit=3)
|
||||
auto p1 = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Second: we hear the packet back with hop_limit=2 (one less), from relay_node=0xCC
|
||||
// This triggers the "heard back" logic: weWereRelayer && hop_limit == ourTxHopLimit-1
|
||||
auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xCC);
|
||||
ph->wasSeenRecently(&p2);
|
||||
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(OUR_RELAY_ID, 100, 0x1111));
|
||||
TEST_ASSERT_TRUE(ph->wasRelayer(0xCC, 100, 0x1111));
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Group 8 — Fallback-to-Flooding Detection
|
||||
// ===========================================================================
|
||||
|
||||
void test_fallback_detected(void)
|
||||
{
|
||||
// The fallback condition requires wasRelayer(relay_node) && !wasRelayer(ourRelayID).
|
||||
// Non-us relayers only enter relayed_by[] via the heard-back merge path, which
|
||||
// also stores ourRelayID. So we must removeRelayer(ourRelayID) to satisfy both.
|
||||
//
|
||||
// Scenario: we relay a directed packet, hear it back from 0xAA, then the router
|
||||
// removes us from the relayer list. Later the sender falls back to flooding.
|
||||
|
||||
// Step 1: We relay (directed to next_hop=0x55)
|
||||
auto p1 = makePacket(0x1111, 100, 3, 0x55, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Step 2: Heard-back from 0xAA at hop_limit-1 → stores 0xAA in relayed_by
|
||||
auto p2 = makePacket(0x1111, 100, 2, 0x55, 0xAA);
|
||||
ph->wasSeenRecently(&p2);
|
||||
|
||||
// Step 3: Router removes us from the relayer list
|
||||
ph->removeRelayer(OUR_RELAY_ID, 100, 0x1111);
|
||||
|
||||
// Step 4: Sender falls back to flooding — same packet, NO_NEXT_HOP_PREFERENCE, from 0xAA
|
||||
auto p3 = makePacket(0x1111, 100, 1, NO_NEXT_HOP_PREFERENCE, 0xAA);
|
||||
bool wasFallback = false;
|
||||
ph->wasSeenRecently(&p3, true, &wasFallback);
|
||||
TEST_ASSERT_TRUE(wasFallback);
|
||||
}
|
||||
|
||||
void test_fallback_not_when_we_relayed(void)
|
||||
{
|
||||
// First observation: directed, we relayed it
|
||||
auto p1 = makePacket(0x1111, 100, 3, 0x55, OUR_RELAY_ID);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Second observation: fallback to flooding from same relayer (us)
|
||||
// But since we already relayed, wasFallback should be false
|
||||
auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, OUR_RELAY_ID);
|
||||
bool wasFallback = false;
|
||||
ph->wasSeenRecently(&p2, true, &wasFallback);
|
||||
TEST_ASSERT_FALSE(wasFallback);
|
||||
}
|
||||
|
||||
void test_fallback_not_on_first_observation(void)
|
||||
{
|
||||
// First time seen — can't be a fallback
|
||||
auto p = makePacket(0x1111, 100, 3, NO_NEXT_HOP_PREFERENCE, 0xAA);
|
||||
bool wasFallback = false;
|
||||
ph->wasSeenRecently(&p, true, &wasFallback);
|
||||
TEST_ASSERT_FALSE(wasFallback);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Group 9 — Next-Hop and Upgrade Detection
|
||||
// ===========================================================================
|
||||
|
||||
void test_weWereNextHop_true(void)
|
||||
{
|
||||
// Packet directed to us (next_hop = ourRelayID)
|
||||
auto p1 = makePacket(0x1111, 100, 3, OUR_RELAY_ID, 0xAA);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Re-observe: check if we were the original next_hop
|
||||
auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
|
||||
bool weWereNextHop = false;
|
||||
ph->wasSeenRecently(&p2, true, nullptr, &weWereNextHop);
|
||||
TEST_ASSERT_TRUE(weWereNextHop);
|
||||
}
|
||||
|
||||
void test_weWereNextHop_false(void)
|
||||
{
|
||||
// Packet directed to someone else
|
||||
auto p1 = makePacket(0x1111, 100, 3, 0x99, 0xAA);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
auto p2 = makePacket(0x1111, 100, 2, NO_NEXT_HOP_PREFERENCE, 0xBB);
|
||||
bool weWereNextHop = false;
|
||||
ph->wasSeenRecently(&p2, true, nullptr, &weWereNextHop);
|
||||
TEST_ASSERT_FALSE(weWereNextHop);
|
||||
}
|
||||
|
||||
void test_wasUpgraded_true(void)
|
||||
{
|
||||
// First observation with hop_limit=3 → stored as highestHopLimit bits 0-2 = 3
|
||||
auto p1 = makePacket(0x1111, 100, 3);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Re-observation with hop_limit=5
|
||||
// The upgrade check on line 122 compares the raw packed byte found->hop_limit against p->hop_limit.
|
||||
// found->hop_limit has highestHopLimit=3 in bits 0-2 (and possibly ourTxHopLimit in bits 3-5).
|
||||
// So the packed byte value is 3 (or more if ourTxHopLimit was set), and p->hop_limit is 5.
|
||||
// Since 3 < 5 (with no ourTxHopLimit set), this should detect an upgrade.
|
||||
auto p2 = makePacket(0x1111, 100, 5);
|
||||
bool wasUpgraded = false;
|
||||
ph->wasSeenRecently(&p2, true, nullptr, nullptr, &wasUpgraded);
|
||||
TEST_ASSERT_TRUE(wasUpgraded);
|
||||
}
|
||||
|
||||
void test_wasUpgraded_false(void)
|
||||
{
|
||||
auto p1 = makePacket(0x1111, 100, 5);
|
||||
ph->wasSeenRecently(&p1);
|
||||
|
||||
// Same or lower hop_limit
|
||||
auto p2 = makePacket(0x1111, 100, 3);
|
||||
bool wasUpgraded = false;
|
||||
ph->wasSeenRecently(&p2, true, nullptr, nullptr, &wasUpgraded);
|
||||
TEST_ASSERT_FALSE(wasUpgraded);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Group 10 — Edge Cases
|
||||
// ===========================================================================
|
||||
|
||||
void test_packet_id_zero_not_stored(void)
|
||||
{
|
||||
auto p = makePacket(0x1111, 0);
|
||||
TEST_ASSERT_FALSE(ph->wasSeenRecently(&p));
|
||||
TEST_ASSERT_FALSE(ph->wasSeenRecently(&p)); // still not found
|
||||
}
|
||||
|
||||
void test_sender_zero_substituted(void)
|
||||
{
|
||||
// from=0 means "from us" — getFrom() substitutes nodeDB->getNodeNum()
|
||||
auto p = makePacket(0, 100);
|
||||
ph->wasSeenRecently(&p);
|
||||
|
||||
// Should be stored under our node num, not 0
|
||||
auto p2 = makePacket(OUR_NODE_NUM, 100);
|
||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p2, false));
|
||||
}
|
||||
|
||||
void test_uninitialized_wasSeenRecently(void)
|
||||
{
|
||||
// Simulate uninitialized state — create a PacketHistory that looks uninitialized
|
||||
// We can't easily make allocation fail, but we can test the initOk guard with a destructed one
|
||||
PacketHistory h(4);
|
||||
TEST_ASSERT_TRUE(h.initOk()); // sanity check
|
||||
h.~PacketHistory();
|
||||
|
||||
auto p = makePacket(0x1111, 100);
|
||||
TEST_ASSERT_FALSE(h.wasSeenRecently(&p));
|
||||
|
||||
// Reconstruct in place to allow proper destruction
|
||||
new (&h) PacketHistory(4);
|
||||
}
|
||||
|
||||
void test_uninitialized_wasRelayer(void)
|
||||
{
|
||||
PacketHistory h(4);
|
||||
h.~PacketHistory();
|
||||
|
||||
TEST_ASSERT_FALSE(h.wasRelayer(0xAA, 100, 0x1111));
|
||||
|
||||
new (&h) PacketHistory(4);
|
||||
}
|
||||
|
||||
void test_multiple_instances_independent(void)
|
||||
{
|
||||
PacketHistory h2(SMALL_CAPACITY);
|
||||
|
||||
auto p = makePacket(0x1111, 100);
|
||||
ph->wasSeenRecently(&p);
|
||||
|
||||
// h2 should NOT find it
|
||||
TEST_ASSERT_FALSE(h2.wasSeenRecently(&p, false));
|
||||
|
||||
// ph should still find it
|
||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p, false));
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Group 11 — Hash Table Stress
|
||||
// ===========================================================================
|
||||
|
||||
void test_many_packets_no_false_negatives(void)
|
||||
{
|
||||
PacketHistory big(64);
|
||||
for (uint32_t i = 1; i <= 64; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
big.wasSeenRecently(&p);
|
||||
}
|
||||
for (uint32_t i = 1; i <= 64; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
TEST_ASSERT_TRUE_MESSAGE(big.wasSeenRecently(&p, false), "False negative in hash table");
|
||||
}
|
||||
}
|
||||
|
||||
void test_many_packets_no_false_positives(void)
|
||||
{
|
||||
PacketHistory big(64);
|
||||
for (uint32_t i = 1; i <= 64; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
big.wasSeenRecently(&p);
|
||||
}
|
||||
// IDs 65..128 were never inserted
|
||||
for (uint32_t i = 65; i <= 128; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
TEST_ASSERT_FALSE_MESSAGE(big.wasSeenRecently(&p, false), "False positive in hash table");
|
||||
}
|
||||
}
|
||||
|
||||
void test_churn_correctness(void)
|
||||
{
|
||||
// Insert 3x capacity to force heavy eviction.
|
||||
// Advance time between each generation so eviction can distinguish old from new.
|
||||
PacketHistory big(32);
|
||||
uint32_t capacity = 32;
|
||||
uint32_t generations = 3;
|
||||
|
||||
for (uint32_t gen = 0; gen < generations; gen++) {
|
||||
if (gen > 0)
|
||||
delay(1); // Ensure new generation has a newer timestamp than the old
|
||||
for (uint32_t i = 1; i <= capacity; i++) {
|
||||
auto p = makePacket(0xAAAA, gen * capacity + i);
|
||||
big.wasSeenRecently(&p);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t total = capacity * generations;
|
||||
|
||||
// Only the most recent 32 should be present (due to LRU eviction)
|
||||
for (uint32_t i = total - 31; i <= total; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
TEST_ASSERT_TRUE_MESSAGE(big.wasSeenRecently(&p, false), "Recent packet lost after churn");
|
||||
}
|
||||
// Older packets should be gone
|
||||
int found = 0;
|
||||
for (uint32_t i = 1; i <= total - capacity; i++) {
|
||||
auto p = makePacket(0xAAAA, i);
|
||||
if (big.wasSeenRecently(&p, false))
|
||||
found++;
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, found, "Evicted packets should not be found");
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Test runner
|
||||
// ===========================================================================
|
||||
|
||||
void setup()
|
||||
{
|
||||
delay(10);
|
||||
delay(2000);
|
||||
|
||||
initializeTestEnvironment();
|
||||
UNITY_BEGIN();
|
||||
|
||||
// Group 1 — Initialization
|
||||
RUN_TEST(test_init_valid_size);
|
||||
RUN_TEST(test_init_minimum_size);
|
||||
RUN_TEST(test_init_too_small_falls_back);
|
||||
|
||||
// Group 2 — Basic Deduplication
|
||||
RUN_TEST(test_first_packet_not_seen);
|
||||
RUN_TEST(test_same_packet_seen_twice);
|
||||
RUN_TEST(test_different_id_not_confused);
|
||||
RUN_TEST(test_different_sender_not_confused);
|
||||
RUN_TEST(test_withUpdate_false_no_insert);
|
||||
RUN_TEST(test_withUpdate_true_inserts);
|
||||
|
||||
// Group 3 — LRU Eviction
|
||||
RUN_TEST(test_fill_capacity_all_found);
|
||||
RUN_TEST(test_eviction_oldest_replaced);
|
||||
RUN_TEST(test_matching_slot_reused);
|
||||
RUN_TEST(test_free_slot_preferred);
|
||||
RUN_TEST(test_evict_all_old_packets);
|
||||
|
||||
// Group 4 — Relayer Tracking
|
||||
RUN_TEST(test_wasRelayer_true);
|
||||
RUN_TEST(test_wasRelayer_false);
|
||||
RUN_TEST(test_wasRelayer_zero_returns_false);
|
||||
RUN_TEST(test_wasRelayer_not_found);
|
||||
RUN_TEST(test_wasRelayer_wasSole_true);
|
||||
RUN_TEST(test_wasRelayer_wasSole_false);
|
||||
RUN_TEST(test_wasRelayer_all_six_slots);
|
||||
|
||||
// Group 5 — removeRelayer
|
||||
RUN_TEST(test_removeRelayer_removes);
|
||||
RUN_TEST(test_removeRelayer_compacts);
|
||||
RUN_TEST(test_removeRelayer_nonexistent_safe);
|
||||
RUN_TEST(test_removeRelayer_packet_not_found_safe);
|
||||
|
||||
// Group 6 — checkRelayers
|
||||
RUN_TEST(test_checkRelayers_both_found);
|
||||
RUN_TEST(test_checkRelayers_one_found);
|
||||
RUN_TEST(test_checkRelayers_r2WasSole);
|
||||
|
||||
// Group 7 — Merge Logic
|
||||
RUN_TEST(test_merge_preserves_original_next_hop);
|
||||
RUN_TEST(test_merge_preserves_highest_hop_limit);
|
||||
RUN_TEST(test_merge_no_duplicate_relayers);
|
||||
RUN_TEST(test_merge_adds_new_relayer);
|
||||
RUN_TEST(test_merge_we_relay_sets_slot_zero);
|
||||
RUN_TEST(test_merge_heard_back_stores_relay_node);
|
||||
|
||||
// Group 8 — Fallback-to-Flooding Detection
|
||||
RUN_TEST(test_fallback_detected);
|
||||
RUN_TEST(test_fallback_not_when_we_relayed);
|
||||
RUN_TEST(test_fallback_not_on_first_observation);
|
||||
|
||||
// Group 9 — Next-Hop and Upgrade Detection
|
||||
RUN_TEST(test_weWereNextHop_true);
|
||||
RUN_TEST(test_weWereNextHop_false);
|
||||
RUN_TEST(test_wasUpgraded_true);
|
||||
RUN_TEST(test_wasUpgraded_false);
|
||||
|
||||
// Group 10 — Edge Cases
|
||||
RUN_TEST(test_packet_id_zero_not_stored);
|
||||
RUN_TEST(test_sender_zero_substituted);
|
||||
RUN_TEST(test_uninitialized_wasSeenRecently);
|
||||
RUN_TEST(test_uninitialized_wasRelayer);
|
||||
RUN_TEST(test_multiple_instances_independent);
|
||||
|
||||
// Group 11 — Hash Table Stress
|
||||
RUN_TEST(test_many_packets_no_false_negatives);
|
||||
RUN_TEST(test_many_packets_no_false_positives);
|
||||
RUN_TEST(test_churn_correctness);
|
||||
|
||||
exit(UNITY_END());
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
@@ -19,7 +19,7 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.9
|
||||
zinggjm/GxEPD2@1.6.8
|
||||
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
|
||||
lewisxhe/SensorLib@0.3.4
|
||||
lib_ignore =
|
||||
|
||||
@@ -11,7 +11,7 @@ upload_speed = 921600
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.9
|
||||
zinggjm/GxEPD2@1.6.8
|
||||
build_unflags =
|
||||
${esp32s3_base.build_unflags}
|
||||
-DARDUINO_USB_MODE=1
|
||||
|
||||
@@ -23,4 +23,4 @@ build_flags = ${esp32s3_base.build_flags}
|
||||
|
||||
lib_deps = ${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.9
|
||||
zinggjm/GxEPD2@1.6.8
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#define LED_POWER 33
|
||||
#define LED_POWER2 34
|
||||
#define EXT_PWR_DETECT 35
|
||||
#define EXT_PWR_DETECT_MODE INPUT_PULLUP
|
||||
|
||||
#define BUTTON_PIN 18
|
||||
#define BUTTON_ACTIVE_LOW false
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#define EXT_PWR_DETECT 20
|
||||
#define EXT_PWR_DETECT_MODE INPUT_PULLUP
|
||||
|
||||
#define BUTTON_PIN 17
|
||||
#define BUTTON_ACTIVE_LOW false
|
||||
|
||||
@@ -10,6 +10,9 @@ build_flags =
|
||||
-D M5STACK_CARDPUTER_ADV
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-I variants/esp32s3/m5stack_cardputer_adv
|
||||
build_src_filter =
|
||||
${esp32s3_base.build_src_filter}
|
||||
+<../variants/esp32s3/m5stack_cardputer_adv>
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main
|
||||
|
||||
+1
-6
@@ -1,8 +1,5 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#ifdef M5STACK_CARDPUTER_ADV
|
||||
|
||||
#include "AudioBoard.h"
|
||||
#include "configuration.h"
|
||||
|
||||
DriverPins PinsAudioBoardES8311;
|
||||
AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311);
|
||||
@@ -41,5 +38,3 @@ void lateInitVariant()
|
||||
es8311_write_reg(0x32, 0xBF); // DAC volume (0dB)
|
||||
es8311_write_reg(0x37, 0x08); // EQ bypass
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -30,7 +30,7 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.9
|
||||
zinggjm/GxEPD2@1.6.8
|
||||
# renovate: datasource=git-refs depName=CSE_Touch packageName=https://github.com/CIRCUITSTATE/CSE_Touch gitBranch=main
|
||||
https://github.com/CIRCUITSTATE/CSE_Touch/archive/b44f23b6f870b848f1fbe453c190879bc6cfaafa.zip
|
||||
# renovate: datasource=github-tags depName=CSE_CST328 packageName=CIRCUITSTATE/CSE_CST328
|
||||
|
||||
@@ -34,7 +34,7 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.9
|
||||
zinggjm/GxEPD2@1.6.8
|
||||
# renovate: datasource=git-refs depName=CSE_Touch packageName=https://github.com/CIRCUITSTATE/CSE_Touch gitBranch=main
|
||||
https://github.com/CIRCUITSTATE/CSE_Touch/archive/b44f23b6f870b848f1fbe453c190879bc6cfaafa.zip
|
||||
# renovate: datasource=github-tags depName=CSE_CST328 packageName=CIRCUITSTATE/CSE_CST328
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
#define DAC_I2S_BCK 48
|
||||
#define DAC_I2S_WS 15
|
||||
#define DAC_I2S_DOUT 46
|
||||
#define DAC_I2S_MCLK -1
|
||||
#define DAC_I2S_MCLK 0
|
||||
|
||||
#define HAS_AXP2101
|
||||
|
||||
|
||||
@@ -6,27 +6,26 @@ NicheGraphics attempts a different approach:
|
||||
Per-device config takes place in this setupNicheGraphics() method
|
||||
(And a small amount in platformio.ini)
|
||||
|
||||
This file sets up InkHUD for the LilyGo T5-E-Paper-S3-Pro.
|
||||
|
||||
The board uses a 4.7" ED047TC1 parallel e-paper display (960×540, 8-bit parallel interface).
|
||||
This is driven via the FastEPD library through the NicheGraphics ED047TC1 driver adapter.
|
||||
This file sets up InkHUD for Heltec VM-E290.
|
||||
Different NicheGraphics UIs and different hardware variants will each have their own setup procedure.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
#include "mesh/MeshModule.h"
|
||||
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
|
||||
// InkHUD-specific components
|
||||
// ---------------------------
|
||||
#include "graphics/niche/InkHUD/InkHUD.h"
|
||||
// #include "graphics/niche/InkHUD/InkHUD.h"
|
||||
#include "graphics/niche/InkHUD/WindowManager.h"
|
||||
|
||||
// Applets
|
||||
#include "graphics/niche/InkHUD/Applets/User/AllMessage/AllMessageApplet.h"
|
||||
#include "graphics/niche/InkHUD/Applets/User/DM/DMApplet.h"
|
||||
#include "graphics/niche/InkHUD/Applets/User/FavoritesMap/FavoritesMapApplet.h"
|
||||
#include "graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.h"
|
||||
#include "graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.h"
|
||||
#include "graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.h"
|
||||
@@ -35,20 +34,26 @@ This is driven via the FastEPD library through the NicheGraphics ED047TC1 driver
|
||||
// Shared NicheGraphics components
|
||||
// --------------------------------
|
||||
#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h"
|
||||
#include "graphics/niche/Drivers/EInk/ED047TC1.h"
|
||||
#include "graphics/niche/Drivers/EInk/DEPG0290BNS800.h"
|
||||
#include "graphics/niche/Inputs/TwoButton.h"
|
||||
|
||||
void setupNicheGraphics()
|
||||
{
|
||||
using namespace NicheGraphics;
|
||||
|
||||
// SPI
|
||||
// -----------------------------
|
||||
|
||||
// Display is connected to HSPI
|
||||
SPIClass *hspi = new SPIClass(HSPI);
|
||||
hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS);
|
||||
|
||||
// E-Ink Driver
|
||||
// -----------------------------
|
||||
// The ED047TC1 is a parallel display — no SPI bus setup needed.
|
||||
// begin() args are part of the EInk interface but are ignored for parallel displays.
|
||||
|
||||
Drivers::EInk *driver = new Drivers::ED047TC1;
|
||||
driver->begin(nullptr, 0, 0, 0);
|
||||
// Use E-Ink driver
|
||||
Drivers::EInk *driver = new Drivers::DEPG0290BNS800;
|
||||
driver->begin(hspi, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY);
|
||||
|
||||
// InkHUD
|
||||
// ----------------------------
|
||||
@@ -62,57 +67,57 @@ void setupNicheGraphics()
|
||||
// Set how unhealthy additional FAST updates beyond this number are
|
||||
inkhud->setDisplayResilience(7, 1.5);
|
||||
|
||||
// Prepare fonts — use larger sizes to suit the 4.7" screen at ~234 DPI
|
||||
InkHUD::Applet::fontLarge = FREESANS_24PT_WIN1253;
|
||||
InkHUD::Applet::fontMedium = FREESANS_18PT_WIN1253;
|
||||
InkHUD::Applet::fontSmall = FREESANS_12PT_WIN1253;
|
||||
// Prepare fonts
|
||||
InkHUD::Applet::fontLarge = FREESANS_9PT_WIN1252;
|
||||
InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252;
|
||||
|
||||
// Customize default settings
|
||||
// Init settings, and customize defaults
|
||||
inkhud->persistence->settings.userTiles.maxCount = 2; // How many tiles can the display handle?
|
||||
inkhud->persistence->settings.rotation = 3; // 270 degrees clockwise
|
||||
inkhud->persistence->settings.rotation = 1; // 90 degrees clockwise
|
||||
inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users
|
||||
inkhud->persistence->settings.optionalFeatures.batteryIcon = true;
|
||||
inkhud->persistence->settings.optionalMenuItems.backlight = true;
|
||||
inkhud->persistence->settings.optionalMenuItems.nextTile = false; // Behavior handled by aux button instead
|
||||
inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery
|
||||
|
||||
// Alignment must cancel rotation for visual-frame touch input: (rotation + alignment) % 4 == 0.
|
||||
inkhud->persistence->settings.joystick.alignment = (4 - inkhud->persistence->settings.rotation) % 4;
|
||||
// Setup backlight
|
||||
// Note: AUX button behavior configured further down
|
||||
Drivers::LatchingBacklight *backlight = Drivers::LatchingBacklight::getInstance();
|
||||
backlight->setPin(PIN_EINK_EN);
|
||||
|
||||
// Pick applets
|
||||
// Note: order of applets determines priority of "auto-show" feature
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, false, false); // Not Active, not autoshown
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet, true, true); // Activated, Autoshown
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0), true, true); // Activated, Autoshown
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1), false, false); // Not Active, not autoshown
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true, false); // Activated, not autoshown
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet, true, false); // Activated, not autoshown
|
||||
// Optional arguments for defaults:
|
||||
// - is activated?
|
||||
// - is autoshown?
|
||||
// - is foreground on a specific tile (index)?
|
||||
inkhud->addApplet("All Messages", new InkHUD::AllMessageApplet, true, true); // Activated, autoshown
|
||||
inkhud->addApplet("DMs", new InkHUD::DMApplet);
|
||||
inkhud->addApplet("Channel 0", new InkHUD::ThreadedMessageApplet(0));
|
||||
inkhud->addApplet("Channel 1", new InkHUD::ThreadedMessageApplet(1));
|
||||
inkhud->addApplet("Positions", new InkHUD::PositionsApplet, true); // Activated
|
||||
inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet);
|
||||
inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, not autoshown, default on tile 0
|
||||
inkhud->addApplet("Favorites Map", new InkHUD::FavoritesMapApplet, false, false); // Not Active, not autoshown
|
||||
|
||||
// Backlight
|
||||
// ----------------------------
|
||||
Drivers::LatchingBacklight *backlight = Drivers::LatchingBacklight::getInstance();
|
||||
backlight->setPin(BOARD_BL_EN); // GPIO11 on V2
|
||||
// inkhud->addApplet("Basic", new InkHUD::BasicExampleApplet);
|
||||
// inkhud->addApplet("NewMsg", new InkHUD::NewMsgExampleApplet);
|
||||
|
||||
// Start running InkHUD
|
||||
inkhud->begin();
|
||||
|
||||
// Touch navigation requires joystick mode — enforce post-begin so flash cannot override.
|
||||
inkhud->persistence->settings.joystick.enabled = true;
|
||||
inkhud->persistence->settings.joystick.aligned = true;
|
||||
|
||||
// Buttons
|
||||
// --------------------------
|
||||
|
||||
Inputs::TwoButton *buttons = Inputs::TwoButton::getInstance(); // A shared NicheGraphics component
|
||||
|
||||
// Setup the main user button (boot button, GPIO 0)
|
||||
// Setup the main user button (0)
|
||||
buttons->setWiring(0, BUTTON_PIN);
|
||||
buttons->setHandlerShortPress(0, [inkhud]() { inkhud->shortpress(); });
|
||||
buttons->setHandlerLongPress(0, [inkhud]() { inkhud->longpress(); });
|
||||
buttons->setHandlerShortPress(0, []() { InkHUD::InkHUD::getInstance()->shortpress(); });
|
||||
buttons->setHandlerLongPress(0, []() { InkHUD::InkHUD::getInstance()->longpress(); });
|
||||
|
||||
// No dedicated aux button on this board
|
||||
// Setup the aux button (1)
|
||||
// Bonus feature of VME290
|
||||
buttons->setWiring(1, BUTTON_PIN_SECONDARY);
|
||||
buttons->setHandlerShortPress(1, []() { InkHUD::InkHUD::getInstance()->nextTile(); });
|
||||
|
||||
buttons->start();
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -5,7 +5,7 @@ board_build.partition = default_16MB.csv
|
||||
board_check = true
|
||||
upload_protocol = esptool
|
||||
build_flags = -fno-strict-aliasing
|
||||
${esp32s3_base.build_flags}
|
||||
${esp32_base.build_flags}
|
||||
-I variants/esp32s3/t5s3_epaper
|
||||
-D T5_S3_EPAPER_PRO
|
||||
-D USE_EINK
|
||||
|
||||
@@ -2,123 +2,21 @@
|
||||
|
||||
#ifdef T5_S3_EPAPER_PRO
|
||||
|
||||
#include "Observer.h"
|
||||
#include "TouchDrvGT911.hpp"
|
||||
#include "Wire.h"
|
||||
#include "input/InputBroker.h"
|
||||
#include "input/TouchScreenImpl1.h"
|
||||
#include "sleep.h"
|
||||
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
#include "graphics/niche/InkHUD/InkHUD.h"
|
||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
||||
|
||||
// Bridges touch events from TouchScreenImpl1 directly into InkHUD,
|
||||
// bypassing the InputBroker (which is excluded in InkHUD builds).
|
||||
// Routing mirrors the mini-epaper-s3 two-way rocker pattern:
|
||||
// - Nav left/right: prevApplet/nextApplet when idle, navUp/Down when a system applet has focus (e.g. menu)
|
||||
// - Nav up/down: navUp/navDown always (menu scroll)
|
||||
// - Tap: shortpress (cycle applets / confirm in menu)
|
||||
// - Long press: longpress (open menu / back)
|
||||
class TouchInkHUDBridge : public Observer<const InputEvent *>
|
||||
{
|
||||
int onNotify(const InputEvent *e) override
|
||||
{
|
||||
auto *inkhud = NicheGraphics::InkHUD::InkHUD::getInstance();
|
||||
|
||||
// Keep alignment in sync with the current rotation so that visual-frame gestures
|
||||
// always pass through nav functions without remapping: (rotation + alignment) % 4 == 0.
|
||||
inkhud->persistence->settings.joystick.alignment = (4 - inkhud->persistence->settings.rotation) % 4;
|
||||
|
||||
// Check whether a system applet (e.g. menu) is currently handling input
|
||||
bool systemHandlingInput = false;
|
||||
for (NicheGraphics::InkHUD::SystemApplet *sa : inkhud->systemApplets) {
|
||||
if (sa->handleInput) {
|
||||
systemHandlingInput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (e->inputEvent) {
|
||||
case INPUT_BROKER_USER_PRESS:
|
||||
inkhud->shortpress();
|
||||
break;
|
||||
case INPUT_BROKER_SELECT:
|
||||
inkhud->longpress();
|
||||
break;
|
||||
case INPUT_BROKER_LEFT:
|
||||
if (systemHandlingInput)
|
||||
inkhud->navUp();
|
||||
else
|
||||
inkhud->prevApplet();
|
||||
break;
|
||||
case INPUT_BROKER_RIGHT:
|
||||
if (systemHandlingInput)
|
||||
inkhud->navDown();
|
||||
else
|
||||
inkhud->nextApplet();
|
||||
break;
|
||||
case INPUT_BROKER_UP:
|
||||
inkhud->navUp();
|
||||
break;
|
||||
case INPUT_BROKER_DOWN:
|
||||
inkhud->navDown();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
static TouchInkHUDBridge touchBridge;
|
||||
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
|
||||
TouchDrvGT911 touch;
|
||||
|
||||
// Commands the GT911 into standby before the Wire bus is torn down.
|
||||
// notifyDeepSleep fires before Wire.end() in doDeepSleep(), so I2C is still available here.
|
||||
struct TouchDeepSleepObserver {
|
||||
int onDeepSleep(void *)
|
||||
{
|
||||
touch.sleep();
|
||||
return 0;
|
||||
}
|
||||
CallbackObserver<TouchDeepSleepObserver, void *> observer{this, &TouchDeepSleepObserver::onDeepSleep};
|
||||
} static touchDeepSleepObserver;
|
||||
|
||||
bool readTouch(int16_t *x, int16_t *y)
|
||||
{
|
||||
if (!digitalRead(GT911_PIN_INT)) {
|
||||
int16_t raw_x;
|
||||
int16_t raw_y;
|
||||
if (touch.getPoint(&raw_x, &raw_y)) {
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
// Transform raw GT911 axes to visual-frame coordinates for the current display rotation.
|
||||
// rotation=3 is the physical identity (device's default orientation).
|
||||
switch (NicheGraphics::InkHUD::InkHUD::getInstance()->persistence->settings.rotation) {
|
||||
default:
|
||||
case 3:
|
||||
*x = raw_x;
|
||||
*y = raw_y;
|
||||
break; // identity
|
||||
case 2:
|
||||
*x = (EPD_WIDTH - 1) - raw_y;
|
||||
*y = raw_x;
|
||||
break; // 90° CW tilt
|
||||
case 1:
|
||||
*x = (EPD_HEIGHT - 1) - raw_x;
|
||||
*y = (EPD_WIDTH - 1) - raw_y;
|
||||
break; // 180° flip
|
||||
case 0:
|
||||
*x = raw_y;
|
||||
*y = (EPD_HEIGHT - 1) - raw_x;
|
||||
break; // 90° CCW tilt
|
||||
}
|
||||
#else
|
||||
*x = raw_x;
|
||||
*y = raw_y;
|
||||
#endif
|
||||
// rotate 90° for landscape
|
||||
*x = raw_y;
|
||||
*y = EPD_WIDTH - 1 - raw_x;
|
||||
LOG_DEBUG("touched(%d/%d)", *x, *y);
|
||||
return true;
|
||||
}
|
||||
@@ -133,47 +31,17 @@ void earlyInitVariant()
|
||||
pinMode(SDCARD_CS, OUTPUT);
|
||||
digitalWrite(SDCARD_CS, HIGH);
|
||||
pinMode(BOARD_BL_EN, OUTPUT);
|
||||
|
||||
// Program GT911 touch controller to I2C address 0x14 (GT911_SLAVE_ADDRESS_H) before
|
||||
// the I2C bus scan runs. GPIO3 (INT) defaults LOW on ESP32-S3 cold boot, which would
|
||||
// leave the GT911 at 0x5D (GT911_SLAVE_ADDRESS_L) — the same address as the SFA30
|
||||
// air quality sensor — causing a false-positive SFA30 detection during the I2C scan.
|
||||
//
|
||||
// GT911 datasheet §4.3 "Address Selection":
|
||||
// Pull INT HIGH before releasing RST → device latches address 0x14 (SLAVE_ADDRESS_H)
|
||||
// Pull INT LOW before releasing RST → device latches address 0x5D (SLAVE_ADDRESS_L)
|
||||
// Minimum RST assert time: 100 µs; minimum startup time after RST deassert: 5 ms.
|
||||
//
|
||||
// lateInitVariant() calls touch.begin() which repeats this sequence internally while
|
||||
// also performing full I2C initialisation; the double-reset is harmless.
|
||||
pinMode(GT911_PIN_RST, OUTPUT);
|
||||
digitalWrite(GT911_PIN_RST, LOW);
|
||||
pinMode(GT911_PIN_INT, OUTPUT);
|
||||
digitalWrite(GT911_PIN_INT, HIGH); // HIGH → latch address 0x14
|
||||
delay(1); // > 100 µs
|
||||
digitalWrite(GT911_PIN_RST, HIGH);
|
||||
delay(10); // > 5 ms startup
|
||||
pinMode(GT911_PIN_INT, INPUT); // release INT for interrupt use
|
||||
}
|
||||
|
||||
void variant_shutdown()
|
||||
{
|
||||
// Ensure frontlight is off during deep sleep
|
||||
digitalWrite(BOARD_BL_EN, LOW);
|
||||
}
|
||||
|
||||
void lateInitVariant()
|
||||
// T5-S3-ePaper Pro specific (late-) init
|
||||
void lateInitVariant(void)
|
||||
{
|
||||
touch.setPins(GT911_PIN_RST, GT911_PIN_INT);
|
||||
if (touch.begin(Wire, GT911_SLAVE_ADDRESS_H, GT911_PIN_SDA, GT911_PIN_SCL)) {
|
||||
touchDeepSleepObserver.observer.observe(¬ifyDeepSleep);
|
||||
if (touch.begin(Wire, GT911_SLAVE_ADDRESS_L, GT911_PIN_SDA, GT911_PIN_SCL)) {
|
||||
touchScreenImpl1 = new TouchScreenImpl1(EPD_WIDTH, EPD_HEIGHT, readTouch);
|
||||
touchScreenImpl1->init();
|
||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
||||
touchBridge.observe(touchScreenImpl1);
|
||||
#endif
|
||||
} else {
|
||||
LOG_ERROR("Failed to find touch controller!");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -26,9 +26,9 @@
|
||||
#define GT911_PIN_RST 9
|
||||
#endif
|
||||
|
||||
#define PCF8563_RTC 0x51
|
||||
#define PCF85063_RTC 0x51
|
||||
#define HAS_RTC 1
|
||||
#define PCF8563_INT 2
|
||||
#define PCF85063_INT 2
|
||||
|
||||
#define USE_POWERSAVE
|
||||
#define SLEEP_TIME 120
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#define BUTTON_PIN 0 // The middle button GPIO on the T-Beam S3
|
||||
// #define EXT_NOTIFY_OUT 13 // Default pin to use for Ext Notify Module.
|
||||
|
||||
#define LED_STATE_ON 0 // State when LED is lit
|
||||
|
||||
// TTGO uses a common pinout for their SX1262 vs RF95 modules - both can be enabled and we will probe at runtime for RF95 and if
|
||||
// not found then probe for SX1262
|
||||
#define USE_SX1262
|
||||
@@ -74,4 +76,4 @@
|
||||
// has 32768 Hz crystal
|
||||
#define HAS_32768HZ 1
|
||||
|
||||
#define USE_SH1106
|
||||
#define USE_SH1106
|
||||
@@ -2,6 +2,8 @@
|
||||
extends = portduino_base
|
||||
build_flags = ${portduino_base.build_flags} -I variants/native/portduino
|
||||
-I /usr/include
|
||||
!pkg-config --cflags --libs libnotify
|
||||
-D HAS_LIBNOTIFY=1
|
||||
board = cross_platform
|
||||
board_level = extra
|
||||
lib_deps =
|
||||
@@ -27,7 +29,8 @@ build_type = release
|
||||
lib_deps =
|
||||
${native_base.lib_deps}
|
||||
${device-ui_base.lib_deps}
|
||||
build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunction-sections -fdata-sections -Wl,--gc-sections
|
||||
build_flags = ${native_base.build_flags}
|
||||
-Os -lX11 -linput -lxkbcommon -ffunction-sections -fdata-sections -Wl,--gc-sections
|
||||
-D RAM_SIZE=16384
|
||||
-D USE_X11=1
|
||||
-D HAS_TFT=1
|
||||
@@ -55,7 +58,8 @@ build_type = release
|
||||
lib_deps =
|
||||
${native_base.lib_deps}
|
||||
${device-ui_base.lib_deps}
|
||||
build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections -Wl,--gc-sections
|
||||
build_flags = ${native_base.build_flags}
|
||||
-Os -ffunction-sections -fdata-sections -Wl,--gc-sections
|
||||
-D RAM_SIZE=8192
|
||||
-D USE_FRAMEBUFFER=1
|
||||
-D LV_COLOR_DEPTH=32
|
||||
@@ -84,7 +88,8 @@ build_type = debug
|
||||
lib_deps =
|
||||
${native_base.lib_deps}
|
||||
${device-ui_base.lib_deps}
|
||||
build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -lxkbcommon
|
||||
build_flags = ${native_base.build_flags}
|
||||
-O0 -fsanitize=address -lX11 -linput -lxkbcommon
|
||||
-D DEBUG_HEAP
|
||||
-D RAM_SIZE=16384
|
||||
-D USE_X11=1
|
||||
@@ -112,7 +117,9 @@ build_src_filter = ${env:native-tft.build_src_filter}
|
||||
|
||||
[env:coverage]
|
||||
extends = env:native
|
||||
build_flags = -lgcov --coverage -fprofile-abs-path -fsanitize=address ${env:native.build_flags}
|
||||
build_flags =
|
||||
-lgcov --coverage -fprofile-abs-path -fsanitize=address
|
||||
${env:native.build_flags}
|
||||
; https://docs.platformio.org/en/latest/projectconf/sections/env/options/test/test_testing_command.html
|
||||
test_testing_command =
|
||||
${platformio.build_dir}/${this.__env__}/meshtasticd
|
||||
|
||||
@@ -12,5 +12,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/Dongle_
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.9
|
||||
zinggjm/GxEPD2@1.6.8
|
||||
debug_tool = jlink
|
||||
|
||||
@@ -85,6 +85,7 @@ static const uint8_t A0 = PIN_A0;
|
||||
|
||||
// charger status
|
||||
#define EXT_CHRG_DETECT (32 + 6)
|
||||
#define EXT_CHRG_DETECT_VALUE HIGH
|
||||
|
||||
// SPI
|
||||
#define SPI_INTERFACES_COUNT 1
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
#include "variant.h"
|
||||
#include "nrf.h"
|
||||
#include "power.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
|
||||
@@ -66,11 +65,7 @@ void variant_shutdown()
|
||||
nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW;
|
||||
nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense1);
|
||||
|
||||
// If we are sleeping because of low battery, wake up when the solar charger detects power.
|
||||
// But if the user intentionally put us to sleep with the button, don't wake up just because the lights are on
|
||||
if (power->isLowBattery()) {
|
||||
nrf_gpio_cfg_input(EXT_CHRG_DETECT, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
|
||||
nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_LOW;
|
||||
nrf_gpio_cfg_sense_set(EXT_CHRG_DETECT, sense2);
|
||||
}
|
||||
nrf_gpio_cfg_input(EXT_CHRG_DETECT, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
|
||||
nrf_gpio_pin_sense_t sense2 = NRF_GPIO_PIN_SENSE_LOW;
|
||||
nrf_gpio_cfg_sense_set(EXT_CHRG_DETECT, sense2);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ static const uint8_t A0 = PIN_A0;
|
||||
|
||||
#define HAS_SOLAR
|
||||
|
||||
#define OCV_ARRAY 4080, 3990, 3935, 3880, 3825, 3770, 3715, 3660, 3605, 3550, 3490
|
||||
#define OCV_ARRAY 4080, 3990, 3935, 3880, 3825, 3770, 3715, 3660, 3605, 3550, 3450
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ME25LS0
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.9
|
||||
zinggjm/GxEPD2@1.6.8
|
||||
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
|
||||
upload_protocol = nrfutil
|
||||
;upload_port = /dev/ttyACM1
|
||||
|
||||
@@ -15,6 +15,6 @@ lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic-ESP32_Codec2 packageName=https://github.com/meshtastic/ESP32_Codec2 gitBranch=master
|
||||
https://github.com/meshtastic/ESP32_Codec2/archive/633326c78ac251c059ab3a8c430fcdf25b41672f.zip
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.9
|
||||
zinggjm/GxEPD2@1.6.8
|
||||
debug_tool = jlink
|
||||
;upload_port = /dev/ttyACM4
|
||||
@@ -9,5 +9,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/TWC_mes
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.9
|
||||
zinggjm/GxEPD2@1.6.8
|
||||
debug_tool = jlink
|
||||
|
||||
@@ -16,7 +16,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.9
|
||||
zinggjm/GxEPD2@1.6.8
|
||||
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
|
||||
melopero/Melopero RV3028@1.2.0
|
||||
# renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library
|
||||
|
||||
@@ -18,7 +18,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/rak4631
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=GxEPD2 packageName=zinggjm/library/GxEPD2
|
||||
zinggjm/GxEPD2@1.6.9
|
||||
zinggjm/GxEPD2@1.6.8
|
||||
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
|
||||
melopero/Melopero RV3028@1.2.0
|
||||
# renovate: datasource=custom.pio depName=RAK NCP5623 RGB LED packageName=rakwireless/library/RAKwireless NCP5623 RGB LED library
|
||||
|
||||
Reference in New Issue
Block a user