Compare commits

..

25 Commits

Author SHA1 Message Date
Jonathan Bennett
4e30795c6b Check for read-only DB 2025-12-28 14:12:01 -06:00
Jonathan Bennett
0228d4e2df Short hashes in 2025-12-28 13:45:40 -06:00
Jonathan Bennett
11963ea3d2 Screen crash fix 2025-12-28 09:48:32 -06:00
Jonathan Bennett
aeb1e7e71a Minor tweak for behavior when chain is empty 2025-12-27 23:28:00 -06:00
Jonathan Bennett
aecdc93c98 Add more SFPP config values 2025-12-27 21:32:45 -06:00
Jonathan Bennett
fa28412b82 Fix getLinkFromCount() 2025-12-27 21:32:45 -06:00
Jonathan Bennett
cf5ecf30d7 Steal the TEXT_MESSAGE_COMPRESSED portnum 2025-12-27 21:32:45 -06:00
Jonathan Bennett
df3934de85 Misc 2025-12-27 21:32:45 -06:00
Jonathan Bennett
c911a8e3f1 misc partial chain fixes 2025-12-27 21:32:45 -06:00
Jonathan Bennett
f392487b46 protobuf changes to test new SFPP feature 2025-12-27 21:32:45 -06:00
Jonathan Bennett
a8c2fb3945 Add count handling to SFPP 2025-12-27 21:32:45 -06:00
Jonathan Bennett
c0d108adff Don't stash messages without a matching chain root 2025-12-27 21:32:45 -06:00
Jonathan Bennett
f7ccab95b2 Allow UDP and API packets in S$F++ 2025-12-27 21:32:45 -06:00
Jonathan Bennett
dc08311b56 Set hop_limit and hop_start on message rebroadcasts 2025-12-27 21:32:45 -06:00
Jonathan Bennett
ce389e44cd Include hop_start in printPacket 2025-12-27 21:32:45 -06:00
Jonathan Bennett
a19d88b022 Don't wipe scratch 2025-12-27 21:32:45 -06:00
Jonathan Bennett
8757584f93 Add missing root_hash from scratch 2025-12-27 21:32:45 -06:00
Jonathan Bennett
83a49536c0 shorthash on canon announce 2025-12-27 21:32:45 -06:00
Jonathan Bennett
49c47c24e3 Build tryfix next 2025-12-27 21:32:45 -06:00
Jonathan Bennett
d847061d02 Misc fixes 2025-12-27 21:32:45 -06:00
Jonathan Bennett
451695a210 unbreak all the targets 2025-12-27 21:32:45 -06:00
Jonathan Bennett
20cd4a196c Store incoming non-canon messages in scratch 2025-12-27 21:32:45 -06:00
Jonathan Bennett
8575e13da8 Scratch fix 2025-12-27 21:32:45 -06:00
Jonathan Bennett
91588f5136 Broadcast root hash for an empty chain 2025-12-27 21:32:45 -06:00
Jonathan Bennett
82325a60ba Add Store and Forward++ module 2025-12-27 21:32:45 -06:00
64 changed files with 1751 additions and 819 deletions

View File

@@ -1,47 +0,0 @@
name: Welcome First-Time Contributor
on:
issues:
types: opened
pull_request_target:
types: opened
permissions: {}
jobs:
welcome:
runs-on: ubuntu-latest
permissions:
issues: write # Required to post comments and labels on issues
pull-requests: write # Required to post comments and labels on PRs
steps:
- uses: plbstl/first-contribution@v4
with:
labels: first-contribution
issue-opened-msg: |
### @{fc-author}, Welcome to Meshtastic! :wave:
Thanks for opening your first issue. If it's helpful, an easy way
to get logs is the "Open Serial Monitor" button on the (Web Flasher](https://flasher.meshtastic.org).
If you have ideas for features, note that we often debate big ideas
in the [discussions tab](https://github.com/meshtastic/firmware/discussions/categories/ideas)
first. This tracker tends to be for ideas that have community
consensus and a clear implementation.
We're very active [on discord](https://discord.com/invite/meshtastic),
especially the \#firmware and \#alphanauts-testing channels. If you'll
be around for a while, we'd love to see you there!
Welcome to the community! :heart:
pr-opened-msg: |
### @{fc-author}, Welcome to Meshtastic!
Thanks for opening your first pull request. We really appreciate it.
We discuss work as a team in discord, please join us in the [#firmware channel](https://discord.com/invite/meshtastic).
There's a big backlog of patches at the moment. If you have time,
please help us with some code review and testing of [other PRs](https://github.com/meshtastic/firmware/pulls)!
Welcome to the team :smile:

View File

@@ -240,7 +240,6 @@ jobs:
needs: [build]
steps:
- uses: actions/checkout@v6
if: github.event_name == 'pull_request_target'
with:
filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head)
fetch-depth: 0
@@ -254,26 +253,19 @@ jobs:
uses: actions/upload-artifact@v6
id: upload-manifest
with:
name: manifests-${{ github.sha }}
name: manifests-all
overwrite: true
path: manifests-new/*.mt.json
path: |
manifests-new/*.mt.json
- name: Find the merge base
if: github.event_name == 'pull_request_target'
run: echo "MERGE_BASE=$(git merge-base "origin/$base" "$head")" >> $GITHUB_ENV
env:
base: ${{ github.base_ref }}
head: ${{ github.sha }}
# Currently broken (for-loop through EVERY artifact -- rate limiting)
# - name: Download the old manifests
# if: github.event_name == 'pull_request_target'
# run: gh run download -R "$repo" --name "manifests-$merge_base" --dir manifest-old/
# env:
# GH_TOKEN: ${{ github.token }}
# merge_base: ${{ env.MERGE_BASE }}
# repo: ${{ github.repository }}
# - name: Do scan and post comment
# if: github.event_name == 'pull_request_target'
# run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/
head: ${{ github.head_ref }}
- name: Download the old manifests
run: gh run download -R ${{ github.repository }} --commit ${{ env.MERGE_BASE }} --name manifests-all --dir manifest-old/
- name: Do scan and post comment
run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/
release-artifacts:
runs-on: ubuntu-latest

9
.gitignore vendored
View File

@@ -41,12 +41,3 @@ src/mesh/raspihttp/private_key.pem
# Ignore logo (set at build time with platformio-custom.py)
data/boot/logo.*
# pioarduino platform
managed_components/*
arduino-lib-builder*
dependencies.lock
idf_component.yml
CMakeLists.txt
sdkconfig.*
.dummy/*

View File

@@ -8,10 +8,10 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.496
- renovate@42.66.14
- checkov@3.2.495
- renovate@42.64.1
- prettier@3.7.4
- trufflehog@3.92.4
- trufflehog@3.92.3
- yamllint@1.37.1
- bandit@1.9.2
- trivy@0.68.2

View File

@@ -21,14 +21,13 @@ rm -f $BUILDDIR/firmware*
export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION
ota_basename=${basename}-ota
pio run --environment $1 -t mtjson # -v
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
echo "Copying NRF52 dfu (OTA) file"
cp $BUILDDIR/$basename.zip $OUTDIR/$ota_basename.zip
cp $BUILDDIR/$basename.zip $OUTDIR/$basename.zip
echo "Copying NRF52 UF2 file"
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2

View File

@@ -87,6 +87,9 @@
</screenshots>
<releases>
<release version="2.7.18" date="2025-12-20">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
</release>
<release version="2.7.17" date="2025-11-28">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.17</url>
</release>

View File

@@ -17,8 +17,6 @@ lfsbin = f"{progname.replace('firmware-', 'littlefs-')}.bin"
def manifest_gather(source, target, env):
out = []
board_platform = env.BoardConfig().get("platform")
needs_ota_suffix = board_platform == "nordicnrf52"
check_paths = [
progname,
f"{progname}.elf",
@@ -34,11 +32,8 @@ def manifest_gather(source, target, env):
for p in check_paths:
f = env.File(env.subst(f"$BUILD_DIR/{p}"))
if f.exists():
manifest_name = p
if needs_ota_suffix and p == f"{progname}.zip":
manifest_name = f"{progname}-ota.zip"
d = {
"name": manifest_name,
"name": p,
"md5": f.get_content_hash(), # Returns MD5 hash
"bytes": f.get_size() # Returns file size in bytes
}

6
debian/changelog vendored
View File

@@ -1,3 +1,9 @@
meshtasticd (2.7.18.0) unstable; urgency=medium
* Version 2.7.18
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Sat, 20 Dec 2025 15:47:25 +0000
meshtasticd (2.7.17.0) unstable; urgency=medium
* Version 2.7.17

View File

@@ -64,7 +64,7 @@ monitor_speed = 115200
monitor_filters = direct
lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/b34c6817c25d6faabb3a8a162b5d14fb75395433.zip
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.zip
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
@@ -94,7 +94,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
end2endzone/NonBlockingRTTTL@1.4.0
build_flags = ${env.build_flags} -Os
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/> -<modules/Native/>
; Common libs for communicating over TCP/IP networks such as MQTT
[networking_base]
@@ -119,7 +119,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/940ba8570f59c59c3508643f4d72840de716ce20.zip
https://github.com/meshtastic/device-ui/archive/862ed040c4ab44f0dfbbe492691f144886102588.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]

View File

@@ -8,7 +8,6 @@
#include "graphics/draw/UIRenderer.h"
#include "main.h"
#include "meshtastic/config.pb.h"
#include "modules/ExternalNotificationModule.h"
#include "power.h"
#include <OLEDDisplay.h>
#include <graphics/images.h>
@@ -57,6 +56,7 @@ void decomposeTime(uint32_t rtc_sec, int &hour, int &minute, int &second)
// === Shared External State ===
bool hasUnreadMessage = false;
bool isMuted = false;
ScreenResolution currentResolution = ScreenResolution::Low;
// === Internal State ===
@@ -306,7 +306,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
}
display->drawXbm(iconX, iconY, mail_width, mail_height, mail);
}
} else if (externalNotificationModule->getMute()) {
} else if (isMuted) {
if (currentResolution == ScreenResolution::High) {
int iconX = iconRightEdge - mute_symbol_big_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
@@ -325,7 +325,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int iconX = iconRightEdge - mute_symbol_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
if (isInverted && !force_no_invert) {
if (isInverted) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mute_symbol_width + 2, mute_symbol_height + 2);
display->setColor(BLACK);
@@ -383,7 +383,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
display->drawXbm(iconX, iconY, mail_width, mail_height, mail);
}
} else if (externalNotificationModule->getMute()) {
} else if (isMuted) {
if (currentResolution == ScreenResolution::High) {
int iconX = iconRightEdge - mute_symbol_big_width;
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
@@ -470,49 +470,18 @@ bool isAllowedPunctuation(char c)
return allowed.find(c) != std::string::npos;
}
static void replaceAll(std::string &s, const std::string &from, const std::string &to)
{
if (from.empty())
return;
size_t pos = 0;
while ((pos = s.find(from, pos)) != std::string::npos) {
s.replace(pos, from.size(), to);
pos += to.size();
}
}
std::string sanitizeString(const std::string &input)
{
std::string output;
bool inReplacement = false;
// Make a mutable copy so we can normalize UTF-8 “smart punctuation” into ASCII first.
std::string s = input;
// Curly single quotes:
replaceAll(s, "\xE2\x80\x98", "'"); // U+2018
replaceAll(s, "\xE2\x80\x99", "'"); // U+2019
// Curly double quotes: “ ”
replaceAll(s, "\xE2\x80\x9C", "\""); // U+201C
replaceAll(s, "\xE2\x80\x9D", "\""); // U+201D
// En dash / Em dash:
replaceAll(s, "\xE2\x80\x93", "-"); // U+2013
replaceAll(s, "\xE2\x80\x94", "-"); // U+2014
// Non-breaking space
replaceAll(s, "\xC2\xA0", " "); // U+00A0
// Now do your original sanitize pass over the normalized string.
for (unsigned char uc : s) {
char c = static_cast<char>(uc);
if (std::isalnum(uc) || isAllowedPunctuation(c)) {
for (char c : input) {
if (std::isalnum(static_cast<unsigned char>(c)) || isAllowedPunctuation(c)) {
output += c;
inReplacement = false;
} else {
if (!inReplacement) {
output += static_cast<char>(0xBF); // ISO-8859-1 for inverted question mark
output += 0xbf; // ISO-8859-1 for inverted question mark
inReplacement = true;
}
}

View File

@@ -41,6 +41,7 @@ namespace graphics
// Shared state (declare inside namespace)
extern bool hasUnreadMessage;
extern bool isMuted;
enum class ScreenResolution : uint8_t { UltraLow = 0, Low = 1, High = 2 };
extern ScreenResolution currentResolution;
ScreenResolution determineScreenResolution(int16_t screenheight, int16_t screenwidth);

View File

@@ -20,8 +20,8 @@
#include "mesh/MeshTypes.h"
#include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h"
#include "modules/ExternalNotificationModule.h"
#include "modules/KeyVerificationModule.h"
#include "modules/TraceRouteModule.h"
#include <algorithm>
#include <array>
@@ -843,21 +843,12 @@ void menuHandler::messageViewModeMenu()
void menuHandler::homeBaseMenu()
{
enum optionsNumbers { Back, Mute, Backlight, Position, Preset, Freetext, Sleep, enumEnd };
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Sleep, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
if (moduleConfig.external_notification.enabled && externalNotificationModule &&
config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED) {
if (!externalNotificationModule->getMute()) {
optionsArray[options] = "Temporarily Mute";
} else {
optionsArray[options] = "Unmute";
}
optionsEnumArray[options++] = Mute;
}
#if defined(PIN_EINK_EN) || defined(PCA_PIN_EINK_EN)
optionsArray[options] = "Toggle Backlight";
optionsEnumArray[options++] = Backlight;
@@ -881,13 +872,7 @@ void menuHandler::homeBaseMenu()
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Mute) {
if (moduleConfig.external_notification.enabled && externalNotificationModule) {
externalNotificationModule->setMute(!externalNotificationModule->getMute());
IF_SCREEN(if (!externalNotificationModule->getMute()) externalNotificationModule->stopNow();)
}
} else if (selected == Backlight) {
screen->setOn(false);
if (selected == Backlight) {
#if defined(PIN_EINK_EN)
if (uiconfig.screen_brightness == 1) {
uiconfig.screen_brightness = 0;
@@ -964,7 +949,6 @@ void menuHandler::systemBaseMenu()
optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Notifications;
optionsArray[options] = "Display Options";
optionsEnumArray[options++] = ScreenOptions;
@@ -1001,7 +985,7 @@ void menuHandler::systemBaseMenu()
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Notifications) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
menuHandler::menuQueue = menuHandler::notifications_menu;
screen->runNow();
} else if (selected == ScreenOptions) {
menuHandler::menuQueue = menuHandler::screen_options_menu;
@@ -1108,23 +1092,11 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu()
{
enum optionsNumbers {
Back,
GPSToggle,
GPSFormat,
CompassMenu,
CompassCalibrate,
GPSSmartPosition,
GPSUpdateInterval,
GPSPositionBroadcast,
enumEnd
};
enum optionsNumbers { Back, GPSToggle, GPSFormat, CompassMenu, CompassCalibrate, enumEnd };
static const char *optionsArray[enumEnd] = {
"Back", "On/Off Toggle", "Format", "Smart Position", "Update Interval", "Broadcast Interval", "Compass"};
static int optionsEnumArray[enumEnd] = {
Back, GPSToggle, GPSFormat, GPSSmartPosition, GPSUpdateInterval, GPSPositionBroadcast, CompassMenu};
int options = 7;
static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "GPS Format", "Compass"};
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, GPSFormat, CompassMenu};
int options = 4;
if (accelerometerThread) {
optionsArray[options] = "Compass Calibrate";
@@ -1132,7 +1104,7 @@ void menuHandler::positionBaseMenu()
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "GPS Action";
bannerOptions.message = "Position Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -1148,15 +1120,6 @@ void menuHandler::positionBaseMenu()
screen->runNow();
} else if (selected == CompassCalibrate) {
accelerometerThread->calibrate(30);
} else if (selected == GPSSmartPosition) {
menuQueue = gps_smart_position_menu;
screen->runNow();
} else if (selected == GPSUpdateInterval) {
menuQueue = gps_update_interval_menu;
screen->runNow();
} else if (selected == GPSPositionBroadcast) {
menuQueue = gps_position_broadcast_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
@@ -1383,217 +1346,6 @@ void menuHandler::GPSFormatMenu()
bannerOptions.InitialSelected = uiconfig.gps_format + 1;
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::GPSSmartPositionMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Toggle Smart Position";
if (currentResolution == ScreenResolution::UltraLow) {
bannerOptions.message = "Smrt Postn";
}
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) {
menuQueue = position_base_menu;
screen->runNow();
} else if (selected == 1) {
config.position.position_broadcast_smart_enabled = true;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
} else if (selected == 2) {
config.position.position_broadcast_smart_enabled = false;
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
}
};
bannerOptions.InitialSelected = config.position.position_broadcast_smart_enabled ? 1 : 2;
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::GPSUpdateIntervalMenu()
{
static const char *optionsArray[] = {"Back", "8 seconds", "20 seconds", "40 seconds", "1 minute", "80 seconds",
"2 minutes", "5 minutes", "10 minutes", "15 minutes", "30 minutes", "1 hour",
"6 hours", "12 hours", "24 hours", "At Boot Only"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Update Interval";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 16;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) {
menuQueue = position_base_menu;
screen->runNow();
} else if (selected == 1) {
config.position.gps_update_interval = 8;
} else if (selected == 2) {
config.position.gps_update_interval = 20;
} else if (selected == 3) {
config.position.gps_update_interval = 40;
} else if (selected == 4) {
config.position.gps_update_interval = 60;
} else if (selected == 5) {
config.position.gps_update_interval = 80;
} else if (selected == 6) {
config.position.gps_update_interval = 120;
} else if (selected == 7) {
config.position.gps_update_interval = 300;
} else if (selected == 8) {
config.position.gps_update_interval = 600;
} else if (selected == 9) {
config.position.gps_update_interval = 900;
} else if (selected == 10) {
config.position.gps_update_interval = 1800;
} else if (selected == 11) {
config.position.gps_update_interval = 3600;
} else if (selected == 12) {
config.position.gps_update_interval = 21600;
} else if (selected == 13) {
config.position.gps_update_interval = 43200;
} else if (selected == 14) {
config.position.gps_update_interval = 86400;
} else if (selected == 15) {
config.position.gps_update_interval = 2147483647; // At Boot Only
}
if (selected != 0) {
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
}
};
if (config.position.gps_update_interval == 8) {
bannerOptions.InitialSelected = 1;
} else if (config.position.gps_update_interval == 20) {
bannerOptions.InitialSelected = 2;
} else if (config.position.gps_update_interval == 40) {
bannerOptions.InitialSelected = 3;
} else if (config.position.gps_update_interval == 60) {
bannerOptions.InitialSelected = 4;
} else if (config.position.gps_update_interval == 80) {
bannerOptions.InitialSelected = 5;
} else if (config.position.gps_update_interval == 120) {
bannerOptions.InitialSelected = 6;
} else if (config.position.gps_update_interval == 300) {
bannerOptions.InitialSelected = 7;
} else if (config.position.gps_update_interval == 600) {
bannerOptions.InitialSelected = 8;
} else if (config.position.gps_update_interval == 900) {
bannerOptions.InitialSelected = 9;
} else if (config.position.gps_update_interval == 1800) {
bannerOptions.InitialSelected = 10;
} else if (config.position.gps_update_interval == 3600) {
bannerOptions.InitialSelected = 11;
} else if (config.position.gps_update_interval == 21600) {
bannerOptions.InitialSelected = 12;
} else if (config.position.gps_update_interval == 43200) {
bannerOptions.InitialSelected = 13;
} else if (config.position.gps_update_interval == 86400) {
bannerOptions.InitialSelected = 14;
} else if (config.position.gps_update_interval == 2147483647) { // At Boot Only
bannerOptions.InitialSelected = 15;
} else {
bannerOptions.InitialSelected = 0;
}
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::GPSPositionBroadcastMenu()
{
static const char *optionsArray[] = {"Back", "1 minute", "90 seconds", "5 minutes", "15 minutes", "1 hour",
"2 hours", "3 hours", "4 hours", "5 hours", "6 hours", "12 hours",
"18 hours", "24 hours", "36 hours", "48 hours", "72 hours"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Broadcast Interval";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 17;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) {
menuQueue = position_base_menu;
screen->runNow();
} else if (selected == 1) {
config.position.position_broadcast_secs = 60;
} else if (selected == 2) {
config.position.position_broadcast_secs = 90;
} else if (selected == 3) {
config.position.position_broadcast_secs = 300;
} else if (selected == 4) {
config.position.position_broadcast_secs = 900;
} else if (selected == 5) {
config.position.position_broadcast_secs = 3600;
} else if (selected == 6) {
config.position.position_broadcast_secs = 7200;
} else if (selected == 7) {
config.position.position_broadcast_secs = 10800;
} else if (selected == 8) {
config.position.position_broadcast_secs = 14400;
} else if (selected == 9) {
config.position.position_broadcast_secs = 18000;
} else if (selected == 10) {
config.position.position_broadcast_secs = 21600;
} else if (selected == 11) {
config.position.position_broadcast_secs = 43200;
} else if (selected == 12) {
config.position.position_broadcast_secs = 64800;
} else if (selected == 13) {
config.position.position_broadcast_secs = 86400;
} else if (selected == 14) {
config.position.position_broadcast_secs = 129600;
} else if (selected == 15) {
config.position.position_broadcast_secs = 172800;
} else if (selected == 16) {
config.position.position_broadcast_secs = 259200;
}
if (selected != 0) {
saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
}
};
if (config.position.position_broadcast_secs == 60) {
bannerOptions.InitialSelected = 1;
} else if (config.position.position_broadcast_secs == 90) {
bannerOptions.InitialSelected = 2;
} else if (config.position.position_broadcast_secs == 300) {
bannerOptions.InitialSelected = 3;
} else if (config.position.position_broadcast_secs == 900) {
bannerOptions.InitialSelected = 4;
} else if (config.position.position_broadcast_secs == 3600) {
bannerOptions.InitialSelected = 5;
} else if (config.position.position_broadcast_secs == 7200) {
bannerOptions.InitialSelected = 6;
} else if (config.position.position_broadcast_secs == 10800) {
bannerOptions.InitialSelected = 7;
} else if (config.position.position_broadcast_secs == 14400) {
bannerOptions.InitialSelected = 8;
} else if (config.position.position_broadcast_secs == 18000) {
bannerOptions.InitialSelected = 9;
} else if (config.position.position_broadcast_secs == 21600) {
bannerOptions.InitialSelected = 10;
} else if (config.position.position_broadcast_secs == 43200) {
bannerOptions.InitialSelected = 11;
} else if (config.position.position_broadcast_secs == 64800) {
bannerOptions.InitialSelected = 12;
} else if (config.position.position_broadcast_secs == 86400) {
bannerOptions.InitialSelected = 13;
} else if (config.position.position_broadcast_secs == 129600) {
bannerOptions.InitialSelected = 14;
} else if (config.position.position_broadcast_secs == 172800) {
bannerOptions.InitialSelected = 15;
} else if (config.position.position_broadcast_secs == 259200) {
bannerOptions.InitialSelected = 16;
} else {
bannerOptions.InitialSelected = 0;
}
screen->showOverlayBanner(bannerOptions);
}
#endif
void menuHandler::BluetoothToggleMenu()
@@ -1620,9 +1372,9 @@ void menuHandler::BluetoothToggleMenu()
void menuHandler::BuzzerModeMenu()
{
static const char *optionsArray[] = {"All Enabled", "All Disabled", "Notifications", "System Only", "DMs Only"};
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only", "DMs Only"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Notification Sounds";
bannerOptions.message = "Buzzer Mode";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -1997,6 +1749,30 @@ void menuHandler::wifiToggleMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::notificationsMenu()
{
enum optionsNumbers { Back, BuzzerActions };
static const char *optionsArray[] = {"Back", "Buzzer Actions"};
static int optionsEnumArray[] = {Back, BuzzerActions};
int options = 2;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Notifications";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == BuzzerActions) {
menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
screen->runNow();
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::screenOptionsMenu()
{
// Check if brightness is supported
@@ -2350,15 +2126,6 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case gps_format_menu:
GPSFormatMenu();
break;
case gps_smart_position_menu:
GPSSmartPositionMenu();
break;
case gps_update_interval_menu:
GPSUpdateIntervalMenu();
break;
case gps_position_broadcast_menu:
GPSPositionBroadcastMenu();
break;
#endif
case compass_point_north_menu:
compassNorthMenu();
@@ -2414,6 +2181,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case bluetooth_toggle_menu:
BluetoothToggleMenu();
break;
case notifications_menu:
notificationsMenu();
break;
case screen_options_menu:
screenOptionsMenu();
break;

View File

@@ -22,9 +22,6 @@ class menuHandler
node_base_menu,
gps_toggle_menu,
gps_format_menu,
gps_smart_position_menu,
gps_update_interval_menu,
gps_position_broadcast_menu,
compass_point_north_menu,
reset_node_db_menu,
buzzermodemenupicker,
@@ -39,6 +36,7 @@ class menuHandler
number_test,
wifi_toggle_menu,
bluetooth_toggle_menu,
notifications_menu,
screen_options_menu,
power_menu,
system_base_menu,
@@ -79,9 +77,6 @@ class menuHandler
static void compassNorthMenu();
static void GPSToggleMenu();
static void GPSFormatMenu();
static void GPSSmartPositionMenu();
static void GPSUpdateIntervalMenu();
static void GPSPositionBroadcastMenu();
static void BuzzerModeMenu();
static void switchToMUIMenu();
static void TFTColorPickerMenu(OLEDDisplay *display);
@@ -97,6 +92,7 @@ class menuHandler
static void numberTest();
static void wifiBaseMenu();
static void wifiToggleMenu();
static void notificationsMenu();
static void screenOptionsMenu();
static void powerMenu();
static void nodeNameLengthMenu();

View File

@@ -1,7 +1,6 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./PositionsApplet.h"
#include "NodeDB.h"
using namespace NicheGraphics;
@@ -50,8 +49,8 @@ ProcessMessage InkHUD::PositionsApplet::handleReceived(const meshtastic_MeshPack
if (!hasPosition)
return ProcessMessage::CONTINUE;
const int8_t hopsAway = getHopsAway(mp);
const bool hasHopsAway = hopsAway >= 0;
bool hasHopsAway = (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start); // From NodeDB::updateFrom
uint8_t hopsAway = mp.hop_start - mp.hop_limit;
// Determine if the position packet would change anything on-screen
// -----------------------------------------------------------------

View File

@@ -27,9 +27,7 @@ bool RotaryEncoderInterruptImpl1::init()
RotaryEncoderInterruptImpl1::handleIntA, RotaryEncoderInterruptImpl1::handleIntB,
RotaryEncoderInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this);
#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
#endif
return true;
}

View File

@@ -45,9 +45,7 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
LOG_DEBUG("Trackball GPIO initialized - UP:%d DOWN:%d LEFT:%d RIGHT:%d PRESS:%d", this->_pinUp, this->_pinDown,
this->_pinLeft, this->_pinRight, pinPress);
#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
#endif
this->setInterval(100);
}

View File

@@ -29,9 +29,7 @@ bool UpDownInterruptImpl1::init()
eventDownLong, UpDownInterruptImpl1::handleIntDown, UpDownInterruptImpl1::handleIntUp,
UpDownInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this);
#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
#endif
return true;
}

View File

@@ -1186,6 +1186,11 @@ void setup()
#endif
#endif
#ifdef PIN_PWR_DELAY_MS
// This may be required to give the peripherals time to power up.
delay(PIN_PWR_DELAY_MS);
#endif
#ifdef ARCH_PORTDUINO
// as one can't use a function pointer to the class constructor:
auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
@@ -1460,10 +1465,8 @@ void setup()
#endif
#if defined(HAS_TRACKBALL) || (defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2)
#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
#endif
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
// Start web server thread.

View File

@@ -195,7 +195,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
// but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs
// bad.
routingModule->sendAckNak(meshtastic_Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel,
routingModule->getHopLimitForResponse(mp));
routingModule->getHopLimitForResponse(mp.hop_start, mp.hop_limit));
}
}
@@ -235,7 +235,7 @@ void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to)
assert(p->which_payload_variant == meshtastic_MeshPacket_decoded_tag); // Should already be set by now
p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0
p->channel = to.channel; // Use the same channel that the request came in on
p->hop_limit = routingModule->getHopLimitForResponse(to);
p->hop_limit = routingModule->getHopLimitForResponse(to.hop_start, to.hop_limit);
// No need for an ack if we are just delivering locally (it just generates an ignored ack)
p->want_ack = (to.from != 0) ? to.want_ack : false;

View File

@@ -95,8 +95,11 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
nodeInfoModule && !isPreferredRebroadcaster && !nodeDB->isFull()) {
if (airTime->isTxAllowedChannelUtil(true)) {
const int8_t hopsUsed = getHopsAway(*mp, config.lora.hop_limit);
if (hopsUsed > (int32_t)(config.lora.hop_limit + 2)) {
// Hops used by the request. If somebody in between running modified firmware modified it, ignore it
auto hopStart = mp->hop_start;
auto hopLimit = mp->hop_limit;
uint8_t hopsUsed = hopStart < hopLimit ? config.lora.hop_limit : hopStart - hopLimit;
if (hopsUsed > config.lora.hop_limit + 2) {
LOG_DEBUG("Skip send NodeInfo: %d hops away is too far away", hopsUsed);
} else {
LOG_INFO("Heard new node on ch. %d, send NodeInfo and ask for response", mp->channel);
@@ -192,13 +195,15 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
p.rx_time = getValidTime(RTCQualityFromNet); // Record the time the packet arrived from the phone
IF_SCREEN(if (p.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP && p.decoded.payload.size > 0 &&
p.to != NODENUM_BROADCAST && p.to != 0) // DM only
{
perhapsDecode(&p);
const StoredMessage &sm = messageStore.addFromPacket(p);
graphics::MessageRenderer::handleNewMessage(nullptr, sm, p); // notify UI
})
#if HAS_SCREEN
if (screen && p.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP && p.decoded.payload.size > 0 &&
p.to != NODENUM_BROADCAST && p.to != 0) // DM only
{
perhapsDecode(&p);
const StoredMessage &sm = messageStore.addFromPacket(p);
graphics::MessageRenderer::handleNewMessage(nullptr, sm, p); // notify UI
}
#endif
// Send the packet into the mesh
DEBUG_HEAP_BEFORE;
auto a = packetPool.allocCopy(p);

View File

@@ -64,7 +64,7 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
perhapsRebroadcast(p);
}
} else {
bool isRepeated = getHopsAway(*p) == 0;
bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
// If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
if (isRepeated) {
if (!findInTxQueue(p->from, p->id)) {
@@ -101,7 +101,8 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to);
bool weWereSoleRelayer = false;
bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer);
if ((weWereRelayer && wasAlreadyRelayer) || (getHopsAway(*p) == 0 && weWereSoleRelayer)) {
if ((weWereRelayer && wasAlreadyRelayer) ||
(p->hop_start != 0 && p->hop_start == p->hop_limit && weWereSoleRelayer)) {
if (origTx->next_hop != p->relay_node) { // Not already set
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply (was relayer %d we were sole %d)", p->from,
p->relay_node, wasAlreadyRelayer, weWereSoleRelayer);

View File

@@ -805,8 +805,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.output_ms = 500;
moduleConfig.external_notification.nag_timeout = 2;
#endif
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \
defined(ELECROW_ThinkNode_M6)
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3)
// Default to PIN_LED2 for external notification output (LED color depends on device variant)
moduleConfig.external_notification.enabled = true;
moduleConfig.external_notification.output = PIN_LED2;
@@ -1549,23 +1548,6 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p)
return delta;
}
int8_t getHopsAway(const meshtastic_MeshPacket &p, int8_t defaultIfUnknown)
{
// Firmware prior to 2.3.0 (585805c) lacked a hop_start field. Firmware version 2.5.0 (bf34329) introduced a
// bitfield that is always present. Use the presence of the bitfield to determine if the origin's firmware
// version is guaranteed to have hop_start populated. Note that this can only be done for decoded packets as
// the bitfield is encrypted under the channel encryption key. For encrypted packets, this returns
// defaultIfUnknown when hop_start is 0.
if (p.hop_start == 0 && !(p.which_payload_variant == meshtastic_MeshPacket_decoded_tag && p.decoded.has_bitfield))
return defaultIfUnknown; // Cannot reliably determine the number of hops.
// Guard against invalid values.
if (p.hop_start < p.hop_limit)
return defaultIfUnknown;
return p.hop_start - p.hop_limit;
}
#define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline
size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
@@ -1818,10 +1800,9 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT
// If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
const int8_t hopsAway = getHopsAway(mp);
if (hopsAway >= 0) {
if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) {
info->has_hops_away = true;
info->hops_away = hopsAway;
info->hops_away = mp.hop_start - mp.hop_limit;
}
sortMeshDB();
}

View File

@@ -110,10 +110,6 @@ uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n);
/// Given a packet, return how many seconds in the past (vs now) it was received
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
/// Given a packet, return the number of hops used to reach this node.
/// Returns defaultIfUnknown if the number of hops couldn't be determined.
int8_t getHopsAway(const meshtastic_MeshPacket &p, int8_t defaultIfUnknown = -1);
enum LoadFileResult {
// Successfully opened the file
LOAD_SUCCESS = 1,

View File

@@ -171,8 +171,7 @@ const RegionInfo regions[] = {
863 - 868 MHz <25 mW EIRP, 500kHz channels allowed, must not be used at airfields
https://github.com/meshtastic/firmware/issues/7204
*/
RDEF(KZ_433, 433.075f, 434.775f, 100, 0, 10, true, false, false),
RDEF(KZ_863, 863.0f, 868.0f, 100, 0, 30, true, false, false),
RDEF(KZ_433, 433.075f, 434.775f, 100, 0, 10, true, false, false), RDEF(KZ_863, 863.0f, 868.0f, 100, 0, 30, true, false, true),
/*
Nepal
@@ -324,9 +323,9 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
{
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
std::string out =
DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id,
p->from, p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->channel);
std::string out = DEBUG_PORT.mt_sprintf(
"%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d HopStart=%d Ch=0x%x", prefix, p->id, p->from,
p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->hop_start, p->channel);
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
auto &s = p->decoded;

View File

@@ -1,7 +1,6 @@
#include "ReliableRouter.h"
#include "Default.h"
#include "MeshTypes.h"
#include "NodeDB.h"
#include "configuration.h"
#include "memGet.h"
#include "mesh-pb-constants.h"
@@ -109,12 +108,12 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
// If this packet should always be ACKed reliably with want_ack back to the original sender, make sure we
// do that unconditionally.
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
routingModule->getHopLimitForResponse(*p), true);
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit), true);
} else if (!p->decoded.request_id && !p->decoded.reply_id) {
// If it's not an ACK or a reply, send an ACK.
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
routingModule->getHopLimitForResponse(*p));
} else if ((getHopsAway(*p) == 0) || p->next_hop != NO_NEXT_HOP_PREFERENCE) {
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
} else if ((p->hop_start > 0 && p->hop_start == p->hop_limit) || p->next_hop != NO_NEXT_HOP_PREFERENCE) {
// If we received the packet directly from the original sender, send a 0-hop ACK since the original sender
// won't overhear any implicit ACKs. If we received the packet via NextHopRouter, also send a 0-hop ACK to
// stop the immediate relayer's retransmissions.
@@ -124,11 +123,11 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
routingModule->getHopLimitForResponse(*p));
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
} else {
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
routingModule->getHopLimitForResponse(*p));
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
}
} else if (p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum()) && p->hop_limit > 0) {
// No wantAck, but we need to ACK with hop limit of 0 if we were the next hop to stop their retransmissions

View File

@@ -81,7 +81,8 @@ Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRA
bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
{
// First hop MUST always decrement to prevent retry issues
if (getHopsAway(*p) == 0) {
bool isFirstHop = (p->hop_start != 0 && p->hop_start == p->hop_limit);
if (isFirstHop) {
return true; // Always decrement on first hop
}
@@ -729,8 +730,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
meshtastic_PortNum_POSITION_APP, meshtastic_PortNum_NODEINFO_APP, meshtastic_PortNum_ROUTING_APP,
meshtastic_PortNum_TELEMETRY_APP, meshtastic_PortNum_ADMIN_APP, meshtastic_PortNum_ALERT_APP,
meshtastic_PortNum_KEY_VERIFICATION_APP, meshtastic_PortNum_WAYPOINT_APP,
meshtastic_PortNum_STORE_FORWARD_APP, meshtastic_PortNum_TRACEROUTE_APP,
meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP)) {
meshtastic_PortNum_STORE_FORWARD_APP, meshtastic_PortNum_TRACEROUTE_APP)) {
LOG_DEBUG("Ignore packet on non-standard portnum for CORE_PORTNUMS_ONLY");
cancelSending(p->from, p->id);
skipHandle = true;
@@ -745,19 +745,15 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
MeshModule::callModules(*p, src);
#if !MESHTASTIC_EXCLUDE_MQTT
if (p_encrypted == nullptr) {
LOG_WARN("p_encrypted is null, skipping MQTT publish");
} else {
// Mark as pki_encrypted if it is not yet decoded and MQTT encryption is also enabled, hash matches and it's a DM not
// to us (because we would be able to decrypt it)
if (decodedState == DecodeState::DECODE_FAILURE && moduleConfig.mqtt.encryption_enabled && p->channel == 0x00 &&
!isBroadcast(p->to) && !isToUs(p))
p_encrypted->pki_encrypted = true;
// After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet
if ((decodedState == DecodeState::DECODE_SUCCESS || p_encrypted->pki_encrypted) && moduleConfig.mqtt.enabled &&
!isFromUs(p) && mqtt)
mqtt->onSend(*p_encrypted, *p, p->channel);
}
// Mark as pki_encrypted if it is not yet decoded and MQTT encryption is also enabled, hash matches and it's a DM not to
// us (because we would be able to decrypt it)
if (decodedState == DecodeState::DECODE_FAILURE && moduleConfig.mqtt.encryption_enabled && p->channel == 0x00 &&
!isBroadcast(p->to) && !isToUs(p))
p_encrypted->pki_encrypted = true;
// After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet
if ((decodedState == DecodeState::DECODE_SUCCESS || p_encrypted->pki_encrypted) && moduleConfig.mqtt.enabled &&
!isFromUs(p) && mqtt)
mqtt->onSend(*p_encrypted, *p, p->channel);
#endif
}

View File

@@ -33,5 +33,3 @@ PB_BIND(meshtastic_KeyVerificationAdmin, meshtastic_KeyVerificationAdmin, AUTO)

View File

@@ -16,16 +16,6 @@
#endif
/* Enum definitions */
/* Firmware update mode for OTA updates */
typedef enum _meshtastic_OTAMode {
/* Do not reboot into OTA mode */
meshtastic_OTAMode_NO_REBOOT_OTA = 0,
/* Reboot into OTA mode for BLE firmware update */
meshtastic_OTAMode_OTA_BLE = 1,
/* Reboot into OTA mode for WiFi firmware update */
meshtastic_OTAMode_OTA_WIFI = 2
} meshtastic_OTAMode;
/* TODO: REPLACE */
typedef enum _meshtastic_AdminMessage_ConfigType {
/* TODO: REPLACE */
@@ -268,13 +258,10 @@ typedef struct _meshtastic_AdminMessage {
meshtastic_SharedContact add_contact;
/* Initiate or respond to a key verification request */
meshtastic_KeyVerificationAdmin key_verification;
/* Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only for now) */
meshtastic_OTAMode reboot_ota_mode;
/* Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. */
int32_t factory_reset_device;
/* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot)
Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth.
Deprecated in favor of reboot_ota_mode in 2.7.17 */
Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. */
int32_t reboot_ota_seconds;
/* This message is only supported for the simulator Portduino build.
If received the simulator will exit successfully. */
@@ -301,10 +288,6 @@ extern "C" {
#endif
/* Helper constants for enums */
#define _meshtastic_OTAMode_MIN meshtastic_OTAMode_NO_REBOOT_OTA
#define _meshtastic_OTAMode_MAX meshtastic_OTAMode_OTA_WIFI
#define _meshtastic_OTAMode_ARRAYSIZE ((meshtastic_OTAMode)(meshtastic_OTAMode_OTA_WIFI+1))
#define _meshtastic_AdminMessage_ConfigType_MIN meshtastic_AdminMessage_ConfigType_DEVICE_CONFIG
#define _meshtastic_AdminMessage_ConfigType_MAX meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG
#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1))
@@ -326,7 +309,6 @@ extern "C" {
#define meshtastic_AdminMessage_payload_variant_backup_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation
#define meshtastic_AdminMessage_payload_variant_restore_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation
#define meshtastic_AdminMessage_payload_variant_remove_backup_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation
#define meshtastic_AdminMessage_payload_variant_reboot_ota_mode_ENUMTYPE meshtastic_OTAMode
@@ -414,7 +396,6 @@ extern "C" {
#define meshtastic_AdminMessage_commit_edit_settings_tag 65
#define meshtastic_AdminMessage_add_contact_tag 66
#define meshtastic_AdminMessage_key_verification_tag 67
#define meshtastic_AdminMessage_reboot_ota_mode_tag 68
#define meshtastic_AdminMessage_factory_reset_device_tag 94
#define meshtastic_AdminMessage_reboot_ota_seconds_tag 95
#define meshtastic_AdminMessage_exit_simulator_tag 96
@@ -473,7 +454,6 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_ed
X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,add_contact,add_contact), 66) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,key_verification,key_verification), 67) \
X(a, STATIC, ONEOF, UENUM, (payload_variant,reboot_ota_mode,reboot_ota_mode), 68) \
X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_device,factory_reset_device), 94) \
X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulator), 96) \

View File

@@ -115,6 +115,7 @@ namespace graphics
extern int bannerSignalBars;
}
extern ScanI2C::DeviceAddress cardkb_found;
extern bool graphics::isMuted;
extern bool osk_found;
static const char *cannedMessagesConfigFile = "/prefs/cannedConf.proto";

View File

@@ -61,6 +61,7 @@
#if ARCH_PORTDUINO
#include "input/LinuxInputImpl.h"
#include "input/SeesawRotary.h"
#include "modules/Native/StoreForwardPlusPlus.h"
#include "modules/Telemetry/HostMetrics.h"
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
#include "modules/StoreForwardModule.h"
@@ -243,6 +244,9 @@ void setupModules()
#endif
#if ARCH_PORTDUINO
new HostMetricsModule();
#if SFPP_ENABLED
new StoreForwardPlusPlusModule();
#endif
#endif
#if HAS_TELEMETRY
new DeviceTelemetryModule();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,224 @@
#pragma once
#if __has_include("sqlite3.h")
#define SFPP_ENABLED 1
#include "Channels.h"
#include "ProtobufModule.h"
#include "Router.h"
#include "SinglePortModule.h"
#include "sqlite3.h"
#define SFPP_HASH_SIZE 32
#define SFPP_SHORT_HASH_SIZE 8
/**
* Store and forward ++ module
* There's an obvious need for a store-and-forward mechanism in Meshtastic.
* This module takes heavy inspiration from Git, building a chain of messages that can be synced between nodes.
* Each message is hashed, and the chain is built by hashing the previous commit hash and the current message hash.
* Nodes can request missing messages by requesting the next message after a given commit hash.
*
* The current focus is text messages, limited to the primary channel.
*
* Each chain is identified by a root hash, which is derived from the channelHash, the local nodenum, and the timestamp when
* created.
*
* Each message is also given a message hash, derived from the encrypted payload, the to, from, id.
* Notably not the timestamp, as we want these to match across nodes, even if the timestamps differ.
*
* The authoritative node for the chain will generate a commit hash for each message when adding it to the chain.
* The first message's commit hash is derived from the root hash and the message hash.
* Subsequent messages' commit hashes are derived from the previous commit hash and the current message hash.
* This allows a node to see only the last commit hash, and confirm it hasn't missed any messages.
*
* Nodes can request the next message in the chain by sending a LINK_REQUEST message with the root hash and the last known commit
* hash. Any node that has the next message can respond with a LINK_PROVIDE message containing the next message.
*
* When a satellite node sees a new text message, it stores it in a scratch database.
* These messages are periodically offered to the authoritative node for inclusion in the chain.
*
* The LINK_PROVIDE message does double-duty, sending both on-chain and off-chain messages.
* The differentiator is whether the commit hash is set or left empty.
*
* When a satellite node receives a canonical link message, it checks if it has the message in scratch.
* And evicts it when adding it to the canonical chain.
*
* This approach allows a node to know whether it has seen a given message before, or if it is new coming via SFPP.
* If new, and the timestamp is within the rebroadcast timeout, it will process that message as if it were just received from the
* mesh, allowing it to be decrypted, shown to the user, and rebroadcast.
*/
class StoreForwardPlusPlusModule : public ProtobufModule<meshtastic_StoreForwardPlusPlus>, private concurrency::OSThread
{
struct link_object {
uint32_t to;
uint32_t from;
uint32_t id;
uint32_t rx_time = 0;
ChannelHash channel_hash;
uint8_t encrypted_bytes[256] = {0};
size_t encrypted_len;
uint8_t message_hash[SFPP_HASH_SIZE] = {0};
size_t message_hash_len = 0;
uint8_t root_hash[SFPP_HASH_SIZE] = {0};
size_t root_hash_len = 0;
uint8_t commit_hash[SFPP_HASH_SIZE] = {0};
size_t commit_hash_len = 0;
uint32_t counter = 0;
std::string payload;
bool validObject = true; // set this false when a chain calulation fails, etc.
};
public:
/** Constructor
*
*/
StoreForwardPlusPlusModule();
/*
-Override the wantPacket method.
*/
virtual bool wantPacket(const meshtastic_MeshPacket *p) override
{
switch (p->decoded.portnum) {
case meshtastic_PortNum_TEXT_MESSAGE_APP:
case meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP:
return true;
default:
return false;
}
}
protected:
/** Called to handle a particular incoming message
@return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for
it
*/
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreForwardPlusPlus *t) override;
virtual int32_t runOnce() override;
private:
sqlite3 *ppDb;
sqlite3_stmt *chain_insert_stmt;
sqlite3_stmt *scratch_insert_stmt;
sqlite3_stmt *checkDup;
sqlite3_stmt *checkScratch;
sqlite3_stmt *removeScratch;
sqlite3_stmt *updatePayloadStmt;
sqlite3_stmt *getPayloadFromScratchStmt;
sqlite3_stmt *fromScratchStmt;
sqlite3_stmt *fromScratchByHashStmt;
sqlite3_stmt *getNextHashStmt;
sqlite3_stmt *getChainEndStmt;
sqlite3_stmt *getLinkStmt;
sqlite3_stmt *getHashFromRootStmt;
sqlite3_stmt *addRootToMappingsStmt;
sqlite3_stmt *getRootFromChannelHashStmt;
sqlite3_stmt *getFullRootHashStmt;
sqlite3_stmt *setChainCountStmt;
sqlite3_stmt *getChainCountStmt;
// For a given Meshtastic ChannelHash, fills the root_hash buffer with a 32-byte root hash
// returns true if the root hash was found
bool getRootFromChannelHash(ChannelHash, uint8_t *);
// For a given root hash, returns the ChannelHash
// can handle partial root hashes
ChannelHash getChannelHashFromRoot(uint8_t *_root_hash, size_t);
// given a root hash and commit hash, returns the next commit hash in the chain
// can handle partial root and commit hashes, always fills the buffer with 32 bytes
// returns true if a next hash was found
bool getNextHash(uint8_t *, size_t, uint8_t *, size_t, uint8_t *);
// For a given Meshtastic ChannelHash, fills the root_hash buffer with a 32-byte root hash
// but this function will add the root hash if it is not already present
// returns hash size or 0 if not found/added
size_t getOrAddRootFromChannelHash(ChannelHash, uint8_t *);
// adds the ChannelHash and root_hash to the mappings table
void addRootToMappings(ChannelHash, uint8_t *);
// requests the next message in the chain from the mesh network
// Sends a LINK_REQUEST message
void requestNextMessage(uint8_t *, size_t, uint8_t *, size_t);
// request the message X entries from the end.
// used to bootstrap a chain, without downloading all of the history
void requestMessageCount(uint8_t *, size_t, uint32_t);
// sends a LINK_PROVIDE message broadcasting the given link object
void broadcastLink(uint8_t *, size_t);
// sends a LINK_PROVIDE message broadcasting the given link object
void broadcastLink(link_object &, bool);
// sends a LINK_PROVIDE message broadcasting the given link object from scratch message store
bool sendFromScratch(uint8_t *);
// Adds the given link object to the canonical chain database
bool addToChain(link_object &);
// Adds an incoming text message to the scratch database
bool addToScratch(link_object &);
// sends a CANON_ANNOUNCE message, specifying the given root and commit hashes
void canonAnnounce(uint8_t *, uint8_t *, uint8_t *, uint32_t);
// checks if the message hash is present in the canonical chain database
bool isInDB(uint8_t *, size_t);
// checks if the message hash is present in the scratch database
bool isInScratch(uint8_t *, size_t);
// retrieves a link object from the scratch database
link_object getFromScratch(uint8_t *, size_t);
// removes a link object from the scratch database
void removeFromScratch(uint8_t *, size_t);
// fills the payload section with the decrypted data for the given message hash
// probably not needed for production, but useful for testing
void updatePayload(uint8_t *, size_t, std::string);
// Takes the decrypted MeshPacket and the encrypted packet copy, and builds a link_object
// Generates a message hash, but does not set the commit hash
link_object ingestTextPacket(const meshtastic_MeshPacket &, const meshtastic_MeshPacket *);
// ingests a LINK_PROVIDE message and builds a link_object
// confirms the root hash and commit hash
link_object ingestLinkMessage(meshtastic_StoreForwardPlusPlus *);
// retrieves a link object from the canonical chain database given a message hash
link_object getLink(uint8_t *, size_t);
// puts the encrypted payload back into the queue as if it were just received
void rebroadcastLinkObject(link_object &);
// Check if an incoming link object's commit hash matches the calculated commit hash
bool checkCommitHash(link_object &lo, uint8_t *commit_hash_bytes, size_t hash_len);
// given a partial root hash, looks up the full 32-byte root hash
// returns true if found
bool lookUpFullRootHash(uint8_t *partial_root_hash, size_t partial_root_hash_len, uint8_t *full_root_hash);
// update the mappings table to set the chain count for the given root hash
void setChainCount(uint8_t *, size_t, uint32_t);
// query the mappings table for the chain count for the given root hash
uint32_t getChainCount(uint8_t *, size_t);
link_object getLinkFromCount(uint32_t, uint8_t *, size_t);
// Track if we have a scheduled runOnce pending
// useful to not accudentally delay a scheduled runOnce
bool pendingRun = false;
// Once we have multiple chain types, we can extend this
enum chain_types {
channel_chain = 0,
};
uint32_t rebroadcastTimeout = 3600; // Messages older than this (in seconds) will not be rebroadcast
};
#endif

View File

@@ -170,7 +170,7 @@ bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
} else {
LOG_DEBUG(" Ignoring dummy neighbor info packet (single neighbor with nodeId 0, snr 0)");
}
} else if (getHopsAway(mp) == 0) {
} else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) {
LOG_DEBUG("Get or create neighbor: %u with snr %f", mp.from, mp.rx_snr);
// If the hopLimit is the same as hopStart, then it is a neighbor
getOrCreateNeighbor(mp.from, mp.from, 0,

View File

@@ -429,9 +429,7 @@ int32_t PositionModule::runOnce()
if (lastGpsSend == 0 || msSinceLastSend >= intervalMs) {
if (waitingForFreshPosition) {
#ifdef GPS_DEBUG
LOG_DEBUG("Skip initial position send; no fresh position since boot");
#endif
} else if (nodeDB->hasValidPosition(node)) {
lastGpsSend = now;

View File

@@ -69,7 +69,7 @@ class PositionModule : public ProtobufModule<meshtastic_Position>, private concu
// In event mode we want to prevent excessive position broadcasts
// we set the minimum interval to 5m
const uint32_t minimumTimeThreshold =
max(uint32_t(300000), Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30));
max(300000, Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30));
#else
const uint32_t minimumTimeThreshold =
Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30);

View File

@@ -58,11 +58,12 @@ void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketI
router->sendLocal(p); // we sometimes send directly to the local node
}
uint8_t RoutingModule::getHopLimitForResponse(const meshtastic_MeshPacket &mp)
uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit)
{
const int8_t hopsUsed = getHopsAway(mp);
if (hopsUsed >= 0) {
if (hopsUsed > (int32_t)(config.lora.hop_limit)) {
if (hopStart != 0) {
// Hops used by the request. If somebody in between running modified firmware modified it, ignore it
uint8_t hopsUsed = hopStart < hopLimit ? config.lora.hop_limit : hopStart - hopLimit;
if (hopsUsed > config.lora.hop_limit) {
// In event mode, we never want to send packets with more than our default 3 hops.
#if !(EVENTMODE) // This falls through to the default.
return hopsUsed; // If the request used more hops than the limit, use the same amount of hops

View File

@@ -20,7 +20,7 @@ class RoutingModule : public ProtobufModule<meshtastic_Routing>
uint8_t hopLimit = 0);
// Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response
uint8_t getHopLimitForResponse(const meshtastic_MeshPacket &mp);
uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit);
protected:
friend class Router;

View File

@@ -20,7 +20,7 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
switch (arg->getStatusType()) {
case STATUS_TYPE_POWER: {
meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)arg;
if (powerStatus->getHasUSB() || powerStatus->getIsCharging()) {
if (powerStatus->getHasUSB()) {
power_state = charging;
if (powerStatus->getBatteryChargePercent() >= 100) {
power_state = charged;

View File

@@ -45,9 +45,10 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event)
// Mute
case INPUT_BROKER_MSG_MUTE_TOGGLE:
if (moduleConfig.external_notification.enabled && externalNotificationModule) {
externalNotificationModule->setMute(externalNotificationModule->getMute());
IF_SCREEN(if (!externalNotificationModule->getMute()) externalNotificationModule->stopNow(); screen->showSimpleBanner(
externalNotificationModule->getMute() ? "Notifications\nEnabled" : "Notifications\nDisabled", 3000);)
bool isMuted = externalNotificationModule->getMute();
externalNotificationModule->setMute(!isMuted);
IF_SCREEN(graphics::isMuted = !isMuted; if (!isMuted) externalNotificationModule->stopNow();
screen->showSimpleBanner(isMuted ? "Notifications\nEnabled" : "Notifications\nDisabled", 3000);)
}
return 0;
// Bluetooth

View File

@@ -21,17 +21,18 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
// We only store/display messages destined for us.
devicestate.rx_text_message = mp;
devicestate.has_rx_text_message = true;
IF_SCREEN(
// Guard against running in MeshtasticUI or with no screen
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
// Store in the central message history
const StoredMessage &sm = messageStore.addFromPacket(mp);
#if HAS_SCREEN
// Guard against running in MeshtasticUI
if (screen && config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
// Store in the central message history
const StoredMessage &sm = messageStore.addFromPacket(mp);
// Pass message to renderer (banner + thread switching + scroll reset)
// Use the global Screen singleton to retrieve the current OLED display
auto *display = screen ? screen->getDisplayDevice() : nullptr;
graphics::MessageRenderer::handleNewMessage(display, sm, mp);
})
// Pass message to renderer (banner + thread switching + scroll reset)
// Use the global Screen singleton to retrieve the current OLED display
auto *display = screen ? screen->getDisplayDevice() : nullptr;
graphics::MessageRenderer::handleNewMessage(display, sm, mp);
}
#endif
// Only trigger screen wake if configuration allows it
if (shouldWakeOnReceivedMessage()) {
powerFSM.trigger(EVENT_RECEIVED_MSG);

View File

@@ -1,6 +1,5 @@
#include "TraceRouteModule.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
@@ -360,10 +359,10 @@ void TraceRouteModule::insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_Ro
}
// Only insert unknown hops if hop_start is valid
const int8_t hopsTaken = getHopsAway(p);
if (hopsTaken >= 0) {
if (p.hop_start != 0 && p.hop_limit <= p.hop_start) {
uint8_t hopsTaken = p.hop_start - p.hop_limit;
int8_t diff = hopsTaken - *route_count;
for (int8_t i = 0; i < diff; i++) {
for (uint8_t i = 0; i < diff; i++) {
if (*route_count < ROUTE_SIZE) {
route[*route_count] = NODENUM_BROADCAST; // This will represent an unknown hop
*route_count += 1;
@@ -371,7 +370,7 @@ void TraceRouteModule::insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_Ro
}
// Add unknown SNR values if necessary
diff = *route_count - *snr_count;
for (int8_t i = 0; i < diff; i++) {
for (uint8_t i = 0; i < diff; i++) {
if (*snr_count < ROUTE_SIZE) {
snr_list[*snr_count] = INT8_MIN; // This will represent an unknown SNR
*snr_count += 1;

View File

@@ -14,11 +14,11 @@
#include <atomic>
#include <mutex>
#ifdef NIMBLE_TWO
#include "NimBLEAdvertising.h"
#ifdef CONFIG_BT_NIMBLE_EXT_ADV
#include "NimBLEExtAdvertising.h"
#include "PowerStatus.h"
#endif
#include "PowerStatus.h"
#if defined(CONFIG_NIMBLE_CPP_IDF)
#include "host/ble_gap.h"
@@ -26,15 +26,12 @@
#include "nimble/nimble/host/include/host/ble_gap.h"
#endif
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6)
namespace
{
constexpr uint16_t kPreferredBleMtu = 517;
constexpr uint16_t kPreferredBleTxOctets = 251;
constexpr uint16_t kPreferredBleTxTimeUs = (kPreferredBleTxOctets + 14) * 8;
} // namespace
#endif
// Debugging options: careful, they slow things down quite a bit!
// #define DEBUG_NIMBLE_ON_READ_TIMING // uncomment to time onRead duration
@@ -313,11 +310,9 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
{
PhoneAPI::onNowHasData(fromRadioNum);
int currentNotifyCount = notifyCount.fetch_add(1);
uint8_t cc = bleServer->getConnectedCount();
#ifdef DEBUG_NIMBLE_NOTIFY
int currentNotifyCount = notifyCount.fetch_add(1);
uint8_t cc = bleServer->getConnectedCount();
// This logging slows things down when there are lots of packets going to the phone, like initial connection:
LOG_DEBUG("BLE notify(%d) fromNum: %d connections: %d", currentNotifyCount, fromRadioNum, cc);
#endif
@@ -326,13 +321,7 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
put_le32(val, fromRadioNum);
fromNumCharacteristic->setValue(val, sizeof(val));
#ifdef NIMBLE_TWO
// NOTE: I don't have any NIMBLE_TWO devices, but this line makes me suspicious, and I suspect it needs to just be
// notify().
fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE);
#else
fromNumCharacteristic->notify();
#endif
}
/// Check the current underlying physical link to see if the client is currently connected
@@ -397,12 +386,7 @@ static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
{
#ifdef NIMBLE_TWO
virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
#else
virtual void onWrite(NimBLECharacteristic *pCharacteristic)
#endif
void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &) override
{
// CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
// Assumption: onWrite is serialized by NimBLE, so we don't need to lock here against multiple concurrent onWrite calls.
@@ -449,11 +433,7 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
{
#ifdef NIMBLE_TWO
virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
#else
virtual void onRead(NimBLECharacteristic *pCharacteristic)
#endif
void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &) override
{
// CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
@@ -561,32 +541,27 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
{
#ifdef NIMBLE_TWO
public:
NimbleBluetoothServerCallback(NimbleBluetooth *ble) { this->ble = ble; }
explicit NimbleBluetoothServerCallback(NimbleBluetooth *ble) : ble(ble) {}
private:
NimbleBluetooth *ble;
virtual uint32_t onPassKeyDisplay()
#else
virtual uint32_t onPassKeyRequest()
#endif
uint32_t onPassKeyDisplay() override
{
uint32_t passkey = config.bluetooth.fixed_pin;
if (config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN) {
LOG_INFO("Use random passkey");
// This is the passkey to be entered on peer - we pick a number >100,000 to ensure 6 digits
passkey = random(100000, 999999);
}
LOG_INFO("*** Enter passkey %d on the peer side ***", passkey);
LOG_INFO("*** Enter passkey %06u on the peer side ***", passkey);
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
meshtastic::BluetoothStatus newStatus(std::to_string(passkey));
bluetoothStatus->updateStatus(&newStatus);
#if HAS_SCREEN // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
#if HAS_SCREEN
if (screen) {
screen->startAlert([passkey](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
char btPIN[16] = "888888";
@@ -615,39 +590,29 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
});
}
#endif
passkeyShowing = true;
passkeyShowing = true;
return passkey;
}
#ifdef NIMBLE_TWO
virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo)
#else
virtual void onAuthenticationComplete(ble_gap_conn_desc *desc)
#endif
void onAuthenticationComplete(NimBLEConnInfo &connInfo) override
{
LOG_INFO("BLE authentication complete");
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)
if (screen) {
screen->endAlert();
}
}
// Store the connection handle for future use
#ifdef NIMBLE_TWO
nimbleBluetoothConnHandle = connInfo.getConnHandle();
#else
nimbleBluetoothConnHandle = desc->conn_handle;
#endif
}
#ifdef NIMBLE_TWO
virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo)
void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) override
{
LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str());
@@ -672,21 +637,12 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
LOG_INFO("BLE conn %u initial MTU %u (target %u)", connHandle, connInfo.getMTU(), kPreferredBleMtu);
pServer->updateConnParams(connHandle, 6, 12, 0, 200);
}
#endif
#ifdef NIMBLE_TWO
virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason)
void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) override
{
LOG_INFO("BLE disconnect reason: %d", reason);
#else
virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc)
{
LOG_INFO("BLE disconnect");
#endif
#ifdef NIMBLE_TWO
if (ble->isDeInit)
return;
#endif
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
bluetoothStatus->updateStatus(&newStatus);
@@ -710,35 +666,69 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
bluetoothPhoneAPI->writeCount = 0;
}
// Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
memset(lastToRadio, 0, sizeof(lastToRadio));
nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE; // BLE_HS_CONN_HANDLE_NONE means "no connection"
nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE;
#ifdef NIMBLE_TWO
// Restart Advertising
ble->startAdvertising();
#else
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
if (!pAdvertising->start(0)) {
if (pAdvertising->isAdvertising()) {
LOG_DEBUG("BLE advertising already running");
} else {
LOG_ERROR("BLE failed to restart advertising");
}
}
#endif
}
};
static NimbleBluetoothToRadioCallback *toRadioCallbacks;
static NimbleBluetoothFromRadioCallback *fromRadioCallbacks;
void NimbleBluetooth::startAdvertising()
{
#if defined(CONFIG_BT_NIMBLE_EXT_ADV)
NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
NimBLEExtAdvertisement legacyAdvertising;
legacyAdvertising.setLegacyAdvertising(true);
legacyAdvertising.setScannable(true);
legacyAdvertising.setConnectable(true);
legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN);
if (powerStatus->getHasBattery() == 1) {
legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f));
}
legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID));
legacyAdvertising.setMinInterval(500);
legacyAdvertising.setMaxInterval(1000);
NimBLEExtAdvertisement legacyScanResponse;
legacyScanResponse.setLegacyAdvertising(true);
legacyScanResponse.setConnectable(true);
legacyScanResponse.setName(getDeviceName());
if (!pAdvertising->setInstanceData(0, legacyAdvertising)) {
LOG_ERROR("BLE failed to set legacyAdvertising");
} else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) {
LOG_ERROR("BLE failed to set legacyScanResponse");
} else if (!pAdvertising->start(0, 0, 0)) {
LOG_ERROR("BLE failed to start legacyAdvertising");
}
#else
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->reset();
pAdvertising->addServiceUUID(MESH_SERVICE_UUID);
if (powerStatus->getHasBattery() == 1) {
pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f));
}
NimBLEAdvertisementData scan;
scan.setName(getDeviceName());
pAdvertising->setScanResponseData(scan);
pAdvertising->enableScanResponse(true);
if (!pAdvertising->start(0)) {
LOG_ERROR("BLE failed to start advertising");
}
#endif
LOG_DEBUG("BLE Advertising started");
}
void NimbleBluetooth::shutdown()
{
// No measurable power saving for ESP32 during light-sleep(?)
#ifndef ARCH_ESP32
// Shutdown bluetooth for minimum power draw
LOG_INFO("Disable bluetooth");
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->reset();
@@ -746,7 +736,6 @@ void NimbleBluetooth::shutdown()
#endif
}
// Proper shutdown for ESP32. Needs reboot to reverse.
void NimbleBluetooth::deinit()
{
#ifdef ARCH_ESP32
@@ -760,21 +749,17 @@ void NimbleBluetooth::deinit()
digitalWrite(BLE_LED, LOW);
#endif
#endif
#ifndef NIMBLE_TWO
NimBLEDevice::deinit();
#endif
#endif
}
// Has initial setup been completed
bool NimbleBluetooth::isActive()
{
return bleServer;
return bleServer != nullptr;
}
bool NimbleBluetooth::isConnected()
{
return bleServer->getConnectedCount() > 0;
return bleServer && bleServer->getConnectedCount() > 0;
}
int NimbleBluetooth::getRssi()
@@ -818,7 +803,7 @@ void NimbleBluetooth::setup()
LOG_INFO("Init the NimBLE bluetooth module");
NimBLEDevice::init(getDeviceName());
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
NimBLEDevice::setPower(9);
#if NIMBLE_ENABLE_2M_PHY && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C6))
int mtuResult = NimBLEDevice::setMTU(kPreferredBleMtu);
@@ -851,11 +836,7 @@ void NimbleBluetooth::setup()
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
}
bleServer = NimBLEDevice::createServer();
#ifdef NIMBLE_TWO
NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this);
#else
NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback();
#endif
auto *serverCallbacks = new NimbleBluetoothServerCallback(this);
bleServer->setCallbacks(serverCallbacks, true);
setupService();
startAdvertising();
@@ -900,11 +881,7 @@ void NimbleBluetooth::setupService()
NimBLEService *batteryService = bleServer->createService(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic)
(uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY, 1);
#ifdef NIMBLE_TWO
NimBLE2904 *batteryLevelDescriptor = BatteryCharacteristic->create2904();
#else
NimBLE2904 *batteryLevelDescriptor = (NimBLE2904 *)BatteryCharacteristic->createDescriptor((uint16_t)0x2904);
#endif
batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8);
batteryLevelDescriptor->setNamespace(1);
batteryLevelDescriptor->setUnit(0x27ad);
@@ -912,54 +889,12 @@ void NimbleBluetooth::setupService()
batteryService->start();
}
void NimbleBluetooth::startAdvertising()
{
#ifdef NIMBLE_TWO
NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
NimBLEExtAdvertisement legacyAdvertising;
legacyAdvertising.setLegacyAdvertising(true);
legacyAdvertising.setScannable(true);
legacyAdvertising.setConnectable(true);
legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN);
if (powerStatus->getHasBattery() == 1) {
legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f));
}
legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID));
legacyAdvertising.setMinInterval(500);
legacyAdvertising.setMaxInterval(1000);
NimBLEExtAdvertisement legacyScanResponse;
legacyScanResponse.setLegacyAdvertising(true);
legacyScanResponse.setConnectable(true);
legacyScanResponse.setName(getDeviceName());
if (!pAdvertising->setInstanceData(0, legacyAdvertising)) {
LOG_ERROR("BLE failed to set legacyAdvertising");
} else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) {
LOG_ERROR("BLE failed to set legacyScanResponse");
} else if (!pAdvertising->start(0, 0, 0)) {
LOG_ERROR("BLE failed to start legacyAdvertising");
}
#else
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->reset();
pAdvertising->addServiceUUID(MESH_SERVICE_UUID);
pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
pAdvertising->start(0);
#endif
}
/// Given a level between 0-100, update the BLE attribute
void updateBatteryLevel(uint8_t level)
{
if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) {
BatteryCharacteristic->setValue(&level, 1);
#ifdef NIMBLE_TWO
BatteryCharacteristic->notify(&level, 1, BLE_HS_CONN_HANDLE_NONE);
#else
BatteryCharacteristic->notify();
#endif
}
}
@@ -974,11 +909,7 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length)
if (!bleServer || !isConnected() || length > 512) {
return;
}
#ifdef NIMBLE_TWO
logRadioCharacteristic->notify(logMessage, length, BLE_HS_CONN_HANDLE_NONE);
#else
logRadioCharacteristic->notify(logMessage, length, true);
#endif
}
void clearNVS()

View File

@@ -12,16 +12,11 @@ class NimbleBluetooth : BluetoothApi
bool isConnected();
int getRssi();
void sendLog(const uint8_t *logMessage, size_t length);
#if defined(NIMBLE_TWO)
void startAdvertising();
#endif
bool isDeInit = false;
private:
void setupService();
#if !defined(NIMBLE_TWO)
void startAdvertising();
#endif
};
void setBluetoothEnable(bool enable);

View File

@@ -279,7 +279,7 @@ void portduinoSetup()
// RAK6421-13300-S1:aabbcc123456:5ba85807d92138b7519cfb60460573af:3061e8d8
// <model string>:mac address :<16 random unique bytes in hexidecimal> : crc32
// crc32 is calculated on the eeprom string up to but not including the final colon
if (strlen(autoconf_product) < 6 && portduino_config.i2cdev != "") {
if (strlen(autoconf_product) < 6) {
try {
char *mac_start = nullptr;
char *devID_start = nullptr;
@@ -788,6 +788,12 @@ bool loadConfig(const char *configPath)
}
}
if (yamlConfig["StoreAndForward"]) {
portduino_config.sfpp_stratum0 = (yamlConfig["StoreAndForward"]["Stratum0"]).as<bool>(false);
portduino_config.sfpp_initial_sync = (yamlConfig["StoreAndForward"]["InitialSync"]).as<int>(10);
portduino_config.sfpp_hops = (yamlConfig["StoreAndForward"]["Hops"]).as<int>(3);
}
if (yamlConfig["General"]) {
portduino_config.MaxNodes = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
portduino_config.maxtophone = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);
@@ -869,4 +875,4 @@ void readGPIOFromYaml(YAML::Node sourceNode, pinMapping &destPin, int pinDefault
destPin.line = destPin.pin;
destPin.gpiochip = portduino_config.lora_default_gpiochip;
}
}
}

View File

@@ -169,6 +169,19 @@ extern struct portduino_config_struct {
int configDisplayMode = 0;
bool has_configDisplayMode = false;
// Store and Forward++
// DB location /var/lib/meshtasticd/
std::string sfpp_db_path = "/var/lib/meshtasticd/";
bool sfpp_stratum0 = false;
int sfpp_initial_sync = 10;
int sfpp_hops = 3;
int sfpp_announce_interval = 5; // minutes
uint32_t sfpp_max_chain = 1000;
// allowed root hashes
// upstream node
// Are we allowing unknown channel hashes? Does this even make sense?
// Allow DMs
// General
std::string mac_address = "";
bool mac_address_explicit = false;
@@ -488,6 +501,18 @@ extern struct portduino_config_struct {
out << YAML::EndMap; // Config
}
// StoreAndForward
if (sfpp_stratum0 || sfpp_initial_sync != 10 || sfpp_hops != 3 || sfpp_announce_interval != 5 || sfpp_max_chain != 1000) {
out << YAML::Key << "StoreAndForward" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "Stratum0" << YAML::Value << sfpp_stratum0;
out << YAML::Key << "InitialSync" << YAML::Value << sfpp_initial_sync;
out << YAML::Key << "Hops" << YAML::Value << sfpp_hops;
out << YAML::Key << "AnnounceInterval" << YAML::Value << sfpp_announce_interval;
out << YAML::Key << "MaxChainLength" << YAML::Value << sfpp_max_chain;
out << YAML::EndMap; // StoreAndForward
}
// General
out << YAML::Key << "General" << YAML::Value << YAML::BeginMap;
if (config_directory != "")

View File

@@ -418,9 +418,8 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi);
if (mp->rx_snr != 0)
jsonObj["snr"] = new JSONValue((float)mp->rx_snr);
const int8_t hopsAway = getHopsAway(*mp);
if (hopsAway >= 0) {
jsonObj["hops_away"] = new JSONValue((unsigned int)(hopsAway));
if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) {
jsonObj["hops_away"] = new JSONValue((unsigned int)(mp->hop_start - mp->hop_limit));
jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start));
}
@@ -451,9 +450,8 @@ std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPa
jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi);
if (mp->rx_snr != 0)
jsonObj["snr"] = new JSONValue((float)mp->rx_snr);
const int8_t hopsAway = getHopsAway(*mp);
if (hopsAway >= 0) {
jsonObj["hops_away"] = new JSONValue((unsigned int)(hopsAway));
if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) {
jsonObj["hops_away"] = new JSONValue((unsigned int)(mp->hop_start - mp->hop_limit));
jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start));
}
jsonObj["size"] = new JSONValue((unsigned int)mp->encrypted.size);

View File

@@ -358,9 +358,8 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
jsonObj["rssi"] = (int)mp->rx_rssi;
if (mp->rx_snr != 0)
jsonObj["snr"] = (float)mp->rx_snr;
const int8_t hopsAway = getHopsAway(*mp);
if (hopsAway >= 0) {
jsonObj["hops_away"] = (unsigned int)(hopsAway);
if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) {
jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit);
jsonObj["hop_start"] = (unsigned int)(mp->hop_start);
}
@@ -394,9 +393,8 @@ std::string MeshPacketSerializer::JsonSerializeEncrypted(const meshtastic_MeshPa
jsonObj["rssi"] = (int)mp->rx_rssi;
if (mp->rx_snr != 0)
jsonObj["snr"] = (float)mp->rx_snr;
const int8_t hopsAway = getHopsAway(*mp);
if (hopsAway >= 0) {
jsonObj["hops_away"] = (unsigned int)(hopsAway);
if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) {
jsonObj["hops_away"] = (unsigned int)(mp->hop_start - mp->hop_limit);
jsonObj["hop_start"] = (unsigned int)(mp->hop_start);
}
jsonObj["size"] = (unsigned int)mp->encrypted.size;

View File

@@ -2,7 +2,7 @@
[env:tbeam]
extends = esp32_base
board = ttgo-t-beam
board_level = extra
board_check = true
build_flags = ${esp32_base.build_flags}
-D TBEAM_V10
@@ -13,7 +13,7 @@ upload_speed = 921600
[env:tbeam-displayshield]
extends = env:tbeam
board_level = extra
build_flags =
${env:tbeam.build_flags}
-D USE_ST7796

View File

@@ -26,7 +26,6 @@ build_flags =
-D HAS_BLUETOOTH=1
-DCONFIG_BT_NIMBLE_EXT_ADV=1
-DCONFIG_BT_NIMBLE_MAX_EXT_ADV_INSTANCES=2
-D NIMBLE_TWO
monitor_speed=115200
lib_ignore =
NonBlockingRTTTL

View File

@@ -17,8 +17,6 @@ static const uint8_t MOSI = 11;
static const uint8_t MISO = 10;
static const uint8_t SCK = 13;
#define SPI_INTERFACES_COUNT 1
#define SPI_MOSI (11)
#define SPI_SCK (13)
#define SPI_MISO (10)
@@ -27,51 +25,4 @@ static const uint8_t SCK = 13;
// LEDs
#define LED_BUILTIN LED_GREEN
#ifdef _VARIANT_RAK3112_
/*
* Serial interfaces
*/
// TXD1 RXD1 on Base Board
#define PIN_SERIAL1_RX (44)
#define PIN_SERIAL1_TX (43)
/*
* Internal SPI to LoRa transceiver
*/
#define LORA_SX126X_SCK 5
#define LORA_SX126X_MISO 3
#define LORA_SX126X_MOSI 6
/*
* Analog pins
*/
#define PIN_A0 (21)
#define PIN_A1 (14)
/*
* Wire Interfaces
*/
#define WIRE_INTERFACES_COUNT 2
#define PIN_WIRE1_SDA (17)
#define PIN_WIRE1_SCL (18)
/*
* GPIO's
*/
#define WB_IO1 21
#define WB_IO2 2
// #define WB_IO2 14
#define WB_IO3 41
#define WB_IO4 42
#define WB_IO5 38
#define WB_IO6 39
// #define WB_SW1 35 NC
#define WB_A0 1
#define WB_A1 2
#define WB_CS 12
#define WB_LED1 46
#define WB_LED2 45
#endif
#endif /* Pins_Arduino_h */

View File

@@ -9,16 +9,3 @@ build_flags =
${esp32s3_base.build_flags}
-D RAK3312
-I variants/esp32s3/rak3312
[env:rak3112]
extends = esp32s3_base
board = wiscore_rak3312
board_level = pr
board_check = true
upload_protocol = esptool
build_flags =
${esp32_base.build_flags}
-D RAK3312
-D _VARIANT_RAK3112_
-I variants/esp32s3/rak3312

View File

@@ -18,6 +18,11 @@
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#endif
#define SX126X_POWER_EN (4)
#define PIN_POWER_EN PIN_3V3_EN
#define PIN_3V3_EN (14)
#define LED_GREEN 46
#define LED_BLUE 45
@@ -30,30 +35,10 @@
#define LED_STATE_ON 1 // State when LED is litted
#define BATTERY_PIN 1
#define ADC_CHANNEL ADC1_GPIO1_CHANNEL
#ifdef _VARIANT_RAK3112_ // Modular variant (stamp)
#define ADC_MULTIPLIER 2.11
#define BUTTON_NEED_PULLUP
#define HAS_SDCARD
#define SDCARD_USE_SPI1
#define SDCARD_CS SPI_CS
#define I2C_SDA1 PIN_WIRE1_SDA
#define I2C_SCL1 PIN_WIRE1_SCL
#else // Generic 3312 variant (40-pin standard connector)
#define ADC_MULTIPLIER 1.667
#define SX126X_POWER_EN (4)
#define PIN_POWER_EN PIN_3V3_EN
#define PIN_3V3_EN (14)
#define HAS_GPS 1
#define GPS_TX_PIN 43
#define GPS_RX_PIN 44
#endif
#define BATTERY_PIN 1
#define ADC_CHANNEL ADC1_GPIO1_CHANNEL
#define ADC_MULTIPLIER 1.667

View File

@@ -100,5 +100,3 @@
#define MODEM_DTR 8
#define MODEM_RX 10
#define MODEM_TX 11
#define HAS_PHYSICAL_KEYBOARD 1

View File

@@ -23,7 +23,6 @@
#define SCREEN_TRANSITION_FRAMERATE 5
#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness
#define USE_TFTDISPLAY 1
#define HAS_PHYSICAL_KEYBOARD 1
#define HAS_TOUCHSCREEN 1
#define SCREEN_TOUCH_INT 16

View File

@@ -21,7 +21,6 @@
#define SCREEN_TRANSITION_FRAMERATE 5
#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness
#define USE_TFTDISPLAY 1
#define HAS_PHYSICAL_KEYBOARD 1
#define I2C_SDA SDA
#define I2C_SCL SCL

View File

@@ -9,7 +9,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Melopero RV3028 packageName=melopero/library/Melopero RV3028
melopero/Melopero RV3028@1.2.0
build_src_filter = ${portduino_base.build_src_filter}
build_src_filter = ${portduino_base.build_src_filter} +<modules/Native/>
[env:native]
extends = native_base
@@ -20,6 +20,7 @@ build_flags = ${native_base.build_flags}
!pkg-config --libs openssl --silence-errors || :
!pkg-config --cflags --libs sdl2 --silence-errors || :
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
!pkg-config --cflags --libs sqlite3 --silence-errors || :
[env:native-tft]
extends = native_base
@@ -46,6 +47,7 @@ build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunctio
!pkg-config --libs openssl --silence-errors || :
!pkg-config --cflags --libs sdl2 --silence-errors || :
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
!pkg-config --cflags --libs sqlite3 --silence-errors || :
build_src_filter =
${native_base.build_src_filter}
@@ -75,6 +77,7 @@ build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections
!pkg-config --libs libulfius --silence-errors || :
!pkg-config --libs openssl --silence-errors || :
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
!pkg-config --cflags --libs sqlite3 --silence-errors || :
build_src_filter =
${native_base.build_src_filter}
@@ -108,6 +111,7 @@ build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -l
!pkg-config --libs libulfius --silence-errors || :
!pkg-config --libs openssl --silence-errors || :
!pkg-config --cflags --libs libbsd-overlay --silence-errors || :
!pkg-config --cflags --libs sqlite3 --silence-errors || :
build_src_filter = ${env:native-tft.build_src_filter}
[env:coverage]

View File

@@ -15,5 +15,5 @@ lib_deps =
${nrf52840_base.lib_deps}
# renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM
khoih-prog/nRF52_PWM@1.0.1
; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
; lewisxhe/SensorLib@0.3.3
# renovate: datasource=custom.pio depName=PCF8563 packageName=lewisxhe/library/PCF8563_Library
lewisxhe/PCF8563_Library@1.0.1

View File

@@ -114,8 +114,7 @@ extern "C" {
#define LR11X0_DIO_AS_RF_SWITCH
// PCF8563 RTC Module
// REVISIT https://github.com/meshtastic/firmware/pull/9084
// #define PCF8563_RTC 0x51
#define PCF8563_RTC 0x51
#ifdef __cplusplus
}

View File

@@ -12,5 +12,5 @@ build_flags = ${nrf52840_base.build_flags}
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M6>
lib_deps =
${nrf52840_base.lib_deps}
; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
; lewisxhe/SensorLib@0.3.3
# renovate: datasource=custom.pio depName=PCF8563 packageName=lewisxhe/library/PCF8563_Library
lewisxhe/PCF8563_Library@1.0.1

View File

@@ -41,30 +41,3 @@ void initVariant()
pinMode(VDD_FLASH_EN, OUTPUT);
digitalWrite(VDD_FLASH_EN, HIGH);
}
// called from main-nrf52.cpp during the cpuDeepSleep() function
void variant_shutdown()
{
// This sets the pin to OUTPUT and LOW for the pins *not* in the if block.
for (int pin = 0; pin < 48; pin++) {
if (pin == PIN_GPS_EN || pin == ADC_CTRL || pin == PIN_BUTTON1 || pin == PIN_SPI_MISO || pin == PIN_SPI_MOSI ||
pin == PIN_SPI_SCK) {
continue;
}
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW);
if (pin >= 32) {
NRF_P1->DIRCLR = (1 << (pin - 32));
} else {
NRF_GPIO->DIRCLR = (1 << pin);
}
}
digitalWrite(PIN_GPS_EN, LOW);
digitalWrite(ADC_CTRL, LOW);
// digitalWrite(RTC_POWER, LOW);
nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW;
nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense1);
}

View File

@@ -44,10 +44,8 @@ extern "C" {
#define LED_BLUE -1
#define LED_CHARGE (12)
#define LED_PAIRING (7)
#define PIN_LED2 LED_PAIRING
#define LED_STATE_ON HIGH
#define LED_STATE_OFF LOW
#define LED_STATE_ON 1
// USB power detection
#define EXT_PWR_DETECT (13)
@@ -121,8 +119,7 @@ static const uint8_t A0 = PIN_A0;
#define PIN_SERIAL2_TX (24)
// PCF8563 RTC Module
// REVISIT https://github.com/meshtastic/firmware/pull/9084
// #define PCF8563_RTC 0x51
#define PCF8563_RTC 0x51
// SPI
#define SPI_INTERFACES_COUNT 1

View File

@@ -103,7 +103,7 @@ static const uint8_t A0 = PIN_A0;
#define EXTERNAL_FLASH_USE_QSPI
// Add a delay on startup to allow LoRa and GPS to power up
#define PERIPHERAL_WARMUP_MS 100
#define PIN_PWR_DELAY_MS 100
/*
* Lora radio
@@ -178,4 +178,4 @@ static const uint8_t A0 = PIN_A0;
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif
#endif

View File

@@ -1,4 +1,4 @@
[VERSION]
major = 2
minor = 7
build = 17
build = 18