mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-16 08:31:35 +10:00
Merge branch 'develop' of https://github.com/meshtastic/firmware into develop
This commit is contained in:
@@ -96,7 +96,6 @@ jobs:
|
||||
pio_platform: ${{ matrix.check.platform }}
|
||||
pio_env: ${{ matrix.check.board }}
|
||||
pio_target: check
|
||||
pio_opts: --fail-on-defect=low
|
||||
|
||||
build:
|
||||
needs: [setup, version]
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Stale PR+Issues
|
||||
uses: actions/stale@v10.1.1
|
||||
uses: actions/stale@v10.2.0
|
||||
with:
|
||||
days-before-stale: 45
|
||||
stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.
|
||||
|
||||
+6
-6
@@ -8,20 +8,20 @@ plugins:
|
||||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.501
|
||||
- renovate@43.10.3
|
||||
- checkov@3.2.506
|
||||
- renovate@43.31.9
|
||||
- prettier@3.8.1
|
||||
- trufflehog@3.93.3
|
||||
- trufflehog@3.93.4
|
||||
- yamllint@1.38.0
|
||||
- bandit@1.9.3
|
||||
- trivy@0.69.1
|
||||
- taplo@0.10.0
|
||||
- ruff@0.15.1
|
||||
- isort@7.0.0
|
||||
- ruff@0.15.2
|
||||
- isort@8.0.0
|
||||
- markdownlint@0.47.0
|
||||
- oxipng@10.1.0
|
||||
- svgo@4.0.0
|
||||
- actionlint@1.7.10
|
||||
- actionlint@1.7.11
|
||||
- flake8@7.3.0
|
||||
- hadolint@2.14.0
|
||||
- shfmt@3.6.0
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Lightweight container for running native PlatformIO tests on non-Linux hosts
|
||||
FROM python:3.14-slim-trixie
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
|
||||
# hadolint ignore=DL3008
|
||||
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 \
|
||||
&& 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
|
||||
|
||||
WORKDIR /firmware
|
||||
RUN chown -R meshtastic:meshtastic /firmware
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD platformio --version || exit 1
|
||||
|
||||
USER meshtastic
|
||||
|
||||
# Run tests by default; override with docker run args for specific filters
|
||||
CMD ["platformio", "test", "-e", "coverage", "-v"]
|
||||
+1
-1
@@ -23,4 +23,4 @@ for BOARD in $BOARDS; do
|
||||
CHECK="${CHECK} -e ${BOARD}"
|
||||
done
|
||||
|
||||
pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt --inline-suppr" $CHECK --skip-packages --pattern="src/" --fail-on-defect=medium --fail-on-defect=high
|
||||
pio check --flags "-DAPP_VERSION=${APP_VERSION} --suppressions-list=suppressions.txt --inline-suppr" $CHECK --skip-packages --pattern="src/" --fail-on-defect=low --fail-on-defect=medium --fail-on-defect=high
|
||||
|
||||
@@ -9,3 +9,5 @@ Lora:
|
||||
DIO3_TCXO_VOLTAGE: true
|
||||
DIO2_AS_RF_SWITCH: true
|
||||
spidev: spidev0.0
|
||||
GPIO_DETECT_PA: 13
|
||||
TX_GAIN_LORA: [8, 8, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7]
|
||||
@@ -13,8 +13,7 @@ Lora:
|
||||
# USB_Serialnum: 12345678
|
||||
SX126X_MAX_POWER: 22
|
||||
# Reduce output power to improve EMI
|
||||
NUM_PA_POINTS: 22
|
||||
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
|
||||
TX_GAIN_LORA: [12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7]
|
||||
# Note: This module integrates an additional PA to achieve higher output power.
|
||||
# The 'power' parameter here does not represent the actual RF output.
|
||||
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm).
|
||||
|
||||
@@ -13,8 +13,7 @@ Lora:
|
||||
# USB_Serialnum: 12345678
|
||||
SX126X_MAX_POWER: 22
|
||||
# Reduce output power to improve EMI
|
||||
NUM_PA_POINTS: 22
|
||||
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
|
||||
TX_GAIN_LORA: [12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7]
|
||||
# Note: This module integrates an additional PA to achieve higher output power.
|
||||
# The 'power' parameter here does not represent the actual RF output.
|
||||
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm).
|
||||
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run native PlatformIO tests inside Docker (for macOS / non-Linux hosts).
|
||||
#
|
||||
# Usage:
|
||||
# ./bin/test-native-docker.sh # run all native tests
|
||||
# ./bin/test-native-docker.sh -f test_transmit_history # run specific test filter
|
||||
# ./bin/test-native-docker.sh --rebuild # force rebuild the image
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
IMAGE_NAME="meshtastic-native-test"
|
||||
|
||||
REBUILD=false
|
||||
EXTRA_ARGS=()
|
||||
|
||||
for arg in "$@"; do
|
||||
if [[ "$arg" == "--rebuild" ]]; then
|
||||
REBUILD=true
|
||||
else
|
||||
EXTRA_ARGS+=("$arg")
|
||||
fi
|
||||
done
|
||||
|
||||
if $REBUILD || ! docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then
|
||||
echo "Building test image (first run may take a few minutes)..."
|
||||
docker build -t "$IMAGE_NAME" -f "$ROOT_DIR/Dockerfile.test" "$ROOT_DIR"
|
||||
fi
|
||||
|
||||
# Disable BUILD_EPOCH to avoid full rebuilds between test runs (matches CI)
|
||||
sed_cmd='s/-DBUILD_EPOCH=$UNIX_TIME/#-DBUILD_EPOCH=$UNIX_TIME/'
|
||||
|
||||
# Default: run all tests. Pass extra args (e.g. -f test_transmit_history) through.
|
||||
if [[ ${#EXTRA_ARGS[@]} -eq 0 ]]; then
|
||||
CMD=("platformio" "test" "-e" "coverage" "-v")
|
||||
else
|
||||
CMD=("platformio" "test" "-e" "coverage" "-v" "${EXTRA_ARGS[@]}")
|
||||
fi
|
||||
|
||||
exec docker run --rm \
|
||||
-v "$ROOT_DIR:/src:ro" \
|
||||
"$IMAGE_NAME" \
|
||||
bash -c "rm -rf /tmp/fw-test && cp -a /src /tmp/fw-test && cd /tmp/fw-test && sed -i '${sed_cmd}' platformio.ini && ${CMD[*]}"
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "default.csv"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_ESP32S3_DEV",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": ["wifi"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "LilyGo Mini-Epaper-S3 (4 MB Flash, 2MB PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 4194304,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"url": "https://www.lilygo.cc",
|
||||
"vendor": "LilyGo"
|
||||
}
|
||||
+1
-1
Submodule protobufs updated: 44298d374f...f7f7c8d2e4
+1
-3
@@ -51,9 +51,7 @@ class AudioThread : public concurrency::OSThread
|
||||
i2sRtttl = nullptr;
|
||||
}
|
||||
|
||||
if (rtttlFile != nullptr) {
|
||||
rtttlFile = nullptr;
|
||||
}
|
||||
rtttlFile = nullptr;
|
||||
|
||||
setCPUFast(false);
|
||||
#ifdef T_LORA_PAGER
|
||||
|
||||
@@ -35,6 +35,8 @@ void consoleInit()
|
||||
#if defined(SERIAL_HAS_ON_RECEIVE)
|
||||
// onReceive does only exist for HardwareSerial not for USB CDC serial
|
||||
Port.onReceive([sc]() { sc->rxInt(); });
|
||||
#else
|
||||
(void)sc;
|
||||
#endif
|
||||
DEBUG_PORT.rpInit(); // Simply sets up semaphore
|
||||
}
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include "concurrency/OSThread.h"
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Periodically invoke a callback. This just provides C-style callback conventions
|
||||
* rather than a virtual function - FIXME, remove?
|
||||
* @brief Periodically invoke a callback.
|
||||
* Supports both legacy function pointers and modern callables.
|
||||
*/
|
||||
class Periodic : public OSThread
|
||||
{
|
||||
int32_t (*callback)();
|
||||
|
||||
public:
|
||||
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||
Periodic(const char *name, int32_t (*_callback)()) : OSThread(name), callback(_callback) {}
|
||||
Periodic(const char *name, int32_t (*cb)()) : OSThread(name), callback(cb) {}
|
||||
Periodic(const char *name, std::function<int32_t()> cb) : OSThread(name), callback(std::move(cb)) {}
|
||||
|
||||
protected:
|
||||
int32_t runOnce() override { return callback(); }
|
||||
int32_t runOnce() override { return callback ? callback() : 0; }
|
||||
|
||||
private:
|
||||
std::function<int32_t()> callback;
|
||||
};
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -259,6 +259,17 @@ bool EInkDisplay::connect()
|
||||
adafruitDisplay->setRotation(3);
|
||||
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
|
||||
}
|
||||
#elif defined(MINI_EPAPER_S3)
|
||||
spi1 = new SPIClass(HSPI);
|
||||
spi1->begin(PIN_SPI1_SCK, PIN_SPI1_MISO, PIN_SPI1_MOSI, PIN_EINK_CS);
|
||||
|
||||
// Create GxEPD2 objects
|
||||
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *spi1);
|
||||
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
|
||||
|
||||
// Init GxEPD2
|
||||
adafruitDisplay->init();
|
||||
adafruitDisplay->setRotation(1);
|
||||
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
|
||||
|
||||
// Detect display model, before starting SPI
|
||||
|
||||
@@ -93,7 +93,8 @@ class EInkDisplay : public OLEDDisplay
|
||||
SPIClass *hspi = NULL;
|
||||
#endif
|
||||
|
||||
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
|
||||
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK) || \
|
||||
defined(MINI_EPAPER_S3)
|
||||
SPIClass *spi1 = NULL;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -875,6 +875,10 @@ int32_t Screen::runOnce()
|
||||
break;
|
||||
case Cmd::STOP_ALERT_FRAME:
|
||||
NotificationRenderer::pauseBanner = false;
|
||||
// Return from one-off alert mode back to regular frames.
|
||||
if (!showingNormalScreen && NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||
setFrames();
|
||||
}
|
||||
break;
|
||||
case Cmd::STOP_BOOT_SCREEN:
|
||||
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
|
||||
|
||||
@@ -1209,8 +1209,8 @@ void TFTDisplay::display(bool fromBlank)
|
||||
bool somethingChanged = false;
|
||||
|
||||
// Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step
|
||||
colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8);
|
||||
colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8);
|
||||
colorTftMesh = __builtin_bswap16(TFT_MESH);
|
||||
colorTftBlack = __builtin_bswap16(TFT_BLACK);
|
||||
|
||||
y = 0;
|
||||
while (y < displayHeight) {
|
||||
|
||||
@@ -73,7 +73,7 @@ ProcessMessage InkHUD::FavoritesMapApplet::handleReceived(const meshtastic_MeshP
|
||||
}
|
||||
} else {
|
||||
// For non-local packets, this applet only reacts to favorited nodes.
|
||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
|
||||
const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
|
||||
if (!sender || !sender->is_favorite)
|
||||
return ProcessMessage::CONTINUE;
|
||||
|
||||
|
||||
@@ -276,7 +276,8 @@ int32_t ButtonThread::runOnce()
|
||||
case BUTTON_EVENT_LONG_RELEASED: {
|
||||
|
||||
LOG_INFO("LONG PRESS RELEASE AFTER %u MILLIS", millis() - buttonPressStartTime);
|
||||
if (millis() > 30000 && _longLongPress != INPUT_BROKER_NONE &&
|
||||
// Require press started after boot holdoff to avoid phantom shutdown from floating pins
|
||||
if (millis() > 30000 && buttonPressStartTime > 30000 && _longLongPress != INPUT_BROKER_NONE &&
|
||||
(millis() - buttonPressStartTime) >= _longLongPressTime && leadUpPlayed) {
|
||||
evt.inputEvent = _longLongPress;
|
||||
this->notifyObservers(&evt);
|
||||
|
||||
+4
-1
@@ -9,6 +9,7 @@
|
||||
#include "PowerMon.h"
|
||||
#include "RadioLibInterface.h"
|
||||
#include "ReliableRouter.h"
|
||||
#include "TransmitHistory.h"
|
||||
#include "airtime.h"
|
||||
#include "buzz.h"
|
||||
#include "power/PowerHAL.h"
|
||||
@@ -253,7 +254,6 @@ uint32_t timeLastPowered = 0;
|
||||
static OSThread *powerFSMthread;
|
||||
OSThread *ambientLightingThread;
|
||||
|
||||
RadioInterface *rIf = NULL;
|
||||
RadioLibHal *RadioLibHAL = NULL;
|
||||
|
||||
/**
|
||||
@@ -726,6 +726,9 @@ void setup()
|
||||
// We do this as early as possible because this loads preferences from flash
|
||||
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
|
||||
nodeDB = new NodeDB;
|
||||
|
||||
// Initialize transmit history to persist broadcast throttle timers across reboots
|
||||
TransmitHistory::getInstance()->loadFromDisk();
|
||||
#if HAS_TFT
|
||||
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
tftSetup();
|
||||
|
||||
@@ -106,6 +106,18 @@ class MeshModule
|
||||
/* We allow modules to ignore a request without sending an error if they have a specific reason for it. */
|
||||
bool ignoreRequest = false;
|
||||
|
||||
/**
|
||||
* Check if the current request is a multi-hop broadcast. Modules should call this in allocReply()
|
||||
* and return NULL to prevent reply storms from broadcast requests that have already been relayed.
|
||||
*/
|
||||
bool isMultiHopBroadcastRequest()
|
||||
{
|
||||
if (currentRequest && isBroadcast(currentRequest->to) && currentRequest->hop_limit < currentRequest->hop_start) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** If a bound channel name is set, we will only accept received packets that come in on that channel.
|
||||
* A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface
|
||||
* are allowed on any channel (this lets the local user do anything).
|
||||
|
||||
+5
-6
@@ -147,9 +147,8 @@ uint32_t get_st7789_id(uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_
|
||||
digitalWrite(rst, HIGH);
|
||||
delay(10);
|
||||
|
||||
uint32_t ID = 0;
|
||||
ID = readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst);
|
||||
ID = readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst); // ST7789 needs twice
|
||||
readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst);
|
||||
uint32_t ID = readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst); // ST7789 needs twice
|
||||
return ID;
|
||||
}
|
||||
|
||||
@@ -326,9 +325,9 @@ NodeDB::NodeDB()
|
||||
// Uncomment below to always enable UDP broadcasts
|
||||
// config.network.enabled_protocols = meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST;
|
||||
|
||||
// If we are setup to broadcast on the default channel, ensure that the telemetry intervals are coerced to the minimum value
|
||||
// of 30 minutes or more
|
||||
if (channels.isDefaultChannel(channels.getPrimaryIndex())) {
|
||||
// If we are setup to broadcast on any default channel slot (with default frequency slot semantics),
|
||||
// ensure that the telemetry intervals are coerced to the minimum value of 30 minutes or more.
|
||||
if (channels.hasDefaultChannel()) {
|
||||
LOG_DEBUG("Coerce telemetry to min of 30 minutes on defaults");
|
||||
moduleConfig.telemetry.device_update_interval = Default::getConfiguredOrMinimumValue(
|
||||
moduleConfig.telemetry.device_update_interval, min_default_telemetry_interval_secs);
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
#include "TransmitHistory.h"
|
||||
#include "FSCommon.h"
|
||||
#include "RTC.h"
|
||||
#include "SPILock.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
#ifdef FSCom
|
||||
|
||||
TransmitHistory *transmitHistory = nullptr;
|
||||
|
||||
TransmitHistory *TransmitHistory::getInstance()
|
||||
{
|
||||
if (!transmitHistory) {
|
||||
transmitHistory = new TransmitHistory();
|
||||
}
|
||||
return transmitHistory;
|
||||
}
|
||||
|
||||
void TransmitHistory::loadFromDisk()
|
||||
{
|
||||
spiLock->lock();
|
||||
auto file = FSCom.open(FILENAME, FILE_O_READ);
|
||||
if (file) {
|
||||
FileHeader header{};
|
||||
if (file.read((uint8_t *)&header, sizeof(header)) == sizeof(header) && header.magic == MAGIC &&
|
||||
header.version == VERSION && header.count <= MAX_ENTRIES) {
|
||||
for (uint8_t i = 0; i < header.count; i++) {
|
||||
Entry entry{};
|
||||
if (file.read((uint8_t *)&entry, sizeof(entry)) == sizeof(entry)) {
|
||||
if (entry.epochSeconds > 0) {
|
||||
history[entry.key] = entry.epochSeconds;
|
||||
// Seed in-memory millis so throttle works even without RTC/GPS.
|
||||
// Treating stored entries as "just sent" is safe — worst case the
|
||||
// node waits one full interval before its first broadcast.
|
||||
lastMillis[entry.key] = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG_INFO("TransmitHistory: loaded %u entries from disk", header.count);
|
||||
} else {
|
||||
LOG_WARN("TransmitHistory: invalid file header, starting fresh");
|
||||
}
|
||||
file.close();
|
||||
} else {
|
||||
LOG_INFO("TransmitHistory: no history file found, starting fresh");
|
||||
}
|
||||
spiLock->unlock();
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
void TransmitHistory::setLastSentToMesh(uint16_t key)
|
||||
{
|
||||
lastMillis[key] = millis();
|
||||
uint32_t now = getTime();
|
||||
if (now >= 2) {
|
||||
history[key] = now;
|
||||
dirty = true;
|
||||
// Don't flush to disk on every transmit — flash has limited write endurance.
|
||||
// The in-memory lastMillis map handles throttle during normal operation.
|
||||
// Disk is flushed: before deep sleep (sleep.cpp) and periodically here,
|
||||
// throttled to at most once per 5 minutes. Always save the first time
|
||||
// after boot so a crash-reboot loop can't avoid persisting.
|
||||
if (lastDiskSave == 0 || !Throttle::isWithinTimespanMs(lastDiskSave, SAVE_INTERVAL_MS)) {
|
||||
if (saveToDisk()) {
|
||||
lastDiskSave = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t TransmitHistory::getLastSentToMeshEpoch(uint16_t key) const
|
||||
{
|
||||
auto it = history.find(key);
|
||||
if (it != history.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const
|
||||
{
|
||||
// Prefer runtime millis value (accurate within this boot)
|
||||
auto mit = lastMillis.find(key);
|
||||
if (mit != lastMillis.end()) {
|
||||
return mit->second;
|
||||
}
|
||||
|
||||
// Fall back to epoch conversion (loaded from disk after reboot)
|
||||
uint32_t storedEpoch = getLastSentToMeshEpoch(key);
|
||||
if (storedEpoch == 0) {
|
||||
return 0; // No stored time — module has never sent
|
||||
}
|
||||
|
||||
uint32_t now = getTime();
|
||||
if (now < 2) {
|
||||
// No valid RTC time yet — can't convert to millis. Return 0 so throttle doesn't block.
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (storedEpoch > now) {
|
||||
// Stored time is in the future (clock went backwards?) — treat as stale
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t secondsAgo = now - storedEpoch;
|
||||
uint32_t msAgo = secondsAgo * 1000;
|
||||
|
||||
// Guard against overflow: if the transmit was very long ago, just return 0 (won't throttle)
|
||||
if (secondsAgo > 86400 || msAgo / 1000 != secondsAgo) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Convert to a millis()-relative timestamp: millis() - msAgo
|
||||
// This gives a value that, when passed to Throttle::isWithinTimespanMs(value, interval),
|
||||
// correctly reports whether the transmit was within interval ms.
|
||||
return millis() - msAgo;
|
||||
}
|
||||
|
||||
bool TransmitHistory::saveToDisk()
|
||||
{
|
||||
if (!dirty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
spiLock->lock();
|
||||
|
||||
FSCom.mkdir("/prefs");
|
||||
|
||||
// Remove old file first
|
||||
if (FSCom.exists(FILENAME)) {
|
||||
FSCom.remove(FILENAME);
|
||||
}
|
||||
|
||||
auto file = FSCom.open(FILENAME, FILE_O_WRITE);
|
||||
if (file) {
|
||||
FileHeader header{};
|
||||
header.magic = MAGIC;
|
||||
header.version = VERSION;
|
||||
header.count = (uint8_t)min((size_t)MAX_ENTRIES, history.size());
|
||||
|
||||
file.write((uint8_t *)&header, sizeof(header));
|
||||
|
||||
uint8_t written = 0;
|
||||
for (auto &pair : history) {
|
||||
if (written >= MAX_ENTRIES)
|
||||
break;
|
||||
Entry entry{};
|
||||
entry.key = pair.first;
|
||||
entry.epochSeconds = pair.second;
|
||||
file.write((uint8_t *)&entry, sizeof(entry));
|
||||
written++;
|
||||
}
|
||||
file.flush();
|
||||
file.close();
|
||||
LOG_DEBUG("TransmitHistory: saved %u entries to disk", written);
|
||||
dirty = false;
|
||||
spiLock->unlock();
|
||||
return true;
|
||||
} else {
|
||||
LOG_WARN("TransmitHistory: failed to open file for writing");
|
||||
}
|
||||
|
||||
spiLock->unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
// No filesystem available — provide stub with in-memory tracking
|
||||
TransmitHistory *transmitHistory = nullptr;
|
||||
|
||||
TransmitHistory *TransmitHistory::getInstance()
|
||||
{
|
||||
if (!transmitHistory) {
|
||||
transmitHistory = new TransmitHistory();
|
||||
}
|
||||
return transmitHistory;
|
||||
}
|
||||
|
||||
void TransmitHistory::loadFromDisk() {}
|
||||
|
||||
void TransmitHistory::setLastSentToMesh(uint16_t key)
|
||||
{
|
||||
lastMillis[key] = millis();
|
||||
}
|
||||
|
||||
uint32_t TransmitHistory::getLastSentToMeshEpoch(uint16_t key) const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t TransmitHistory::getLastSentToMeshMillis(uint16_t key) const
|
||||
{
|
||||
auto mit = lastMillis.find(key);
|
||||
return (mit != lastMillis.end()) ? mit->second : 0;
|
||||
}
|
||||
|
||||
bool TransmitHistory::saveToDisk()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* TransmitHistory persists the last broadcast transmit time (epoch seconds) per portnum
|
||||
* to the filesystem so that throttle checks survive reboots/crashes.
|
||||
*
|
||||
* On boot, modules call getLastSentToMeshMillis() to recover a millis()-relative timestamp
|
||||
* from the stored epoch time, which plugs directly into existing throttle logic.
|
||||
*
|
||||
* On every broadcast transmit, modules call setLastSentToMesh() which updates the
|
||||
* in-memory cache and flushes to disk.
|
||||
*
|
||||
* Keys are meshtastic_PortNum values (one entry per portnum).
|
||||
*/
|
||||
|
||||
#include "mesh/generated/meshtastic/portnums.pb.h"
|
||||
|
||||
class TransmitHistory
|
||||
{
|
||||
public:
|
||||
static TransmitHistory *getInstance();
|
||||
|
||||
/**
|
||||
* Load persisted transmit times from disk. Call once during init after filesystem is ready.
|
||||
*/
|
||||
void loadFromDisk();
|
||||
|
||||
/**
|
||||
* Record that a broadcast was sent for the given key right now.
|
||||
* Stores epoch seconds and flushes to disk.
|
||||
*/
|
||||
void setLastSentToMesh(uint16_t key);
|
||||
|
||||
/**
|
||||
* Get the last transmit epoch seconds for a given key, or 0 if unknown.
|
||||
*/
|
||||
uint32_t getLastSentToMeshEpoch(uint16_t key) const;
|
||||
|
||||
/**
|
||||
* Convert a stored epoch timestamp into a millis()-relative timestamp suitable
|
||||
* for use with Throttle::isWithinTimespanMs().
|
||||
*
|
||||
* Returns 0 if no valid time is stored or if the stored time is in the future
|
||||
* (which shouldn't happen but guards against clock weirdness).
|
||||
*
|
||||
* Example: if the stored epoch is 300 seconds ago, and millis() is currently 10000,
|
||||
* this returns 10000 - 300000 (wrapped appropriately for uint32_t arithmetic).
|
||||
*/
|
||||
uint32_t getLastSentToMeshMillis(uint16_t key) const;
|
||||
|
||||
/**
|
||||
* Flush dirty entries to disk. Called periodically or on demand.
|
||||
*
|
||||
* @return true if the data is persisted (or there was nothing to write), false on write/open failure.
|
||||
*/
|
||||
bool saveToDisk();
|
||||
|
||||
private:
|
||||
TransmitHistory() = default;
|
||||
|
||||
static constexpr const char *FILENAME = "/prefs/transmit_history.dat";
|
||||
static constexpr uint32_t MAGIC = 0x54485354; // "THST"
|
||||
static constexpr uint8_t VERSION = 1;
|
||||
static constexpr uint8_t MAX_ENTRIES = 16;
|
||||
static constexpr uint32_t SAVE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
struct __attribute__((packed)) Entry {
|
||||
uint16_t key;
|
||||
uint32_t epochSeconds;
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) FileHeader {
|
||||
uint32_t magic;
|
||||
uint8_t version;
|
||||
uint8_t count;
|
||||
};
|
||||
|
||||
std::map<uint16_t, uint32_t> history; // key -> epoch seconds (for disk persistence)
|
||||
std::map<uint16_t, uint32_t> lastMillis; // key -> millis() value (for runtime throttle)
|
||||
bool dirty = false;
|
||||
uint32_t lastDiskSave = 0; // millis() of last disk flush
|
||||
};
|
||||
|
||||
extern TransmitHistory *transmitHistory;
|
||||
@@ -81,7 +81,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType {
|
||||
/* TODO: REPLACE */
|
||||
meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13,
|
||||
/* Traffic management module config */
|
||||
meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG = 14
|
||||
meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG = 14,
|
||||
/* TAK module config */
|
||||
meshtastic_AdminMessage_ModuleConfigType_TAK_CONFIG = 15
|
||||
} meshtastic_AdminMessage_ModuleConfigType;
|
||||
|
||||
typedef enum _meshtastic_AdminMessage_BackupLocation {
|
||||
@@ -395,8 +397,8 @@ extern "C" {
|
||||
#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1))
|
||||
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_TRAFFICMANAGEMENT_CONFIG+1))
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_TAK_CONFIG
|
||||
#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_TAK_CONFIG+1))
|
||||
|
||||
#define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH
|
||||
#define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD
|
||||
|
||||
@@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
/* meshtastic_NodeDatabase_size depends on runtime parameters */
|
||||
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
|
||||
#define meshtastic_BackupPreferences_size 2419
|
||||
#define meshtastic_BackupPreferences_size 2426
|
||||
#define meshtastic_ChannelFile_size 718
|
||||
#define meshtastic_DeviceState_size 1737
|
||||
#define meshtastic_NodeInfoLite_size 196
|
||||
|
||||
@@ -93,6 +93,9 @@ typedef struct _meshtastic_LocalModuleConfig {
|
||||
/* The part of the config that is specific to the Traffic Management module */
|
||||
bool has_traffic_management;
|
||||
meshtastic_ModuleConfig_TrafficManagementConfig traffic_management;
|
||||
/* TAK Config */
|
||||
bool has_tak;
|
||||
meshtastic_ModuleConfig_TAKConfig tak;
|
||||
} meshtastic_LocalModuleConfig;
|
||||
|
||||
|
||||
@@ -102,9 +105,9 @@ extern "C" {
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default}
|
||||
#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_default}
|
||||
#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_default, false, meshtastic_ModuleConfig_TAKConfig_init_default}
|
||||
#define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero}
|
||||
#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_zero}
|
||||
#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero, false, meshtastic_ModuleConfig_TrafficManagementConfig_init_zero, false, meshtastic_ModuleConfig_TAKConfig_init_zero}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define meshtastic_LocalConfig_device_tag 1
|
||||
@@ -132,6 +135,7 @@ extern "C" {
|
||||
#define meshtastic_LocalModuleConfig_paxcounter_tag 14
|
||||
#define meshtastic_LocalModuleConfig_statusmessage_tag 15
|
||||
#define meshtastic_LocalModuleConfig_traffic_management_tag 16
|
||||
#define meshtastic_LocalModuleConfig_tak_tag 17
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define meshtastic_LocalConfig_FIELDLIST(X, a) \
|
||||
@@ -171,7 +175,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16)
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, tak, 17)
|
||||
#define meshtastic_LocalModuleConfig_CALLBACK NULL
|
||||
#define meshtastic_LocalModuleConfig_DEFAULT NULL
|
||||
#define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig
|
||||
@@ -189,6 +194,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, traffic_management, 16)
|
||||
#define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig
|
||||
#define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig
|
||||
#define meshtastic_LocalModuleConfig_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig
|
||||
#define meshtastic_LocalModuleConfig_tak_MSGTYPE meshtastic_ModuleConfig_TAKConfig
|
||||
|
||||
extern const pb_msgdesc_t meshtastic_LocalConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
@@ -200,7 +206,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size
|
||||
#define meshtastic_LocalConfig_size 751
|
||||
#define meshtastic_LocalModuleConfig_size 813
|
||||
#define meshtastic_LocalModuleConfig_size 820
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -300,6 +300,10 @@ typedef enum _meshtastic_HardwareModel {
|
||||
meshtastic_HardwareModel_TBEAM_1_WATT = 122,
|
||||
/* LilyGo T5 S3 ePaper Pro (V1 and V2) */
|
||||
meshtastic_HardwareModel_T5_S3_EPAPER_PRO = 123,
|
||||
/* LilyGo T-Beam BPF (144-148Mhz) */
|
||||
meshtastic_HardwareModel_TBEAM_BPF = 124,
|
||||
/* LilyGo T-Mini E-paper S3 Kit */
|
||||
meshtastic_HardwareModel_MINI_EPAPER_S3 = 125,
|
||||
/* ------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
|
||||
------------------------------------------------------------------------------------------------------------------------------------------ */
|
||||
|
||||
@@ -57,6 +57,9 @@ PB_BIND(meshtastic_ModuleConfig_AmbientLightingConfig, meshtastic_ModuleConfig_A
|
||||
PB_BIND(meshtastic_ModuleConfig_StatusMessageConfig, meshtastic_ModuleConfig_StatusMessageConfig, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_ModuleConfig_TAKConfig, meshtastic_ModuleConfig_TAKConfig, AUTO)
|
||||
|
||||
|
||||
PB_BIND(meshtastic_RemoteHardwarePin, meshtastic_RemoteHardwarePin, AUTO)
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#ifndef PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED
|
||||
#define PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED
|
||||
#include <pb.h>
|
||||
#include "meshtastic/atak.pb.h"
|
||||
|
||||
#if PB_PROTO_HEADER_VERSION != 40
|
||||
#error Regenerate this file with the current version of nanopb generator.
|
||||
@@ -448,6 +449,16 @@ typedef struct _meshtastic_ModuleConfig_StatusMessageConfig {
|
||||
char node_status[80];
|
||||
} meshtastic_ModuleConfig_StatusMessageConfig;
|
||||
|
||||
/* TAK team/role configuration */
|
||||
typedef struct _meshtastic_ModuleConfig_TAKConfig {
|
||||
/* Team color.
|
||||
Default Unspecifed_Color -> firmware uses Cyan */
|
||||
meshtastic_Team team;
|
||||
/* Member role.
|
||||
Default Unspecifed -> firmware uses TeamMember */
|
||||
meshtastic_MemberRole role;
|
||||
} meshtastic_ModuleConfig_TAKConfig;
|
||||
|
||||
/* A GPIO pin definition for remote hardware module */
|
||||
typedef struct _meshtastic_RemoteHardwarePin {
|
||||
/* GPIO Pin number (must match Arduino) */
|
||||
@@ -503,6 +514,8 @@ typedef struct _meshtastic_ModuleConfig {
|
||||
meshtastic_ModuleConfig_StatusMessageConfig statusmessage;
|
||||
/* Traffic management module config for mesh network optimization */
|
||||
meshtastic_ModuleConfig_TrafficManagementConfig traffic_management;
|
||||
/* TAK team/role configuration for TAK_TRACKER */
|
||||
meshtastic_ModuleConfig_TAKConfig tak;
|
||||
} payload_variant;
|
||||
} meshtastic_ModuleConfig;
|
||||
|
||||
@@ -560,6 +573,9 @@ extern "C" {
|
||||
|
||||
|
||||
|
||||
#define meshtastic_ModuleConfig_TAKConfig_team_ENUMTYPE meshtastic_Team
|
||||
#define meshtastic_ModuleConfig_TAKConfig_role_ENUMTYPE meshtastic_MemberRole
|
||||
|
||||
#define meshtastic_RemoteHardwarePin_type_ENUMTYPE meshtastic_RemoteHardwarePinType
|
||||
|
||||
|
||||
@@ -581,6 +597,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_init_default {""}
|
||||
#define meshtastic_ModuleConfig_TAKConfig_init_default {_meshtastic_Team_MIN, _meshtastic_MemberRole_MIN}
|
||||
#define meshtastic_RemoteHardwarePin_init_default {0, "", _meshtastic_RemoteHardwarePinType_MIN}
|
||||
#define meshtastic_ModuleConfig_init_zero {0, {meshtastic_ModuleConfig_MQTTConfig_init_zero}}
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0, 0, "", 0, 0, false, meshtastic_ModuleConfig_MapReportSettings_init_zero}
|
||||
@@ -599,6 +616,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_init_zero {""}
|
||||
#define meshtastic_ModuleConfig_TAKConfig_init_zero {_meshtastic_Team_MIN, _meshtastic_MemberRole_MIN}
|
||||
#define meshtastic_RemoteHardwarePin_init_zero {0, "", _meshtastic_RemoteHardwarePinType_MIN}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
@@ -717,6 +735,8 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_green_tag 4
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_blue_tag 5
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_node_status_tag 1
|
||||
#define meshtastic_ModuleConfig_TAKConfig_team_tag 1
|
||||
#define meshtastic_ModuleConfig_TAKConfig_role_tag 2
|
||||
#define meshtastic_RemoteHardwarePin_gpio_pin_tag 1
|
||||
#define meshtastic_RemoteHardwarePin_name_tag 2
|
||||
#define meshtastic_RemoteHardwarePin_type_tag 3
|
||||
@@ -738,6 +758,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_paxcounter_tag 13
|
||||
#define meshtastic_ModuleConfig_statusmessage_tag 14
|
||||
#define meshtastic_ModuleConfig_traffic_management_tag 15
|
||||
#define meshtastic_ModuleConfig_tak_tag 16
|
||||
|
||||
/* Struct field encoding specification for nanopb */
|
||||
#define meshtastic_ModuleConfig_FIELDLIST(X, a) \
|
||||
@@ -755,7 +776,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ambient_lighting,payload_var
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,detection_sensor,payload_variant.detection_sensor), 12) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,paxcounter,payload_variant.paxcounter), 13) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,statusmessage,payload_variant.statusmessage), 14) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_variant.traffic_management), 15)
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_variant.traffic_management), 15) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,tak,payload_variant.tak), 16)
|
||||
#define meshtastic_ModuleConfig_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_DEFAULT NULL
|
||||
#define meshtastic_ModuleConfig_payload_variant_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig
|
||||
@@ -773,6 +795,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,traffic_management,payload_v
|
||||
#define meshtastic_ModuleConfig_payload_variant_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig
|
||||
#define meshtastic_ModuleConfig_payload_variant_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig
|
||||
#define meshtastic_ModuleConfig_payload_variant_traffic_management_MSGTYPE meshtastic_ModuleConfig_TrafficManagementConfig
|
||||
#define meshtastic_ModuleConfig_payload_variant_tak_MSGTYPE meshtastic_ModuleConfig_TAKConfig
|
||||
|
||||
#define meshtastic_ModuleConfig_MQTTConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
|
||||
@@ -958,6 +981,12 @@ X(a, STATIC, SINGULAR, STRING, node_status, 1)
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_DEFAULT NULL
|
||||
|
||||
#define meshtastic_ModuleConfig_TAKConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UENUM, team, 1) \
|
||||
X(a, STATIC, SINGULAR, UENUM, role, 2)
|
||||
#define meshtastic_ModuleConfig_TAKConfig_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_TAKConfig_DEFAULT NULL
|
||||
|
||||
#define meshtastic_RemoteHardwarePin_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UINT32, gpio_pin, 1) \
|
||||
X(a, STATIC, SINGULAR, STRING, name, 2) \
|
||||
@@ -982,6 +1011,7 @@ extern const pb_msgdesc_t meshtastic_ModuleConfig_TelemetryConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_CannedMessageConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_AmbientLightingConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_StatusMessageConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_ModuleConfig_TAKConfig_msg;
|
||||
extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
|
||||
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||
@@ -1002,6 +1032,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_fields &meshtastic_ModuleConfig_CannedMessageConfig_msg
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_fields &meshtastic_ModuleConfig_AmbientLightingConfig_msg
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_fields &meshtastic_ModuleConfig_StatusMessageConfig_msg
|
||||
#define meshtastic_ModuleConfig_TAKConfig_fields &meshtastic_ModuleConfig_TAKConfig_msg
|
||||
#define meshtastic_RemoteHardwarePin_fields &meshtastic_RemoteHardwarePin_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
@@ -1020,6 +1051,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
#define meshtastic_ModuleConfig_SerialConfig_size 28
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_size 81
|
||||
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24
|
||||
#define meshtastic_ModuleConfig_TAKConfig_size 4
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_size 50
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_size 52
|
||||
#define meshtastic_ModuleConfig_size 227
|
||||
|
||||
@@ -46,10 +46,6 @@ using namespace httpsserver;
|
||||
|
||||
#include "mesh/http/ContentHandler.h"
|
||||
|
||||
#include <HTTPClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
HTTPClient httpClient;
|
||||
|
||||
#define DEST_FS_USES_LITTLEFS
|
||||
|
||||
// We need to specify some content-type mapping, so the resources get delivered with the
|
||||
@@ -344,11 +340,6 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
|
||||
res->print(jsonString.c_str());
|
||||
|
||||
delete value;
|
||||
|
||||
// Clean up the fileList to prevent memory leak
|
||||
for (auto *val : fileList) {
|
||||
delete val;
|
||||
}
|
||||
}
|
||||
|
||||
void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
|
||||
@@ -543,6 +534,7 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res)
|
||||
if (name != "file") {
|
||||
LOG_DEBUG("Skip unexpected field");
|
||||
res->println("<p>No file found.</p>");
|
||||
delete parser;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -550,6 +542,7 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res)
|
||||
if (filename == "") {
|
||||
LOG_DEBUG("Skip unexpected field");
|
||||
res->println("<p>No file found.</p>");
|
||||
delete parser;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -783,11 +776,6 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
|
||||
std::string jsonString = value->Stringify();
|
||||
res->print(jsonString.c_str());
|
||||
delete value;
|
||||
|
||||
// Clean up the nodesArray to prevent memory leak
|
||||
for (auto *val : nodesArray) {
|
||||
delete val;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -941,10 +929,5 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
|
||||
std::string jsonString = value->Stringify();
|
||||
res->print(jsonString.c_str());
|
||||
delete value;
|
||||
|
||||
// Clean up the networkObjs to prevent memory leak
|
||||
for (auto *val : networkObjs) {
|
||||
delete val;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <HTTPBodyParser.hpp>
|
||||
#include <HTTPMultipartBodyParser.hpp>
|
||||
#include <HTTPURLEncodedBodyParser.hpp>
|
||||
#include <Throttle.h>
|
||||
#include <WebServer.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
@@ -55,6 +56,12 @@ static const int32_t ACTIVE_INTERVAL_MS = 50;
|
||||
static const int32_t MEDIUM_INTERVAL_MS = 200;
|
||||
static const int32_t IDLE_INTERVAL_MS = 1000;
|
||||
|
||||
// Maximum concurrent HTTPS connections (reduced from default 4 to save memory)
|
||||
static const uint8_t MAX_HTTPS_CONNECTIONS = 2;
|
||||
|
||||
// Minimum free heap required for SSL handshake (~40KB for mbedTLS contexts)
|
||||
static const uint32_t MIN_HEAP_FOR_SSL = 40000;
|
||||
|
||||
static SSLCert *cert;
|
||||
static HTTPSServer *secureServer;
|
||||
static HTTPServer *insecureServer;
|
||||
@@ -67,8 +74,20 @@ static void handleWebResponse()
|
||||
if (isWifiAvailable()) {
|
||||
|
||||
if (isWebServerReady) {
|
||||
if (secureServer)
|
||||
secureServer->loop();
|
||||
// Check heap before HTTPS processing - SSL requires significant memory
|
||||
if (secureServer) {
|
||||
uint32_t freeHeap = ESP.getFreeHeap();
|
||||
if (freeHeap >= MIN_HEAP_FOR_SSL) {
|
||||
secureServer->loop();
|
||||
} else {
|
||||
// Skip HTTPS when memory is low to prevent SSL setup failures
|
||||
static uint32_t lastHeapWarning = 0;
|
||||
if (lastHeapWarning == 0 || !Throttle::isWithinTimespanMs(lastHeapWarning, 30000)) {
|
||||
LOG_WARN("Low heap (%u bytes), skipping HTTPS processing", freeHeap);
|
||||
lastHeapWarning = millis();
|
||||
}
|
||||
}
|
||||
}
|
||||
insecureServer->loop();
|
||||
}
|
||||
}
|
||||
@@ -229,7 +248,7 @@ void initWebServer()
|
||||
LOG_DEBUG("Init Web Server");
|
||||
|
||||
// We can now use the new certificate to setup our server as usual.
|
||||
secureServer = new HTTPSServer(cert);
|
||||
secureServer = new HTTPSServer(cert, 443, MAX_HTTPS_CONNECTIONS);
|
||||
insecureServer = new HTTPServer();
|
||||
|
||||
registerHandlers(insecureServer, secureServer);
|
||||
|
||||
@@ -56,7 +56,7 @@ class UdpMulticastHandler final
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
void onReceive(AsyncUDPPacket packet)
|
||||
void onReceive(AsyncUDPPacket &packet)
|
||||
{
|
||||
if (!isRunning) {
|
||||
return;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "NodeStatus.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "TransmitHistory.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include <Throttle.h>
|
||||
@@ -133,11 +134,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
|
||||
|
||||
// Use graduated scaling based on active mesh size (10 minute base, scales with congestion coefficient)
|
||||
uint32_t timeoutMs = Default::getConfiguredOrDefaultMsScaled(0, 10 * 60, nodeStatus->getNumOnline());
|
||||
if (!shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, timeoutMs)) {
|
||||
uint32_t lastNodeInfo = transmitHistory ? transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP) : 0;
|
||||
if (!shorterTimeout && lastNodeInfo && Throttle::isWithinTimespanMs(lastNodeInfo, timeoutMs)) {
|
||||
LOG_DEBUG("Skip send NodeInfo since we sent it <%us ago", timeoutMs / 1000);
|
||||
ignoreRequest = true; // Mark it as ignored for MeshModule
|
||||
return NULL;
|
||||
} else if (shorterTimeout && lastSentToMesh && Throttle::isWithinTimespanMs(lastSentToMesh, 60 * 1000)) {
|
||||
} else if (shorterTimeout && lastNodeInfo && Throttle::isWithinTimespanMs(lastNodeInfo, 60 * 1000)) {
|
||||
// For interactive/urgent requests (e.g., user-triggered or implicit requests), use a shorter 60s timeout
|
||||
LOG_DEBUG("Skip send NodeInfo since we sent it <60s ago");
|
||||
ignoreRequest = true;
|
||||
@@ -159,7 +161,8 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
|
||||
strcpy(u.id, nodeDB->getNodeId().c_str());
|
||||
|
||||
LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name);
|
||||
lastSentToMesh = millis();
|
||||
if (transmitHistory)
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP);
|
||||
return allocDataProtobuf(u);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ class NodeInfoModule : public ProtobufModule<meshtastic_User>, private concurren
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
private:
|
||||
uint32_t lastSentToMesh = 0; // Last time we sent our NodeInfo to the mesh
|
||||
bool shorterTimeout = false;
|
||||
bool suppressReplyForCurrentRequest = false;
|
||||
std::map<NodeNum, uint32_t> lastNodeInfoSeen;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "TransmitHistory.h"
|
||||
#include "TypeConversions.h"
|
||||
#include "airtime.h"
|
||||
#include "configuration.h"
|
||||
@@ -27,6 +28,15 @@ PositionModule::PositionModule()
|
||||
isPromiscuous = true; // We always want to update our nodedb, even if we are sniffing on others
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
|
||||
// Seed throttle timer from persisted transmit history so we don't re-broadcast immediately after reboot
|
||||
if (transmitHistory) {
|
||||
uint32_t restored = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP);
|
||||
if (restored != 0) {
|
||||
lastGpsSend = restored;
|
||||
LOG_INFO("Position: restored lastGpsSend from transmit history");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_TAK_TRACKER) {
|
||||
setIntervalFromNow(setStartDelay());
|
||||
@@ -438,6 +448,8 @@ int32_t PositionModule::runOnce()
|
||||
lastGpsLatitude = node->position.latitude_i;
|
||||
lastGpsLongitude = node->position.longitude_i;
|
||||
|
||||
if (transmitHistory)
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP);
|
||||
sendOurPosition();
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND) {
|
||||
sendLostAndFoundText();
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "PowerFSM.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "TransmitHistory.h"
|
||||
#include "UnitConversions.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
@@ -19,6 +20,8 @@
|
||||
#include "sleep.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
static constexpr uint16_t TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY = 0x8004;
|
||||
|
||||
// Sensors
|
||||
#include "Sensor/AddI2CSensorTemplate.h"
|
||||
#include "Sensor/PMSA003ISensor.h"
|
||||
@@ -108,11 +111,13 @@ int32_t AirQualityTelemetryModule::runOnce()
|
||||
|
||||
// Wake up the sensors that need it
|
||||
LOG_INFO("Waking up sensors...");
|
||||
uint32_t lastTelemetry =
|
||||
transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY) : 0;
|
||||
for (TelemetrySensor *sensor : sensors) {
|
||||
if (!sensor->canSleep()) {
|
||||
LOG_DEBUG("%s sensor doesn't have sleep feature. Skipping", sensor->sensorName);
|
||||
} else if (((lastSentToMesh == 0) ||
|
||||
!Throttle::isWithinTimespanMs(lastSentToMesh - sensor->wakeUpTimeMs(),
|
||||
} else if (((lastTelemetry == 0) ||
|
||||
!Throttle::isWithinTimespanMs(lastTelemetry - sensor->wakeUpTimeMs(),
|
||||
Default::getConfiguredOrDefaultMsScaled(
|
||||
moduleConfig.telemetry.air_quality_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
@@ -131,14 +136,15 @@ int32_t AirQualityTelemetryModule::runOnce()
|
||||
}
|
||||
}
|
||||
|
||||
if (((lastSentToMesh == 0) ||
|
||||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
|
||||
moduleConfig.telemetry.air_quality_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
if (((lastTelemetry == 0) ||
|
||||
!Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled(
|
||||
moduleConfig.telemetry.air_quality_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
|
||||
airTime->isTxAllowedAirUtil()) {
|
||||
sendTelemetry();
|
||||
lastSentToMesh = millis();
|
||||
if (transmitHistory)
|
||||
transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_AIR_QUALITY_TELEMETRY);
|
||||
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
|
||||
(service->isToPhoneQueueEmpty())) {
|
||||
// Just send to phone when it's not our time to send to mesh yet
|
||||
@@ -321,6 +327,10 @@ bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m)
|
||||
meshtastic_MeshPacket *AirQualityTelemetryModule::allocReply()
|
||||
{
|
||||
if (currentRequest) {
|
||||
if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) {
|
||||
ignoreRequest = true;
|
||||
return NULL;
|
||||
}
|
||||
auto req = *currentRequest;
|
||||
const auto &p = req.decoded;
|
||||
meshtastic_Telemetry scratch;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BaseTelemetryModule.h"
|
||||
|
||||
#ifndef AIR_QUALITY_TELEMETRY_MODULE_ENABLE
|
||||
#define AIR_QUALITY_TELEMETRY_MODULE_ENABLE 0
|
||||
#endif
|
||||
@@ -17,6 +19,7 @@
|
||||
|
||||
class AirQualityTelemetryModule : private concurrency::OSThread,
|
||||
public ScanI2CConsumer,
|
||||
public BaseTelemetryModule,
|
||||
public ProtobufModule<meshtastic_Telemetry>
|
||||
{
|
||||
CallbackObserver<AirQualityTelemetryModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
@@ -65,7 +68,6 @@ class AirQualityTelemetryModule : private concurrency::OSThread,
|
||||
meshtastic_MeshPacket *lastMeasurementPacket;
|
||||
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
|
||||
// uint32_t sendToPhoneIntervalMs = 1000; // Send to phone every minute
|
||||
uint32_t lastSentToMesh = 0;
|
||||
uint32_t lastSentToPhone = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "NodeDB.h"
|
||||
#include "configuration.h"
|
||||
|
||||
class BaseTelemetryModule
|
||||
{
|
||||
protected:
|
||||
bool isSensorOrRouterRole() const
|
||||
{
|
||||
return config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR ||
|
||||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER;
|
||||
}
|
||||
};
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "RTC.h"
|
||||
#include "RadioLibInterface.h"
|
||||
#include "Router.h"
|
||||
#include "TransmitHistory.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "memGet.h"
|
||||
@@ -15,21 +16,23 @@
|
||||
#include <meshUtils.h>
|
||||
|
||||
#define MAGIC_USB_BATTERY_LEVEL 101
|
||||
static constexpr uint16_t TX_HISTORY_KEY_DEVICE_TELEMETRY = 0x8001;
|
||||
|
||||
int32_t DeviceTelemetryModule::runOnce()
|
||||
{
|
||||
refreshUptime();
|
||||
bool isImpoliteRole =
|
||||
IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_SENSOR, meshtastic_Config_DeviceConfig_Role_ROUTER);
|
||||
if (((lastSentToMesh == 0) ||
|
||||
((uptimeLastMs - lastSentToMesh) >=
|
||||
Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_DEVICE_TELEMETRY) : 0;
|
||||
bool isImpoliteRole = isSensorOrRouterRole();
|
||||
if (((lastTelemetry == 0) ||
|
||||
((uptimeLastMs - lastTelemetry) >= Default::getConfiguredOrDefaultMsScaled(moduleConfig.telemetry.device_update_interval,
|
||||
default_telemetry_broadcast_interval_secs,
|
||||
numOnlineNodes))) &&
|
||||
airTime->isTxAllowedChannelUtil(!isImpoliteRole) && airTime->isTxAllowedAirUtil() &&
|
||||
config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN &&
|
||||
moduleConfig.telemetry.device_telemetry_enabled) {
|
||||
sendTelemetry();
|
||||
lastSentToMesh = uptimeLastMs;
|
||||
if (transmitHistory)
|
||||
transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_DEVICE_TELEMETRY);
|
||||
} else if (service->isToPhoneQueueEmpty()) {
|
||||
// Just send to phone when it's not our time to send to mesh yet
|
||||
// Only send while queue is empty (phone assumed connected)
|
||||
@@ -60,6 +63,10 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
|
||||
meshtastic_MeshPacket *DeviceTelemetryModule::allocReply()
|
||||
{
|
||||
if (currentRequest) {
|
||||
if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) {
|
||||
ignoreRequest = true;
|
||||
return NULL;
|
||||
}
|
||||
auto req = *currentRequest;
|
||||
const auto &p = req.decoded;
|
||||
meshtastic_Telemetry scratch;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "BaseTelemetryModule.h"
|
||||
#include "NodeDB.h"
|
||||
#include "ProtobufModule.h"
|
||||
#include <OLEDDisplay.h>
|
||||
#include <OLEDDisplayUi.h>
|
||||
|
||||
class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry>
|
||||
class DeviceTelemetryModule : private concurrency::OSThread,
|
||||
public BaseTelemetryModule,
|
||||
public ProtobufModule<meshtastic_Telemetry>
|
||||
{
|
||||
CallbackObserver<DeviceTelemetryModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<DeviceTelemetryModule, const meshtastic::Status *>(this, &DeviceTelemetryModule::handleStatusUpdate);
|
||||
@@ -48,7 +51,6 @@ class DeviceTelemetryModule : private concurrency::OSThread, public ProtobufModu
|
||||
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
|
||||
uint32_t sendStatsToPhoneIntervalMs = 15 * SECONDS_IN_MINUTE * 1000; // Send stats to phone every 15 minutes
|
||||
uint32_t lastSentStatsToPhone = 0;
|
||||
uint32_t lastSentToMesh = 0;
|
||||
|
||||
void refreshUptime()
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "PowerFSM.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "TransmitHistory.h"
|
||||
#include "UnitConversions.h"
|
||||
#include "buzz.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
@@ -145,6 +146,8 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
static constexpr uint16_t TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY = 0x8002;
|
||||
|
||||
void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
|
||||
{
|
||||
if (!moduleConfig.telemetry.environment_measurement_enabled && !ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE) {
|
||||
@@ -297,7 +300,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
// this only works on the wismesh hub with the solar option. This is not an I2C sensor, so we don't need the
|
||||
// sensormap here.
|
||||
#ifdef HAS_RAKPROT
|
||||
result = rak9154Sensor.runOnce();
|
||||
if (rak9154Sensor.hasSensor())
|
||||
result = rak9154Sensor.runOnce();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@@ -317,14 +321,17 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
}
|
||||
}
|
||||
|
||||
if (((lastSentToMesh == 0) ||
|
||||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
|
||||
moduleConfig.telemetry.environment_update_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
uint32_t lastTelemetry =
|
||||
transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY) : 0;
|
||||
if (((lastTelemetry == 0) ||
|
||||
!Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled(
|
||||
moduleConfig.telemetry.environment_update_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
|
||||
airTime->isTxAllowedAirUtil()) {
|
||||
sendTelemetry();
|
||||
lastSentToMesh = millis();
|
||||
if (transmitHistory)
|
||||
transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY);
|
||||
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
|
||||
(service->isToPhoneQueueEmpty())) {
|
||||
// Just send to phone when it's not our time to send to mesh yet
|
||||
@@ -567,9 +574,11 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
|
||||
}
|
||||
#endif
|
||||
#ifdef HAS_RAKPROT
|
||||
get_metrics = rak9154Sensor.getMetrics(m);
|
||||
valid = valid || get_metrics;
|
||||
hasSensor = true;
|
||||
if (rak9154Sensor.hasSensor()) {
|
||||
get_metrics = rak9154Sensor.getMetrics(m);
|
||||
valid = valid || get_metrics;
|
||||
hasSensor = true;
|
||||
}
|
||||
#endif
|
||||
return valid && hasSensor;
|
||||
}
|
||||
@@ -577,6 +586,10 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
|
||||
meshtastic_MeshPacket *EnvironmentTelemetryModule::allocReply()
|
||||
{
|
||||
if (currentRequest) {
|
||||
if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) {
|
||||
ignoreRequest = true;
|
||||
return NULL;
|
||||
}
|
||||
auto req = *currentRequest;
|
||||
const auto &p = req.decoded;
|
||||
meshtastic_Telemetry scratch;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BaseTelemetryModule.h"
|
||||
|
||||
#ifndef ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE
|
||||
#define ENVIRONMENTAL_TELEMETRY_MODULE_ENABLE 0
|
||||
#endif
|
||||
@@ -17,6 +19,7 @@
|
||||
|
||||
class EnvironmentTelemetryModule : private concurrency::OSThread,
|
||||
public ScanI2CConsumer,
|
||||
public BaseTelemetryModule,
|
||||
public ProtobufModule<meshtastic_Telemetry>
|
||||
{
|
||||
CallbackObserver<EnvironmentTelemetryModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
@@ -65,7 +68,6 @@ class EnvironmentTelemetryModule : private concurrency::OSThread,
|
||||
bool firstTime = 1;
|
||||
meshtastic_MeshPacket *lastMeasurementPacket;
|
||||
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
|
||||
uint32_t lastSentToMesh = 0;
|
||||
uint32_t lastSentToPhone = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "PowerFSM.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "TransmitHistory.h"
|
||||
#include "UnitConversions.h"
|
||||
#include "main.h"
|
||||
#include "power.h"
|
||||
@@ -33,6 +34,8 @@ MLX90614Sensor mlx90614Sensor;
|
||||
#endif
|
||||
#include <Throttle.h>
|
||||
|
||||
static constexpr uint16_t TX_HISTORY_KEY_HEALTH_TELEMETRY = 0x8003;
|
||||
|
||||
int32_t HealthTelemetryModule::runOnce()
|
||||
{
|
||||
if (sleepOnNextExecution == true) {
|
||||
@@ -69,14 +72,16 @@ int32_t HealthTelemetryModule::runOnce()
|
||||
return disable();
|
||||
}
|
||||
|
||||
if (((lastSentToMesh == 0) ||
|
||||
!Throttle::isWithinTimespanMs(lastSentToMesh, Default::getConfiguredOrDefaultMsScaled(
|
||||
moduleConfig.telemetry.health_update_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_HEALTH_TELEMETRY) : 0;
|
||||
if (((lastTelemetry == 0) ||
|
||||
!Throttle::isWithinTimespanMs(lastTelemetry, Default::getConfiguredOrDefaultMsScaled(
|
||||
moduleConfig.telemetry.health_update_interval,
|
||||
default_telemetry_broadcast_interval_secs, numOnlineNodes))) &&
|
||||
airTime->isTxAllowedChannelUtil(config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
|
||||
airTime->isTxAllowedAirUtil()) {
|
||||
sendTelemetry();
|
||||
lastSentToMesh = millis();
|
||||
if (transmitHistory)
|
||||
transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_HEALTH_TELEMETRY);
|
||||
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
|
||||
(service->isToPhoneQueueEmpty())) {
|
||||
// Just send to phone when it's not our time to send to mesh yet
|
||||
@@ -192,6 +197,10 @@ bool HealthTelemetryModule::getHealthTelemetry(meshtastic_Telemetry *m)
|
||||
meshtastic_MeshPacket *HealthTelemetryModule::allocReply()
|
||||
{
|
||||
if (currentRequest) {
|
||||
if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) {
|
||||
ignoreRequest = true;
|
||||
return NULL;
|
||||
}
|
||||
auto req = *currentRequest;
|
||||
const auto &p = req.decoded;
|
||||
meshtastic_Telemetry scratch;
|
||||
|
||||
@@ -4,12 +4,15 @@
|
||||
|
||||
#pragma once
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "BaseTelemetryModule.h"
|
||||
#include "NodeDB.h"
|
||||
#include "ProtobufModule.h"
|
||||
#include <OLEDDisplay.h>
|
||||
#include <OLEDDisplayUi.h>
|
||||
|
||||
class HealthTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry>
|
||||
class HealthTelemetryModule : private concurrency::OSThread,
|
||||
public BaseTelemetryModule,
|
||||
public ProtobufModule<meshtastic_Telemetry>
|
||||
{
|
||||
CallbackObserver<HealthTelemetryModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<HealthTelemetryModule, const meshtastic::Status *>(this, &HealthTelemetryModule::handleStatusUpdate);
|
||||
@@ -52,7 +55,6 @@ class HealthTelemetryModule : private concurrency::OSThread, public ProtobufModu
|
||||
bool firstTime = 1;
|
||||
meshtastic_MeshPacket *lastMeasurementPacket;
|
||||
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
|
||||
uint32_t lastSentToMesh = 0;
|
||||
uint32_t lastSentToPhone = 0;
|
||||
uint32_t sensor_read_error_count = 0;
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "PowerTelemetry.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "TransmitHistory.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "main.h"
|
||||
#include "power.h"
|
||||
@@ -22,6 +23,8 @@
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
static constexpr uint16_t TX_HISTORY_KEY_POWER_TELEMETRY = 0x8005;
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert,
|
||||
@@ -88,10 +91,12 @@ int32_t PowerTelemetryModule::runOnce()
|
||||
if (!moduleConfig.telemetry.power_measurement_enabled)
|
||||
return disable();
|
||||
|
||||
if (((lastSentToMesh == 0) || !Throttle::isWithinTimespanMs(lastSentToMesh, sendToMeshIntervalMs)) &&
|
||||
uint32_t lastTelemetry = transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_POWER_TELEMETRY) : 0;
|
||||
if (((lastTelemetry == 0) || !Throttle::isWithinTimespanMs(lastTelemetry, sendToMeshIntervalMs)) &&
|
||||
airTime->isTxAllowedAirUtil()) {
|
||||
sendTelemetry();
|
||||
lastSentToMesh = millis();
|
||||
if (transmitHistory)
|
||||
transmitHistory->setLastSentToMesh(TX_HISTORY_KEY_POWER_TELEMETRY);
|
||||
} else if (((lastSentToPhone == 0) || !Throttle::isWithinTimespanMs(lastSentToPhone, sendToPhoneIntervalMs)) &&
|
||||
(service->isToPhoneQueueEmpty())) {
|
||||
// Just send to phone when it's not our time to send to mesh yet
|
||||
@@ -217,6 +222,10 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m)
|
||||
meshtastic_MeshPacket *PowerTelemetryModule::allocReply()
|
||||
{
|
||||
if (currentRequest) {
|
||||
if (isMultiHopBroadcastRequest() && !isSensorOrRouterRole()) {
|
||||
ignoreRequest = true;
|
||||
return NULL;
|
||||
}
|
||||
auto req = *currentRequest;
|
||||
const auto &p = req.decoded;
|
||||
meshtastic_Telemetry scratch;
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "BaseTelemetryModule.h"
|
||||
#include "NodeDB.h"
|
||||
#include "ProtobufModule.h"
|
||||
#include <OLEDDisplay.h>
|
||||
#include <OLEDDisplayUi.h>
|
||||
|
||||
class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModule<meshtastic_Telemetry>
|
||||
class PowerTelemetryModule : private concurrency::OSThread,
|
||||
public BaseTelemetryModule,
|
||||
public ProtobufModule<meshtastic_Telemetry>
|
||||
{
|
||||
CallbackObserver<PowerTelemetryModule, const meshtastic::Status *> nodeStatusObserver =
|
||||
CallbackObserver<PowerTelemetryModule, const meshtastic::Status *>(this, &PowerTelemetryModule::handleStatusUpdate);
|
||||
@@ -51,7 +54,6 @@ class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModul
|
||||
bool firstTime = 1;
|
||||
meshtastic_MeshPacket *lastMeasurementPacket;
|
||||
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
|
||||
uint32_t lastSentToMesh = 0;
|
||||
uint32_t lastSentToPhone = 0;
|
||||
uint32_t sensor_read_error_count = 0;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ class RAK9154Sensor : public TelemetrySensor, VoltageSensor, CurrentSensor
|
||||
|
||||
public:
|
||||
RAK9154Sensor();
|
||||
bool hasSensor() { return true; } // Not an I2C sensor; always available when HAS_RAKPROT is defined
|
||||
virtual int32_t runOnce() override;
|
||||
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
|
||||
virtual uint16_t getBusVoltageMv() override;
|
||||
|
||||
+8
-3
@@ -53,6 +53,9 @@ static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for cha
|
||||
static bool isMqttServerAddressPrivate = false;
|
||||
static bool isConnected = false;
|
||||
|
||||
static uint32_t lastPositionUnavailableWarning = 0;
|
||||
static const uint32_t POSITION_UNAVAILABLE_WARNING_INTERVAL_MS = 15000; // 15 seconds
|
||||
|
||||
inline void onReceiveProto(char *topic, byte *payload, size_t length)
|
||||
{
|
||||
const DecodedServiceEnvelope e(payload, length);
|
||||
@@ -845,12 +848,14 @@ void MQTT::perhapsReportToMap()
|
||||
map_position_precision = default_map_position_precision;
|
||||
}
|
||||
|
||||
if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs))
|
||||
if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs) && last_report_to_map != 0)
|
||||
return;
|
||||
|
||||
if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) {
|
||||
last_report_to_map = millis();
|
||||
LOG_WARN("MQTT Map report enabled, but no position available");
|
||||
if (Throttle::isWithinTimespanMs(lastPositionUnavailableWarning, POSITION_UNAVAILABLE_WARNING_INTERVAL_MS) == false) {
|
||||
LOG_WARN("MQTT Map report enabled, but no position available");
|
||||
lastPositionUnavailableWarning = millis();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,20 @@ NimBLEServer *bleServer;
|
||||
static bool passkeyShowing;
|
||||
static std::atomic<uint16_t> nimbleBluetoothConnHandle{BLE_HS_CONN_HANDLE_NONE}; // BLE_HS_CONN_HANDLE_NONE means "no connection"
|
||||
|
||||
static void clearPairingDisplay()
|
||||
{
|
||||
if (!passkeyShowing) {
|
||||
return;
|
||||
}
|
||||
|
||||
passkeyShowing = false;
|
||||
#if HAS_SCREEN
|
||||
if (screen) {
|
||||
screen->endAlert();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
||||
{
|
||||
/*
|
||||
@@ -630,13 +644,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
|
||||
|
||||
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
|
||||
bluetoothStatus->updateStatus(&newStatus);
|
||||
|
||||
// Todo: migrate this display code back into Screen class, and observe bluetoothStatus
|
||||
if (passkeyShowing) {
|
||||
passkeyShowing = false;
|
||||
if (screen)
|
||||
screen->endAlert();
|
||||
}
|
||||
clearPairingDisplay();
|
||||
|
||||
// Store the connection handle for future use
|
||||
#ifdef NIMBLE_TWO
|
||||
@@ -693,6 +701,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
|
||||
|
||||
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
|
||||
bluetoothStatus->updateStatus(&newStatus);
|
||||
clearPairingDisplay();
|
||||
|
||||
if (bluetoothPhoneAPI) {
|
||||
bluetoothPhoneAPI->close();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@file BLEDfu.cpp
|
||||
@file BLEDfuSecure.cpp
|
||||
@author hathach (tinyusb.org)
|
||||
|
||||
@section LICENSE
|
||||
@@ -1,6 +1,6 @@
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@file BLEDfu.h
|
||||
@file BLEDfuSecure.h
|
||||
@author hathach (tinyusb.org)
|
||||
|
||||
@section LICENSE
|
||||
|
||||
@@ -32,6 +32,7 @@ static uint8_t toRadioBytes[meshtastic_ToRadio_size];
|
||||
static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
|
||||
|
||||
static uint16_t connectionHandle;
|
||||
static bool passkeyShowing;
|
||||
|
||||
class BluetoothPhoneAPI : public PhoneAPI
|
||||
{
|
||||
@@ -86,6 +87,16 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason)
|
||||
// Notify UI (or any other interested firmware components)
|
||||
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
|
||||
bluetoothStatus->updateStatus(&newStatus);
|
||||
|
||||
#if HAS_SCREEN
|
||||
// If a pairing prompt is active, make sure we dismiss it on disconnect/cancel/failure paths.
|
||||
if (passkeyShowing) {
|
||||
passkeyShowing = false;
|
||||
if (screen) {
|
||||
screen->endAlert();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value)
|
||||
{
|
||||
@@ -400,6 +411,8 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke
|
||||
});
|
||||
}
|
||||
#endif
|
||||
passkeyShowing = true;
|
||||
|
||||
if (match_request) {
|
||||
uint32_t start_time = millis();
|
||||
while (millis() < start_time + 30000) {
|
||||
@@ -451,6 +464,7 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu
|
||||
}
|
||||
|
||||
// Todo: migrate this display code back into Screen class, and observe bluetoothStatus
|
||||
passkeyShowing = false;
|
||||
if (screen) {
|
||||
screen->endAlert();
|
||||
}
|
||||
@@ -464,4 +478,4 @@ void NRF52Bluetooth::sendLog(const uint8_t *logMessage, size_t length)
|
||||
logRadio.indicate(logMessage, (uint16_t)length);
|
||||
else
|
||||
logRadio.notify(logMessage, (uint16_t)length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,6 +537,27 @@ void portduinoSetup()
|
||||
}
|
||||
}
|
||||
|
||||
// In one test, this dance seemed necessary to trigger the pin to detect properly.
|
||||
if (portduino_config.lora_pa_detect_pin.enabled) {
|
||||
pinMode(portduino_config.lora_pa_detect_pin.pin, INPUT_PULLDOWN);
|
||||
sleep(1);
|
||||
if (digitalRead(portduino_config.lora_pa_detect_pin.pin) == LOW) {
|
||||
std::cout << "Pin " << portduino_config.lora_pa_detect_pin.pin << " PULLDOWN is LOW" << std::endl;
|
||||
}
|
||||
pinMode(portduino_config.lora_pa_detect_pin.pin, INPUT_PULLUP);
|
||||
sleep(1);
|
||||
if (digitalRead(portduino_config.lora_pa_detect_pin.pin) == HIGH) {
|
||||
std::cout << "Pin " << portduino_config.lora_pa_detect_pin.pin << " PULLUP is HIGH, dropping PA curve" << std::endl;
|
||||
portduino_config.num_pa_points = 1;
|
||||
portduino_config.tx_gain_lora[0] = 0;
|
||||
} else {
|
||||
std::cout << "Pin " << portduino_config.lora_pa_detect_pin.pin << " PULLUP is LOW, using PA curve" << std::endl;
|
||||
}
|
||||
|
||||
// disable bias once finished
|
||||
pinMode(portduino_config.lora_pa_detect_pin.pin, INPUT);
|
||||
}
|
||||
|
||||
// Only initialize the radio pins when dealing with real, kernel controlled SPI hardware
|
||||
if (portduino_config.lora_spi_dev != "" && portduino_config.lora_spi_dev != "ch341") {
|
||||
SPI.begin(portduino_config.lora_spi_dev.c_str());
|
||||
|
||||
@@ -107,6 +107,7 @@ extern struct portduino_config_struct {
|
||||
pinMapping lora_txen_pin = {"Lora", "TXen"};
|
||||
pinMapping lora_rxen_pin = {"Lora", "RXen"};
|
||||
pinMapping lora_sx126x_ant_sw_pin = {"Lora", "SX126X_ANT_SW"};
|
||||
pinMapping lora_pa_detect_pin = {"Lora", "GPIO_DETECT_PA"};
|
||||
std::vector<pinMapping> extra_pins = {};
|
||||
|
||||
// GPS
|
||||
@@ -194,13 +195,14 @@ extern struct portduino_config_struct {
|
||||
int maxtophone = 100;
|
||||
int MaxNodes = 200;
|
||||
|
||||
pinMapping *all_pins[20] = {&lora_cs_pin,
|
||||
pinMapping *all_pins[21] = {&lora_cs_pin,
|
||||
&lora_irq_pin,
|
||||
&lora_busy_pin,
|
||||
&lora_reset_pin,
|
||||
&lora_txen_pin,
|
||||
&lora_rxen_pin,
|
||||
&lora_sx126x_ant_sw_pin,
|
||||
&lora_pa_detect_pin,
|
||||
&displayDC,
|
||||
&displayCS,
|
||||
&displayBacklight,
|
||||
@@ -255,18 +257,23 @@ extern struct portduino_config_struct {
|
||||
out << YAML::Key << "TX_GAIN_LORA" << YAML::Value << tx_gain_lora[0];
|
||||
}
|
||||
|
||||
out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch;
|
||||
if (dio2_as_rf_switch)
|
||||
out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch;
|
||||
if (dio3_tcxo_voltage != 0)
|
||||
out << YAML::Key << "DIO3_TCXO_VOLTAGE" << YAML::Value << YAML::Precision(3) << (float)dio3_tcxo_voltage / 1000;
|
||||
if (lora_usb_pid != 0x5512)
|
||||
out << YAML::Key << "USB_PID" << YAML::Value << YAML::Hex << lora_usb_pid;
|
||||
if (lora_usb_vid != 0x1A86)
|
||||
out << YAML::Key << "USB_VID" << YAML::Value << YAML::Hex << lora_usb_vid;
|
||||
if (lora_spi_dev != "")
|
||||
if (lora_spi_dev != "" && !(lora_spi_dev == "/dev/spidev0.0" && lora_module == use_autoconf)) {
|
||||
if (lora_spi_dev.find("/dev/") != std::string::npos)
|
||||
lora_spi_dev = lora_spi_dev.substr(5);
|
||||
out << YAML::Key << "spidev" << YAML::Value << lora_spi_dev;
|
||||
}
|
||||
if (lora_usb_serial_num != "")
|
||||
out << YAML::Key << "USB_Serialnum" << YAML::Value << lora_usb_serial_num;
|
||||
out << YAML::Key << "spiSpeed" << YAML::Value << spiSpeed;
|
||||
if (spiSpeed != 2000000)
|
||||
out << YAML::Key << "spiSpeed" << YAML::Value << spiSpeed;
|
||||
if (rfswitch_dio_pins[0] != RADIOLIB_NC) {
|
||||
out << YAML::Key << "rfswitch_table" << YAML::Value << YAML::BeginMap;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerMon.h"
|
||||
#include "TransmitHistory.h"
|
||||
#include "detect/LoRaRadioType.h"
|
||||
#include "error.h"
|
||||
#include "main.h"
|
||||
@@ -245,6 +246,10 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
|
||||
nodeDB->saveToDisk();
|
||||
}
|
||||
|
||||
// Persist broadcast transmit times so throttle survives reboot
|
||||
if (transmitHistory)
|
||||
transmitHistory->saveToDisk();
|
||||
|
||||
#ifdef PIN_POWER_EN
|
||||
digitalWrite(PIN_POWER_EN, LOW);
|
||||
pinMode(PIN_POWER_EN, INPUT); // power off peripherals
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "TestUtil.h"
|
||||
#include <unity.h>
|
||||
|
||||
static void test_placeholder()
|
||||
{
|
||||
TEST_ASSERT_TRUE(true);
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
void setup()
|
||||
{
|
||||
initializeTestEnvironment();
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_placeholder);
|
||||
exit(UNITY_END());
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "MeshModule.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "TestUtil.h"
|
||||
#include <unity.h>
|
||||
|
||||
// Minimal concrete subclass for testing the base class helper
|
||||
class TestModule : public MeshModule
|
||||
{
|
||||
public:
|
||||
TestModule() : MeshModule("TestModule") {}
|
||||
virtual bool wantPacket(const meshtastic_MeshPacket *p) override { return true; }
|
||||
using MeshModule::currentRequest;
|
||||
using MeshModule::isMultiHopBroadcastRequest;
|
||||
};
|
||||
|
||||
static TestModule *testModule;
|
||||
static meshtastic_MeshPacket testPacket;
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
testModule = new TestModule();
|
||||
memset(&testPacket, 0, sizeof(testPacket));
|
||||
TestModule::currentRequest = &testPacket;
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
TestModule::currentRequest = NULL;
|
||||
delete testModule;
|
||||
}
|
||||
|
||||
// Zero-hop broadcast (hop_limit == hop_start): should be allowed
|
||||
static void test_zeroHopBroadcast_isAllowed()
|
||||
{
|
||||
testPacket.to = NODENUM_BROADCAST;
|
||||
testPacket.hop_start = 3;
|
||||
testPacket.hop_limit = 3; // Not yet relayed
|
||||
|
||||
TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest());
|
||||
}
|
||||
|
||||
// Multi-hop broadcast (hop_limit < hop_start): should be blocked
|
||||
static void test_multiHopBroadcast_isBlocked()
|
||||
{
|
||||
testPacket.to = NODENUM_BROADCAST;
|
||||
testPacket.hop_start = 7;
|
||||
testPacket.hop_limit = 4; // Already relayed 3 hops
|
||||
|
||||
TEST_ASSERT_TRUE(testModule->isMultiHopBroadcastRequest());
|
||||
}
|
||||
|
||||
// Direct message (not broadcast): should always be allowed regardless of hops
|
||||
static void test_directMessage_isAllowed()
|
||||
{
|
||||
testPacket.to = 0x12345678; // Specific node
|
||||
testPacket.hop_start = 7;
|
||||
testPacket.hop_limit = 4;
|
||||
|
||||
TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest());
|
||||
}
|
||||
|
||||
// Broadcast with hop_limit == 0 (fully relayed): should be blocked
|
||||
static void test_fullyRelayedBroadcast_isBlocked()
|
||||
{
|
||||
testPacket.to = NODENUM_BROADCAST;
|
||||
testPacket.hop_start = 3;
|
||||
testPacket.hop_limit = 0;
|
||||
|
||||
TEST_ASSERT_TRUE(testModule->isMultiHopBroadcastRequest());
|
||||
}
|
||||
|
||||
// No current request: should not crash, should return false
|
||||
static void test_noCurrentRequest_isAllowed()
|
||||
{
|
||||
TestModule::currentRequest = NULL;
|
||||
|
||||
TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest());
|
||||
}
|
||||
|
||||
// Broadcast with hop_start == 0 (legacy or local): should be allowed
|
||||
static void test_legacyPacket_zeroHopStart_isAllowed()
|
||||
{
|
||||
testPacket.to = NODENUM_BROADCAST;
|
||||
testPacket.hop_start = 0;
|
||||
testPacket.hop_limit = 0;
|
||||
|
||||
// hop_limit == hop_start, so not multi-hop
|
||||
TEST_ASSERT_FALSE(testModule->isMultiHopBroadcastRequest());
|
||||
}
|
||||
|
||||
// Single hop relayed broadcast (hop_limit = hop_start - 1): should be blocked
|
||||
static void test_singleHopRelayedBroadcast_isBlocked()
|
||||
{
|
||||
testPacket.to = NODENUM_BROADCAST;
|
||||
testPacket.hop_start = 3;
|
||||
testPacket.hop_limit = 2;
|
||||
|
||||
TEST_ASSERT_TRUE(testModule->isMultiHopBroadcastRequest());
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
initializeTestEnvironment();
|
||||
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_zeroHopBroadcast_isAllowed);
|
||||
RUN_TEST(test_multiHopBroadcast_isBlocked);
|
||||
RUN_TEST(test_directMessage_isAllowed);
|
||||
RUN_TEST(test_fullyRelayedBroadcast_isBlocked);
|
||||
RUN_TEST(test_noCurrentRequest_isAllowed);
|
||||
RUN_TEST(test_legacyPacket_zeroHopStart_isAllowed);
|
||||
RUN_TEST(test_singleHopRelayedBroadcast_isBlocked);
|
||||
exit(UNITY_END());
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
@@ -334,7 +334,7 @@ void setUp(void)
|
||||
owner = meshtastic_User{.id = "!12345678"};
|
||||
myNodeInfo = meshtastic_MyNodeInfo{.my_node_num = 0x12345678}; // Match the expected gateway ID in topic
|
||||
localPosition =
|
||||
meshtastic_Position{.has_latitude_i = true, .latitude_i = 7 * 1e7, .has_longitude_i = true, .longitude_i = 3 * 1e7};
|
||||
meshtastic_Position{.has_latitude_i = true, .latitude_i = 700000000, .has_longitude_i = true, .longitude_i = 300000000};
|
||||
|
||||
router = mockRouter = new MockRouter();
|
||||
service = mockMeshService = new MockMeshService();
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
#include "TestUtil.h"
|
||||
#include "TransmitHistory.h"
|
||||
#include <Throttle.h>
|
||||
#include <unity.h>
|
||||
|
||||
// Reset the singleton between tests
|
||||
static void resetTransmitHistory()
|
||||
{
|
||||
if (transmitHistory) {
|
||||
delete transmitHistory;
|
||||
transmitHistory = nullptr;
|
||||
}
|
||||
transmitHistory = TransmitHistory::getInstance();
|
||||
}
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
resetTransmitHistory();
|
||||
}
|
||||
|
||||
void tearDown(void) {}
|
||||
|
||||
static void test_setLastSentToMesh_stores_millis()
|
||||
{
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP);
|
||||
|
||||
uint32_t result = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
|
||||
TEST_ASSERT_NOT_EQUAL(0, result);
|
||||
|
||||
// The stored millis value should be very close to current millis()
|
||||
uint32_t diff = millis() - result;
|
||||
TEST_ASSERT_LESS_OR_EQUAL(100, diff); // Within 100ms
|
||||
}
|
||||
|
||||
static void test_set_overwrites_previous_value()
|
||||
{
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP);
|
||||
uint32_t first = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP);
|
||||
|
||||
testDelay(50);
|
||||
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP);
|
||||
uint32_t second = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP);
|
||||
|
||||
// The second value should be newer (larger millis)
|
||||
TEST_ASSERT_GREATER_THAN(first, second);
|
||||
}
|
||||
|
||||
// --- Throttle integration ---
|
||||
|
||||
static void test_throttle_blocks_within_interval()
|
||||
{
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP);
|
||||
uint32_t lastMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
|
||||
|
||||
// Should be within a 10-minute interval (just set it)
|
||||
bool withinInterval = Throttle::isWithinTimespanMs(lastMs, 10 * 60 * 1000);
|
||||
TEST_ASSERT_TRUE(withinInterval);
|
||||
}
|
||||
|
||||
static void test_throttle_allows_after_interval()
|
||||
{
|
||||
// Unknown key returns 0 — throttle should NOT block
|
||||
uint32_t lastMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
|
||||
TEST_ASSERT_EQUAL_UINT32(0, lastMs);
|
||||
|
||||
// When lastMs == 0, the module check `lastMs == 0 || !isWithinTimespan` allows sending
|
||||
bool shouldSend = (lastMs == 0) || !Throttle::isWithinTimespanMs(lastMs, 10 * 60 * 1000);
|
||||
TEST_ASSERT_TRUE(shouldSend);
|
||||
}
|
||||
|
||||
static void test_throttle_blocks_after_set_then_zero_does_not()
|
||||
{
|
||||
// Set it — now throttle should block
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP);
|
||||
uint32_t lastMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP);
|
||||
bool shouldSend = (lastMs == 0) || !Throttle::isWithinTimespanMs(lastMs, 60 * 60 * 1000);
|
||||
TEST_ASSERT_FALSE(shouldSend); // Should be blocked (within 1hr interval)
|
||||
|
||||
// Different key — should allow
|
||||
uint32_t otherMs = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP);
|
||||
bool otherShouldSend = (otherMs == 0) || !Throttle::isWithinTimespanMs(otherMs, 60 * 60 * 1000);
|
||||
TEST_ASSERT_TRUE(otherShouldSend);
|
||||
}
|
||||
|
||||
// --- Multiple keys ---
|
||||
|
||||
static void test_multiple_keys_stored_independently()
|
||||
{
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP);
|
||||
uint32_t nodeInfoInitial = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
|
||||
testDelay(20);
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP);
|
||||
uint32_t positionInitial = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP);
|
||||
testDelay(20);
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_TELEMETRY_APP);
|
||||
|
||||
uint32_t nodeInfo = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
|
||||
uint32_t position = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_POSITION_APP);
|
||||
uint32_t telemetry = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_TELEMETRY_APP);
|
||||
|
||||
// All should be non-zero
|
||||
TEST_ASSERT_NOT_EQUAL(0, nodeInfo);
|
||||
TEST_ASSERT_NOT_EQUAL(0, position);
|
||||
TEST_ASSERT_NOT_EQUAL(0, telemetry);
|
||||
|
||||
// Updating other keys should not overwrite earlier key timestamps
|
||||
TEST_ASSERT_EQUAL_UINT32(nodeInfoInitial, nodeInfo);
|
||||
TEST_ASSERT_EQUAL_UINT32(positionInitial, position);
|
||||
}
|
||||
|
||||
// --- Singleton ---
|
||||
|
||||
static void test_getInstance_returns_same_instance()
|
||||
{
|
||||
TransmitHistory *a = TransmitHistory::getInstance();
|
||||
TransmitHistory *b = TransmitHistory::getInstance();
|
||||
TEST_ASSERT_EQUAL_PTR(a, b);
|
||||
}
|
||||
|
||||
static void test_getInstance_creates_global()
|
||||
{
|
||||
if (transmitHistory) {
|
||||
delete transmitHistory;
|
||||
transmitHistory = nullptr;
|
||||
}
|
||||
TEST_ASSERT_NULL(transmitHistory);
|
||||
|
||||
TransmitHistory::getInstance();
|
||||
TEST_ASSERT_NOT_NULL(transmitHistory);
|
||||
}
|
||||
|
||||
// --- Persistence round-trip (loadFromDisk / saveToDisk) ---
|
||||
|
||||
static void test_save_and_load_round_trip()
|
||||
{
|
||||
// Set some values
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP);
|
||||
testDelay(10);
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP);
|
||||
|
||||
uint32_t nodeInfoEpoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP);
|
||||
uint32_t positionEpoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_POSITION_APP);
|
||||
|
||||
// Force save
|
||||
transmitHistory->saveToDisk();
|
||||
|
||||
// Reset and reload
|
||||
delete transmitHistory;
|
||||
transmitHistory = nullptr;
|
||||
transmitHistory = TransmitHistory::getInstance();
|
||||
transmitHistory->loadFromDisk();
|
||||
|
||||
// Epoch values should be restored (if RTC was available when set)
|
||||
uint32_t restoredNodeInfo = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP);
|
||||
uint32_t restoredPosition = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_POSITION_APP);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT32(nodeInfoEpoch, restoredNodeInfo);
|
||||
TEST_ASSERT_EQUAL_UINT32(positionEpoch, restoredPosition);
|
||||
|
||||
// After loadFromDisk, millis should be seeded (non-zero) for stored entries
|
||||
uint32_t restoredMillis = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
|
||||
if (restoredNodeInfo > 0) {
|
||||
// If epoch was stored, millis should be seeded from load
|
||||
TEST_ASSERT_NOT_EQUAL(0, restoredMillis);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Boot without RTC scenario ---
|
||||
|
||||
static void test_load_seeds_millis_even_without_rtc()
|
||||
{
|
||||
// This tests the critical crash-reboot scenario:
|
||||
// After loadFromDisk(), even if getTime() returns 0 (no RTC),
|
||||
// lastMillis should be seeded so throttle blocks immediate re-broadcast.
|
||||
|
||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_NODEINFO_APP);
|
||||
transmitHistory->saveToDisk();
|
||||
|
||||
// Simulate reboot: destroy and recreate
|
||||
delete transmitHistory;
|
||||
transmitHistory = nullptr;
|
||||
transmitHistory = TransmitHistory::getInstance();
|
||||
transmitHistory->loadFromDisk();
|
||||
|
||||
// The key insight: after load, getLastSentToMeshMillis should return non-zero
|
||||
// because loadFromDisk seeds lastMillis[key] = millis() for every loaded entry.
|
||||
// This ensures throttle works even without RTC.
|
||||
uint32_t result = transmitHistory->getLastSentToMeshMillis(meshtastic_PortNum_NODEINFO_APP);
|
||||
|
||||
uint32_t epoch = transmitHistory->getLastSentToMeshEpoch(meshtastic_PortNum_NODEINFO_APP);
|
||||
if (epoch > 0) {
|
||||
// Data was persisted — millis must be seeded
|
||||
TEST_ASSERT_NOT_EQUAL(0, result);
|
||||
|
||||
// And it should cause throttle to block (treating as "just sent")
|
||||
bool withinInterval = Throttle::isWithinTimespanMs(result, 10 * 60 * 1000);
|
||||
TEST_ASSERT_TRUE(withinInterval);
|
||||
}
|
||||
// If epoch == 0, RTC wasn't available — no data was saved, so nothing to restore.
|
||||
// This is expected on platforms without RTC during the very first boot.
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
initializeTestEnvironment();
|
||||
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_setLastSentToMesh_stores_millis);
|
||||
RUN_TEST(test_set_overwrites_previous_value);
|
||||
|
||||
RUN_TEST(test_throttle_blocks_within_interval);
|
||||
RUN_TEST(test_throttle_allows_after_interval);
|
||||
RUN_TEST(test_throttle_blocks_after_set_then_zero_does_not);
|
||||
|
||||
RUN_TEST(test_multiple_keys_stored_independently);
|
||||
|
||||
// Singleton
|
||||
RUN_TEST(test_getInstance_returns_same_instance);
|
||||
RUN_TEST(test_getInstance_creates_global);
|
||||
|
||||
// Persistence
|
||||
RUN_TEST(test_save_and_load_round_trip);
|
||||
RUN_TEST(test_load_seeds_millis_even_without_rtc);
|
||||
|
||||
exit(UNITY_END());
|
||||
}
|
||||
|
||||
void loop() {}
|
||||
@@ -31,7 +31,7 @@ build_flags =
|
||||
|
||||
lib_deps = ${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
# renovate: datasource=custom.pio depName=PCA9557-arduino packageName=maxpromer/library/PCA9557-arduino
|
||||
maxpromer/PCA9557-arduino@1.0.0
|
||||
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
|
||||
|
||||
@@ -26,7 +26,7 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
|
||||
[env:crowpanel-esp32s3-4-epaper]
|
||||
extends = esp32s3_base
|
||||
@@ -56,7 +56,7 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
|
||||
[env:crowpanel-esp32s3-2-epaper]
|
||||
extends = esp32s3_base
|
||||
@@ -86,4 +86,4 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
|
||||
@@ -29,7 +29,7 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
upload_speed = 115200
|
||||
|
||||
[env:heltec-vision-master-e213-inkhud]
|
||||
|
||||
@@ -32,7 +32,7 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
upload_speed = 115200
|
||||
|
||||
[env:heltec-vision-master-e290-inkhud]
|
||||
|
||||
@@ -20,5 +20,5 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main
|
||||
https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip
|
||||
https://github.com/meshtastic/st7789/archive/9ee76d6b18b9a8f45a2c5cae06b1134a587691eb.zip
|
||||
upload_speed = 921600
|
||||
|
||||
@@ -29,7 +29,7 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
upload_speed = 115200
|
||||
|
||||
[env:heltec-wireless-paper-inkhud]
|
||||
|
||||
@@ -26,5 +26,5 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
upload_speed = 115200
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#ifndef Pins_Arduino_h
|
||||
#define Pins_Arduino_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define USB_VID 0x303a
|
||||
#define USB_PID 0x1001
|
||||
|
||||
// The default Wire will be mapped to PMU and RTC
|
||||
static const uint8_t SDA = 18;
|
||||
static const uint8_t SCL = 9;
|
||||
|
||||
// Default SPI will be mapped to Radio
|
||||
static const uint8_t SS = -1;
|
||||
static const uint8_t MOSI = 17;
|
||||
static const uint8_t MISO = 6;
|
||||
static const uint8_t SCK = 8;
|
||||
|
||||
#define SPI_MOSI (39)
|
||||
#define SPI_SCK (41)
|
||||
#define SPI_MISO (38)
|
||||
#define SPI_CS (40)
|
||||
|
||||
#define SDCARD_CS SPI_CS
|
||||
|
||||
#endif /* Pins_Arduino_h */
|
||||
@@ -0,0 +1,31 @@
|
||||
[env:mini-epaper-s3]
|
||||
;custom_meshtastic_hw_model =
|
||||
custom_meshtastic_hw_model_slug = MINI_EPAPER_S3
|
||||
custom_meshtastic_architecture = esp32-s3
|
||||
custom_meshtastic_actively_supported = true
|
||||
custom_meshtastic_support_level = 1
|
||||
custom_meshtastic_display_name = LILYGO Mini ePaper S3 E-Ink
|
||||
custom_meshtastic_images = mini-epaper-s3.svg
|
||||
custom_meshtastic_tags = LilyGo
|
||||
custom_meshtastic_requires_dfu = no
|
||||
|
||||
extends = esp32s3_base
|
||||
board = mini-epaper-s3
|
||||
board_check = true
|
||||
upload_protocol = esptool
|
||||
|
||||
build_flags =
|
||||
${esp32s3_base.build_flags}
|
||||
-I variants/esp32s3/mini-epaper-s3
|
||||
-DMINI_EPAPER_S3
|
||||
-DUSE_EINK
|
||||
-DEINK_DISPLAY_MODEL=GxEPD2_102
|
||||
-DEINK_WIDTH=128
|
||||
-DEINK_HEIGHT=80
|
||||
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
|
||||
lewisxhe/SensorLib@0.3.4
|
||||
@@ -0,0 +1,57 @@
|
||||
// Display (E-Ink)
|
||||
|
||||
#define PIN_EINK_CS 13
|
||||
#define PIN_EINK_BUSY 10
|
||||
#define PIN_EINK_RES 11
|
||||
#define PIN_EINK_SCLK 14
|
||||
#define PIN_EINK_MOSI 15
|
||||
#define PIN_EINK_DC 12
|
||||
#define PIN_EINK_EN 42
|
||||
|
||||
#define SPI_INTERFACES_COUNT 2
|
||||
#define PIN_SPI1_MISO -1
|
||||
#define PIN_SPI1_MOSI PIN_EINK_MOSI
|
||||
#define PIN_SPI1_SCK PIN_EINK_SCLK
|
||||
#define DISPLAY_FORCE_SMALL_FONTS
|
||||
|
||||
#define I2C_SDA SDA
|
||||
#define I2C_SCL SCL
|
||||
|
||||
#define BATTERY_PIN 2 // A battery voltage measurement pin, voltage divider connected here to
|
||||
// measure battery voltage ratio of voltage divider = 2.0 (assumption)
|
||||
#define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage.
|
||||
#define ADC_CHANNEL ADC1_GPIO2_CHANNEL
|
||||
|
||||
#define HAS_GPS 0
|
||||
#undef GPS_RX_PIN
|
||||
#undef GPS_TX_PIN
|
||||
|
||||
#define BUTTON_PIN 3
|
||||
#define BUTTON_NEED_PULLUP
|
||||
#define ALT_BUTTON_PIN 4
|
||||
#define ALT_BUTTON_ACTIVE_LOW true
|
||||
#define ALT_BUTTON_ACTIVE_PULLUP true
|
||||
#define PIN_BUTTON3 0
|
||||
|
||||
// #define HAS_SDCARD 1
|
||||
// #define SDCARD_USE_SOFT_SPI
|
||||
|
||||
// PCF85063 RTC Module
|
||||
#define PCF85063_RTC 0x51
|
||||
#define HAS_RTC 1
|
||||
|
||||
#define USE_SX1262
|
||||
#define LORA_DIO1 5
|
||||
#define LORA_SCK 8
|
||||
#define LORA_MISO 6
|
||||
#define LORA_MOSI 17
|
||||
#define LORA_CS 7 // CS not connected; IO7 is free
|
||||
#define LORA_RESET 21
|
||||
|
||||
#ifdef USE_SX1262
|
||||
#define SX126X_CS LORA_CS
|
||||
#define SX126X_DIO1 LORA_DIO1
|
||||
#define SX126X_BUSY 16
|
||||
#define SX126X_RESET LORA_RESET
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||
#endif
|
||||
@@ -31,7 +31,7 @@ build_flags =
|
||||
lib_deps =
|
||||
${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
|
||||
[env:tlora-t3s3-epaper-inkhud]
|
||||
extends = esp32s3_base, inkhud
|
||||
|
||||
@@ -33,7 +33,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
# renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM
|
||||
khoih-prog/nRF52_PWM@1.0.1
|
||||
;upload_protocol = fs
|
||||
|
||||
@@ -23,4 +23,4 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main
|
||||
https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip
|
||||
https://github.com/meshtastic/st7789/archive/9ee76d6b18b9a8f45a2c5cae06b1134a587691eb.zip
|
||||
|
||||
@@ -38,7 +38,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
|
||||
[env:heltec-mesh-pocket-5000-inkhud]
|
||||
extends = nrf52840_base, inkhud
|
||||
@@ -101,7 +101,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
|
||||
[env:heltec-mesh-pocket-10000-inkhud]
|
||||
extends = nrf52840_base, inkhud
|
||||
|
||||
@@ -68,7 +68,7 @@ build_flags = ${heltec_mesh_solar_base.build_flags}
|
||||
lib_deps =
|
||||
${heltec_mesh_solar_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
|
||||
[env:heltec-mesh-solar-inkhud]
|
||||
extends = heltec_mesh_solar_base, inkhud
|
||||
@@ -132,4 +132,4 @@ build_flags = ${heltec_mesh_solar_base.build_flags}
|
||||
lib_deps =
|
||||
${heltec_mesh_solar_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main
|
||||
https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip
|
||||
https://github.com/meshtastic/st7789/archive/9ee76d6b18b9a8f45a2c5cae06b1134a587691eb.zip
|
||||
|
||||
@@ -47,7 +47,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshlin
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
debug_tool = jlink
|
||||
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
|
||||
; Note: as of 6/2013 the serial/bootloader based programming takes approximately 30 seconds
|
||||
|
||||
@@ -52,12 +52,6 @@ extern "C" {
|
||||
|
||||
#define LED_STATE_ON 1 // State when LED is litted
|
||||
|
||||
/*
|
||||
* Buttons
|
||||
*/
|
||||
#define PIN_BUTTON1 (9)
|
||||
#define BUTTON_NEED_PULLUP
|
||||
|
||||
/*
|
||||
* Analog pins
|
||||
*/
|
||||
|
||||
@@ -34,7 +34,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_w
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
debug_tool = jlink
|
||||
|
||||
[env:seeed_wio_tracker_L1_eink-inkhud]
|
||||
|
||||
@@ -30,5 +30,5 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo-
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
;upload_protocol = fs
|
||||
|
||||
@@ -30,7 +30,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo>
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=git-refs depName=meshtastic-GxEPD2 packageName=https://github.com/meshtastic/GxEPD2 gitBranch=master
|
||||
https://github.com/meshtastic/GxEPD2/archive/a05c11c02862624266b61599b0d6ba93e33c6f24.zip
|
||||
https://github.com/meshtastic/GxEPD2/archive/c7eb4c3c167cf396ef4f541cc5d4c6aa42f3c46b.zip
|
||||
# renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
|
||||
lewisxhe/SensorLib@0.3.4
|
||||
;upload_protocol = fs
|
||||
|
||||
Reference in New Issue
Block a user