Compare commits

...

25 Commits

Author SHA1 Message Date
Jason P
c7844410ac Merge branch 'develop' into baseui_temporarymute 2025-12-28 11:49:09 -06:00
Jonathan Bennett
63aadba526 Use IF_SCREEN macro to guard against null screen object 2025-12-28 11:33:14 -06:00
Jason P
4c27c84b5f Remove banner notification, we display the icon immediately 2025-12-27 21:13:33 -06:00
Jason P
fbd866fda3 Only show Temporary Mute if it applies 2025-12-27 21:07:09 -06:00
Jason P
ae770e9c6c Add Temporary Mute to Home frame and unbury Notifications 2025-12-27 20:57:08 -06:00
Jason P
759a972f77 GPS Menu Validation Fix - Missed in Reviews (#9093)
* Reviews sometimes miss things, whoops
* Validation is hard - but this fixes it
2025-12-27 12:42:22 -06:00
Jason P
d1db4433f4 Add menus for Smart Position, Broadcast Interval and Position Interval (#9080)
* Add menus for Smart Position, Broadcast Interval and Position Interval

* Realigned time intervals to match Android app options

* Fixed missing last option
2025-12-27 11:18:16 -06:00
Jason P
2c68710e8c Improve sanitizeString function for Node Names (#9086) 2025-12-27 11:17:55 -06:00
Tom Fifield
9f8f4471aa PIN_PWR_DELAY_MS --> PERIPHERAL_WARMUP_MS (#8467)
It turns out we had two methods for delaying startup while peripherals
warmed up. They were invented within months of each other and just missed
the chance to merge.

Let's delete PIN_PWR_DELAY_MS and use PERIPHERAL_WARMUP_MS, since it's
most common and earlier in the sequence.
2025-12-27 22:36:34 +11:00
github-actions[bot]
cf03caff10 Upgrade trunk (#9076)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-26 22:22:32 -06:00
Jonathan Bennett
ac937766cd In autoconf, don't probe Wire unless i2c device is set (#9081)
Found another bit of code that crashes my desktop, by probing the wrong i2c bus.
2025-12-26 22:22:32 -06:00
github-actions[bot]
9e215213a7 Upgrade trunk (#9072)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-26 22:22:32 -06:00
Jonathan Bennett
4fbe5356ca M6 shutdown and LEDs work (#9065)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-12-26 22:22:32 -06:00
github-actions[bot]
e899e84ef9 Upgrade trunk (#9067)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-26 22:22:32 -06:00
github-actions[bot]
69c3c0151f Upgrade trunk (#9047)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2025-12-26 22:22:32 -06:00
renovate[bot]
2b977b4830 Update meshtastic-esp8266-oled-ssd1306 digest to b34c681 (#9062)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-26 22:22:32 -06:00
Jonathan Bennett
b88a8bf968 In statusLEDModule, also detect isCharging (#9050) 2025-12-26 22:22:32 -06:00
Ben Meadors
514f8590fe Revert "Automated version bumps (#9025)"
This reverts commit 1021d967da.
2025-12-26 22:22:32 -06:00
Ben Meadors
cbd40faa89 Fix -ota.zip in manifest and build output 2025-12-26 22:22:32 -06:00
Jorropo
7dd9c8b223 pass GH_TOKEN to shame's gh run download step (#9087) 2025-12-26 22:04:18 -05:00
Austin
3473c32e81 Fix PR#8061 SensorLib nRF ThinkNode M-series (#9084) 2025-12-26 18:55:47 -06:00
Austin
db2224ed0e pioarduino .gitignore (#9085)
I'm already going insane!
2025-12-26 17:45:43 -06:00
Austin
29c5713a7e Correctly set type for event_mode max() position threshold (#9083)
Fixes EVENT_MODE firmware builds
2025-12-27 10:04:43 +11:00
Jorropo
82cf2bf16a action: skip trying to comment binary size change results if it is not a PR (#9033)
* action: skip trying to comment binary size change results if it is not a PR

* action: fix halucinations in the clanker's code

* action: cleanup one line

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Austin <vidplace7@gmail.com>
2025-12-26 17:26:48 -05:00
Jason P
ef530db44c Implement HAS_PHYSICAL_KEYBOARD for devices with physical keyboards (#9071)
- Implement HAS_PHYSICAL_KEYBOARD for devices with physical keyboards
- Add HAS_PHYSICAL_KEYBOARD to variant.h for:
  - TDeck
  - TLora Pager
  - TDeck Pro
2025-12-26 07:34:25 -06:00
32 changed files with 419 additions and 103 deletions

View File

@@ -240,6 +240,7 @@ 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
@@ -253,18 +254,24 @@ jobs:
uses: actions/upload-artifact@v6
id: upload-manifest
with:
name: manifests-all
name: manifests-${{ github.sha }}
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.head_ref }}
head: ${{ github.sha }}
- name: Download the old manifests
run: gh run download -R ${{ github.repository }} --commit ${{ env.MERGE_BASE }} --name manifests-all --dir manifest-old/
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/
release-artifacts:

9
.gitignore vendored
View File

@@ -41,3 +41,12 @@ 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

@@ -9,9 +9,9 @@ plugins:
lint:
enabled:
- checkov@3.2.495
- renovate@42.64.1
- renovate@42.66.8
- prettier@3.7.4
- trufflehog@3.92.3
- trufflehog@3.92.4
- yamllint@1.37.1
- bandit@1.9.2
- trivy@0.68.2

View File

@@ -21,13 +21,14 @@ 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/$basename.zip
cp $BUILDDIR/$basename.zip $OUTDIR/$ota_basename.zip
echo "Copying NRF52 UF2 file"
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2

View File

@@ -87,9 +87,6 @@
</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,6 +17,8 @@ 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",
@@ -32,8 +34,11 @@ 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": p,
"name": manifest_name,
"md5": f.get_content_hash(), # Returns MD5 hash
"bytes": f.get_size() # Returns file size in bytes
}

6
debian/changelog vendored
View File

@@ -1,9 +1,3 @@
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/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.zip
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/b34c6817c25d6faabb3a8a162b5d14fb75395433.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

View File

@@ -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) {
if (isInverted && !force_no_invert) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mute_symbol_width + 2, mute_symbol_height + 2);
display->setColor(BLACK);
@@ -470,18 +470,49 @@ 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;
for (char c : input) {
if (std::isalnum(static_cast<unsigned char>(c)) || isAllowedPunctuation(c)) {
// 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)) {
output += c;
inReplacement = false;
} else {
if (!inReplacement) {
output += 0xbf; // ISO-8859-1 for inverted question mark
output += static_cast<char>(0xBF); // ISO-8859-1 for inverted question mark
inReplacement = true;
}
}

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,12 +843,20 @@ void menuHandler::messageViewModeMenu()
void menuHandler::homeBaseMenu()
{
enum optionsNumbers { Back, Backlight, Position, Preset, Freetext, Sleep, enumEnd };
enum optionsNumbers { Back, Mute, Backlight, Position, Preset, Freetext, Sleep, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
if (config.device.buzzer_mode != meshtastic_Config_DeviceConfig_BuzzerMode_DISABLED) {
if (!isMuted) {
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;
@@ -872,7 +880,14 @@ void menuHandler::homeBaseMenu()
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Backlight) {
if (selected == Mute) {
if (moduleConfig.external_notification.enabled && externalNotificationModule) {
bool isMuted = externalNotificationModule->getMute();
externalNotificationModule->setMute(!isMuted);
IF_SCREEN(graphics::isMuted = !isMuted; if (!isMuted) externalNotificationModule->stopNow();)
}
} else if (selected == Backlight) {
screen->setOn(false);
#if defined(PIN_EINK_EN)
if (uiconfig.screen_brightness == 1) {
uiconfig.screen_brightness = 0;
@@ -949,6 +964,7 @@ void menuHandler::systemBaseMenu()
optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Notifications;
optionsArray[options] = "Display Options";
optionsEnumArray[options++] = ScreenOptions;
@@ -985,7 +1001,7 @@ void menuHandler::systemBaseMenu()
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Notifications) {
menuHandler::menuQueue = menuHandler::notifications_menu;
menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
screen->runNow();
} else if (selected == ScreenOptions) {
menuHandler::menuQueue = menuHandler::screen_options_menu;
@@ -1092,11 +1108,23 @@ void menuHandler::favoriteBaseMenu()
void menuHandler::positionBaseMenu()
{
enum optionsNumbers { Back, GPSToggle, GPSFormat, CompassMenu, CompassCalibrate, enumEnd };
enum optionsNumbers {
Back,
GPSToggle,
GPSFormat,
CompassMenu,
CompassCalibrate,
GPSSmartPosition,
GPSUpdateInterval,
GPSPositionBroadcast,
enumEnd
};
static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "GPS Format", "Compass"};
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, GPSFormat, CompassMenu};
int options = 4;
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;
if (accelerometerThread) {
optionsArray[options] = "Compass Calibrate";
@@ -1104,7 +1132,7 @@ void menuHandler::positionBaseMenu()
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Position Action";
bannerOptions.message = "GPS Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -1120,6 +1148,15 @@ 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);
@@ -1346,6 +1383,217 @@ 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()
@@ -1372,9 +1620,9 @@ void menuHandler::BluetoothToggleMenu()
void menuHandler::BuzzerModeMenu()
{
static const char *optionsArray[] = {"All Enabled", "Disabled", "Notifications", "System Only", "DMs Only"};
static const char *optionsArray[] = {"All Enabled", "All Disabled", "Notifications", "System Only", "DMs Only"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Buzzer Mode";
bannerOptions.message = "Notification Sounds";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -1749,30 +1997,6 @@ 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
@@ -2126,6 +2350,15 @@ 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();
@@ -2181,9 +2414,6 @@ 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,6 +22,9 @@ 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,
@@ -36,7 +39,6 @@ class menuHandler
number_test,
wifi_toggle_menu,
bluetooth_toggle_menu,
notifications_menu,
screen_options_menu,
power_menu,
system_base_menu,
@@ -77,6 +79,9 @@ 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);
@@ -92,7 +97,6 @@ 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

@@ -27,7 +27,9 @@ 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,7 +45,9 @@ 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,7 +29,9 @@ 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,11 +1186,6 @@ 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,
@@ -1465,8 +1460,10 @@ 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,15 +195,13 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
p.rx_time = getValidTime(RTCQualityFromNet); // Record the time the packet arrived from the phone
#if HAS_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
}
#endif
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
})
// Send the packet into the mesh
DEBUG_HEAP_BEFORE;
auto a = packetPool.allocCopy(p);

View File

@@ -805,7 +805,8 @@ 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)
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \
defined(ELECROW_ThinkNode_M6)
// 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;

View File

@@ -429,7 +429,9 @@ 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(300000, Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30));
max(uint32_t(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

@@ -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()) {
if (powerStatus->getHasUSB() || powerStatus->getIsCharging()) {
power_state = charging;
if (powerStatus->getBatteryChargePercent() >= 100) {
power_state = charged;

View File

@@ -21,18 +21,17 @@ 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 HAS_SCREEN
// Guard against running in MeshtasticUI
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
// Store in the central message history
const StoredMessage &sm = messageStore.addFromPacket(mp);
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);
// 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
// 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);
})
// Only trigger screen wake if configuration allows it
if (shouldWakeOnReceivedMessage()) {
powerFSM.trigger(EVENT_RECEIVED_MSG);

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) {
if (strlen(autoconf_product) < 6 && portduino_config.i2cdev != "") {
try {
char *mac_start = nullptr;
char *devID_start = nullptr;
@@ -869,4 +869,4 @@ void readGPIOFromYaml(YAML::Node sourceNode, pinMapping &destPin, int pinDefault
destPin.line = destPin.pin;
destPin.gpiochip = portduino_config.lora_default_gpiochip;
}
}
}

View File

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

View File

@@ -23,6 +23,7 @@
#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,6 +21,7 @@
#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

@@ -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=PCF8563 packageName=lewisxhe/library/PCF8563_Library
lewisxhe/PCF8563_Library@1.0.1
; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
; lewisxhe/SensorLib@0.3.3

View File

@@ -114,7 +114,8 @@ extern "C" {
#define LR11X0_DIO_AS_RF_SWITCH
// PCF8563 RTC Module
#define PCF8563_RTC 0x51
// REVISIT https://github.com/meshtastic/firmware/pull/9084
// #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=PCF8563 packageName=lewisxhe/library/PCF8563_Library
lewisxhe/PCF8563_Library@1.0.1
; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
; lewisxhe/SensorLib@0.3.3

View File

@@ -41,3 +41,30 @@ 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,8 +44,10 @@ extern "C" {
#define LED_BLUE -1
#define LED_CHARGE (12)
#define LED_PAIRING (7)
#define PIN_LED2 LED_PAIRING
#define LED_STATE_ON 1
#define LED_STATE_ON HIGH
#define LED_STATE_OFF LOW
// USB power detection
#define EXT_PWR_DETECT (13)
@@ -119,7 +121,8 @@ static const uint8_t A0 = PIN_A0;
#define PIN_SERIAL2_TX (24)
// PCF8563 RTC Module
#define PCF8563_RTC 0x51
// REVISIT https://github.com/meshtastic/firmware/pull/9084
// #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 PIN_PWR_DELAY_MS 100
#define PERIPHERAL_WARMUP_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 = 18
build = 17