Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
781b1ba
Enable Lite and Narrow regions and introduce getEffectiveDutyCycle fo…
NomDeTom Apr 10, 2026
4755865
Add TrafficType enum and extend getConfiguredOrDefaultMsScaled to man…
NomDeTom Apr 10, 2026
c127304
Refactor telemetry modules to include TrafficType in getConfiguredOrD…
NomDeTom Apr 10, 2026
fb169b3
Update submodule protobufs to latest commit
NomDeTom Apr 10, 2026
ea075d1
Merge branch 'develop' into newer-presets
NomDeTom Apr 10, 2026
bc47e41
Add support for new region presets and modem presets in menu options
NomDeTom Apr 10, 2026
8f701e7
Add new LoRa region codes and modem presets for EU bands
NomDeTom Apr 14, 2026
05b364d
boof
NomDeTom Apr 14, 2026
136bd28
Merge branch 'meshtastic:develop' into newer-presets
NomDeTom Apr 14, 2026
e707860
Add modem presets for LITE and NARROW configurations
NomDeTom Apr 14, 2026
e4f75d3
Update subproject commit reference in protobufs
NomDeTom Apr 14, 2026
2361217
Update protobufs
NomDeTom Apr 14, 2026
1baaeff
Merge pull request #14 from NomDeTom/create-pull-request/update-proto…
NomDeTom Apr 14, 2026
c589bfd
Refactor modem preset definitions to use macro for consistency and cl…
NomDeTom Apr 14, 2026
c8592dc
Refactor modem preset cases to use PRESET macro for consistency
NomDeTom Apr 15, 2026
dd5c689
Merge remote-tracking branch 'meshtasticupstream/develop' into newer-…
NomDeTom Apr 30, 2026
5d84c72
fix: update LoRa region code for EU 868 narrowband configuration
NomDeTom Apr 30, 2026
8f2010e
feat: implement coding rate adjustments and add unit tests for retran…
NomDeTom May 1, 2026
cd2872e
refactor: simplify retransmission logic and improve logging for codin…
NomDeTom May 1, 2026
7191ed1
feat: add retransmission failure count to Router class
NomDeTom May 1, 2026
60edf71
Merge branch 'develop' into CRCRRCRRR
NomDeTom May 2, 2026
147e8a0
fix: correct airtime calculation
NomDeTom May 3, 2026
30fa5a7
improve retransmission coding-rate selection and airtime calculation …
NomDeTom May 3, 2026
b763faf
Merge branch 'develop' into CRCRRCRRR
NomDeTom May 3, 2026
a5a577a
Merge branch 'develop' into CRCRRCRRR
NomDeTom May 5, 2026
47adeef
Merge branch 'develop' into CRCRRCRRR
NomDeTom May 6, 2026
f4ce64c
OVERRIDE_SLOT override
NomDeTom May 6, 2026
a2178ba
Merge branch 'meshtastic:develop' into CRCRRCRRR
NomDeTom May 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions src/DisplayFormatters.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "DisplayFormatters.h"
#include "MeshRadio.h"

const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
bool usePreset)
Expand All @@ -11,33 +12,45 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
}

switch (preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
case PRESET(SHORT_TURBO):
return useShortName ? "ShortT" : "ShortTurbo";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
case PRESET(SHORT_SLOW):
return useShortName ? "ShortS" : "ShortSlow";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
case PRESET(SHORT_FAST):
return useShortName ? "ShortF" : "ShortFast";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
case PRESET(MEDIUM_SLOW):
return useShortName ? "MedS" : "MediumSlow";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
case PRESET(MEDIUM_FAST):
return useShortName ? "MedF" : "MediumFast";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
case PRESET(LONG_SLOW):
return useShortName ? "LongS" : "LongSlow";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
case PRESET(LONG_FAST):
return useShortName ? "LongF" : "LongFast";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
case PRESET(LONG_TURBO):
return useShortName ? "LongT" : "LongTurbo";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
case PRESET(LONG_MODERATE):
return useShortName ? "LongM" : "LongMod";
break;
case PRESET(LITE_FAST):
return useShortName ? "LiteF" : "LiteFast";
break;
case PRESET(LITE_SLOW):
return useShortName ? "LiteS" : "LiteSlow";
break;
case PRESET(NARROW_FAST):
return useShortName ? "NarF" : "NarrowFast";
break;
case PRESET(NARROW_SLOW):
return useShortName ? "NarS" : "NarrowSlow";
break;
default:
return useShortName ? "Custom" : "Invalid";
break;
Expand Down
7 changes: 4 additions & 3 deletions src/airtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,12 @@ bool AirTime::isTxAllowedChannelUtil(bool polite)

bool AirTime::isTxAllowedAirUtil()
{
if (!config.lora.override_duty_cycle && myRegion->dutyCycle < 100) {
if (utilizationTXPercent() < myRegion->dutyCycle * polite_duty_cycle_percent / 100) {
float effectiveDutyCycle = getEffectiveDutyCycle();
if (!config.lora.override_duty_cycle && effectiveDutyCycle < 100) {
if (utilizationTXPercent() < effectiveDutyCycle * polite_duty_cycle_percent / 100) {
return true;
} else {
LOG_WARN("TX air util. >%f%%. Skip send", myRegion->dutyCycle * polite_duty_cycle_percent / 100);
LOG_WARN("TX air util. >%f%%. Skip send", effectiveDutyCycle * polite_duty_cycle_percent / 100);
return false;
}
}
Expand Down
92 changes: 59 additions & 33 deletions src/graphics/draw/MenuHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#if HAS_SCREEN
#include "ClockRenderer.h"
#include "Default.h"
#include "DisplayFormatters.h"
#include "GPS.h"
#include "MenuHandler.h"
#include "MeshRadio.h"
Expand Down Expand Up @@ -180,6 +181,8 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
{"US", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_US},
{"EU_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_433},
{"EU_868", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_868},
{"EU_866", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_866},
{"EU_868_NARROW", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_N_868},
{"CN", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_CN},
{"JP", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_JP},
{"ANZ", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ANZ},
Expand All @@ -203,6 +206,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
{"KZ_863", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_KZ_863},
{"NP_865", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_NP_865},
{"BR_902", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_BR_902},

};

constexpr size_t regionCount = sizeof(regionOptions) / sizeof(regionOptions[0]);
Expand Down Expand Up @@ -244,7 +248,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
#endif
config.lora.tx_enabled = true;
initRegion();
if (myRegion->dutyCycle < 100) {
if (getEffectiveDutyCycle() < 100) {
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
}

Expand Down Expand Up @@ -378,42 +382,64 @@ void menuHandler::FrequencySlotPicker()
screen->showOverlayBanner(bannerOptions);
}

void menuHandler::radioPresetPicker()
{
static const RadioPresetOption presetOptions[] = {
{"Back", OptionsAction::Back},
{"LongTurbo", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO},
{"LongModerate", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE},
{"LongFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST},
{"MediumSlow", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW},
{"MediumFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST},
{"ShortSlow", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW},
{"ShortFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST},
{"ShortTurbo", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO},
};

constexpr size_t presetCount = sizeof(presetOptions) / sizeof(presetOptions[0]);
static std::array<const char *, presetCount> presetLabels{};
// Maximum presets any region can have + 1 for Back
static constexpr int MAX_PRESET_OPTIONS = 16;

auto bannerOptions =
createStaticBannerOptions("Radio Preset", presetOptions, presetLabels, [](const RadioPresetOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuHandler::menuQueue = menuHandler::LoraMenu;
screen->runNow();
return;
}
static BannerOverlayOptions buildRegionPresetBanner()
{
// Static storage reused each call — safe because the banner is shown immediately after.
static const char *optionsArray[MAX_PRESET_OPTIONS];
static int optionsEnumArray[MAX_PRESET_OPTIONS];
static char presetLabelBuf[MAX_PRESET_OPTIONS][12]; // scratch space for name copies
int count = 0;

optionsArray[count] = "Back";
optionsEnumArray[count++] = -1;

if (myRegion && myRegion->profile) {
const meshtastic_Config_LoRaConfig_ModemPreset *presets = myRegion->getAvailablePresets();
size_t numPresets = myRegion->getNumPresets();
for (size_t i = 0; i < numPresets && count < MAX_PRESET_OPTIONS; ++i) {
const char *name = DisplayFormatters::getModemPresetDisplayName(presets[i], false, true);
strncpy(presetLabelBuf[count], name, sizeof(presetLabelBuf[count]) - 1);
presetLabelBuf[count][sizeof(presetLabelBuf[count]) - 1] = '\0';
optionsArray[count] = presetLabelBuf[count];
optionsEnumArray[count++] = static_cast<int>(presets[i]);
}
}

if (!option.hasValue) {
return;
}
int initialSelection = 0;
for (int i = 1; i < count; ++i) {
if (optionsEnumArray[i] == static_cast<int>(config.lora.modem_preset)) {
initialSelection = i;
break;
}
}

config.lora.modem_preset = option.value;
config.lora.channel_num = 0; // Reset to default channel for the preset
config.lora.override_frequency = 0; // Clear any custom frequency
service->reloadConfig(SEGMENT_CONFIG);
});
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Radio Preset";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = static_cast<uint8_t>(count);
bannerOptions.InitialSelected = initialSelection;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == -1) {
menuHandler::menuQueue = menuHandler::LoraMenu;
screen->runNow();
return;
}
config.lora.use_preset = true;
config.lora.modem_preset = static_cast<meshtastic_Config_LoRaConfig_ModemPreset>(selected);
config.lora.channel_num = 0; // Reset to default channel for the preset
config.lora.override_frequency = 0; // Clear any custom frequency
service->reloadConfig(SEGMENT_CONFIG);
};
return bannerOptions;
}

screen->showOverlayBanner(bannerOptions);
void menuHandler::radioPresetPicker()
{
screen->showOverlayBanner(buildRegionPresetBanner());
}

void menuHandler::twelveHourPicker()
Expand Down
17 changes: 9 additions & 8 deletions src/graphics/draw/UIRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#if HAS_SCREEN
#include "CompassRenderer.h"
#include "GPSStatus.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "NodeListRenderer.h"
Expand Down Expand Up @@ -816,16 +817,16 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
// Helper to get SNR limit based on modem preset
auto getSnrLimit = [](meshtastic_Config_LoRaConfig_ModemPreset preset) -> float {
switch (preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
case PRESET(LONG_SLOW):
case PRESET(LONG_MODERATE):
case PRESET(LONG_FAST):
return -6.0f;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
case PRESET(MEDIUM_SLOW):
case PRESET(MEDIUM_FAST):
return -5.5f;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
case PRESET(SHORT_SLOW):
case PRESET(SHORT_FAST):
case PRESET(SHORT_TURBO):
return -4.5f;
default:
return -6.0f;
Expand Down
7 changes: 7 additions & 0 deletions src/graphics/niche/InkHUD/Applets/System/Menu/MenuAction.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ enum MenuAction {
SET_REGION_KZ_863,
SET_REGION_NP_865,
SET_REGION_BR_902,
SET_REGION_EU_866,
SET_REGION_NARROW_868,
// Device Roles
SET_ROLE_CLIENT,
SET_ROLE_CLIENT_MUTE,
Expand All @@ -78,6 +80,11 @@ enum MenuAction {
SET_PRESET_SHORT_SLOW,
SET_PRESET_SHORT_FAST,
SET_PRESET_SHORT_TURBO,
SET_PRESET_LITE_SLOW,
SET_PRESET_LITE_FAST,
SET_PRESET_NARROW_SLOW,
SET_PRESET_NARROW_FAST,
SET_PRESET_FROM_REGION, // Dynamic: preset chosen from region-available list
// Timezones
SET_TZ_US_HAWAII,
SET_TZ_US_ALASKA,
Expand Down
61 changes: 45 additions & 16 deletions src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "DisplayFormatters.h"
#include "GPS.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "RTC.h"
#include "Router.h"
Expand Down Expand Up @@ -257,6 +258,11 @@ int32_t InkHUD::MenuApplet::runOnce()
return OSThread::disable();
}

// Storage for the dynamically-built region preset list — populated in showPage(NODE_CONFIG_PRESET)
static constexpr uint8_t MAX_REGION_PRESETS = 16;
static meshtastic_Config_LoRaConfig_ModemPreset regionPresets[MAX_REGION_PRESETS];
static uint8_t regionPresetCount = 0;

static void applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode region)
{
if (config.lora.region == region)
Expand All @@ -276,7 +282,7 @@ static void applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode region)

initRegion();

if (myRegion && myRegion->dutyCycle < 100) {
if (myRegion && getEffectiveDutyCycle() < 100) {
config.lora.ignore_mqtt = true;
}

Expand Down Expand Up @@ -770,6 +776,14 @@ void InkHUD::MenuApplet::execute(MenuItem item)
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_BR_902);
break;

case SET_REGION_EU_866:
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_866);
break;

case SET_REGION_NARROW_868:
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_N_868);
break;

// Roles
case SET_ROLE_CLIENT:
applyDeviceRole(meshtastic_Config_DeviceConfig_Role_CLIENT);
Expand All @@ -789,37 +803,46 @@ void InkHUD::MenuApplet::execute(MenuItem item)

// Presets
case SET_PRESET_LONG_SLOW:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW);
applyLoRaPreset(PRESET(LONG_SLOW));
break;

case SET_PRESET_LONG_MODERATE:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE);
applyLoRaPreset(PRESET(LONG_MODERATE));
break;

case SET_PRESET_LONG_FAST:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST);
applyLoRaPreset(PRESET(LONG_FAST));
break;

case SET_PRESET_MEDIUM_SLOW:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW);
applyLoRaPreset(PRESET(MEDIUM_SLOW));
break;

case SET_PRESET_MEDIUM_FAST:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST);
applyLoRaPreset(PRESET(MEDIUM_FAST));
break;

case SET_PRESET_SHORT_SLOW:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW);
applyLoRaPreset(PRESET(SHORT_SLOW));
break;

case SET_PRESET_SHORT_FAST:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST);
applyLoRaPreset(PRESET(SHORT_FAST));
break;

case SET_PRESET_SHORT_TURBO:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO);
applyLoRaPreset(PRESET(SHORT_TURBO));
break;

case SET_PRESET_FROM_REGION: {
// cursor - 1 because index 0 is "Back"
const uint8_t index = cursor - 1;
if (index < regionPresetCount) {
applyLoRaPreset(regionPresets[index]);
}
break;
}

// Timezones
case SET_TZ_US_HAWAII:
applyTimezone("HST10");
Expand Down Expand Up @@ -1421,6 +1444,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
items.push_back(MenuItem("US", MenuAction::SET_REGION_US, MenuPage::EXIT));
items.push_back(MenuItem("EU 868", MenuAction::SET_REGION_EU_868, MenuPage::EXIT));
items.push_back(MenuItem("EU 433", MenuAction::SET_REGION_EU_433, MenuPage::EXIT));
items.push_back(MenuItem("EU 866", MenuAction::SET_REGION_EU_866, MenuPage::EXIT));
items.push_back(MenuItem("EU 868 Narrow", MenuAction::SET_REGION_NARROW_868, MenuPage::EXIT));
items.push_back(MenuItem("CN", MenuAction::SET_REGION_CN, MenuPage::EXIT));
items.push_back(MenuItem("JP", MenuAction::SET_REGION_JP, MenuPage::EXIT));
items.push_back(MenuItem("ANZ", MenuAction::SET_REGION_ANZ, MenuPage::EXIT));
Expand Down Expand Up @@ -1450,13 +1475,17 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
case NODE_CONFIG_PRESET: {
previousPage = MenuPage::NODE_CONFIG_LORA;
items.push_back(MenuItem("Back", previousPage));
items.push_back(MenuItem("Long Moderate", MenuAction::SET_PRESET_LONG_MODERATE, MenuPage::EXIT));
items.push_back(MenuItem("Long Fast", MenuAction::SET_PRESET_LONG_FAST, MenuPage::EXIT));
items.push_back(MenuItem("Medium Slow", MenuAction::SET_PRESET_MEDIUM_SLOW, MenuPage::EXIT));
items.push_back(MenuItem("Medium Fast", MenuAction::SET_PRESET_MEDIUM_FAST, MenuPage::EXIT));
items.push_back(MenuItem("Short Slow", MenuAction::SET_PRESET_SHORT_SLOW, MenuPage::EXIT));
items.push_back(MenuItem("Short Fast", MenuAction::SET_PRESET_SHORT_FAST, MenuPage::EXIT));
items.push_back(MenuItem("Short Turbo", MenuAction::SET_PRESET_SHORT_TURBO, MenuPage::EXIT));
regionPresetCount = 0;
if (myRegion && myRegion->profile) {
const meshtastic_Config_LoRaConfig_ModemPreset *presets = myRegion->getAvailablePresets();
size_t numPresets = myRegion->getNumPresets();
for (size_t i = 0; i < numPresets && regionPresetCount < MAX_REGION_PRESETS; ++i) {
regionPresets[regionPresetCount++] = presets[i];
const char *name = DisplayFormatters::getModemPresetDisplayName(presets[i], false, true);
nodeConfigLabels.emplace_back(name);
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::SET_PRESET_FROM_REGION, MenuPage::EXIT));
}
}
items.push_back(MenuItem("Exit", MenuPage::EXIT));
break;
}
Expand Down
Loading
Loading