Add Transmit Enabled menu to LoRa frame (#11442)

* Add Transmit Enabled menu to LoRa frame

* Fix outer variable shadows
This commit is contained in:
Jason P
2026-08-12 21:59:26 +00:00
committed by GitHub
parent fdb644e0b7
commit f06f808391
3 changed files with 110 additions and 66 deletions
+71 -63
View File
@@ -224,75 +224,83 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
#if !defined(OLED_TINY)
// === Fifth Row: Channel Utilization ===
const char *chUtil = "ChUtil:";
char chUtilPercentage[10];
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
int chUtil_x = (currentResolution == ScreenResolution::High) ? display->getStringWidth(chUtil) + 10
: display->getStringWidth(chUtil) + 5;
int chUtil_y = getTextPositions(display)[line] + 3;
int chutil_bar_width = (currentResolution == ScreenResolution::High) ? 100 : 50;
int chutil_bar_max_fill = chutil_bar_width - 2; // Account for border
int chutil_bar_height = (currentResolution == ScreenResolution::High) ? 12 : 7;
int extraoffset = (currentResolution == ScreenResolution::High) ? 6 : 3;
int chutil_percent = airTime->channelUtilizationPercent();
const int raw_chutil_percent = chutil_percent;
int centerofscreen = SCREEN_WIDTH / 2;
int total_line_content_width = (chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
int starting_position = centerofscreen - total_line_content_width;
display->drawString(starting_position, getTextPositions(display)[line], chUtil);
// Force 61% or higher to show a full 100% bar, text would still show related percent.
if (chutil_percent >= 61) {
chutil_percent = 100;
}
// Weighting for nonlinear segments
float milestone1 = 25;
float milestone2 = 40;
float weight1 = 0.45; // Weight for 0-25%
float weight2 = 0.35; // Weight for 25-40%
float weight3 = 0.20; // Weight for 40-100%
float totalWeight = weight1 + weight2 + weight3;
int seg1 = chutil_bar_max_fill * (weight1 / totalWeight);
int seg2 = chutil_bar_max_fill * (weight2 / totalWeight);
int seg3 = chutil_bar_max_fill - seg1 - seg2; // Remainder absorbs rounding errors
int fillRight = 0;
if (chutil_percent <= milestone1) {
fillRight = (seg1 * (chutil_percent / milestone1));
} else if (chutil_percent <= milestone2) {
fillRight = seg1 + (seg2 * ((chutil_percent - milestone1) / (milestone2 - milestone1)));
if (!config.lora.tx_enabled) {
const char *txdisabled = "Transmit Disabled";
textWidth = display->getStringWidth(txdisabled);
display->drawString((SCREEN_WIDTH - textWidth) / 2, getTextPositions(display)[line], txdisabled);
} else {
fillRight = seg1 + seg2 + (seg3 * ((chutil_percent - milestone2) / (100 - milestone2)));
}
// Draw outline
display->drawRect(starting_position + chUtil_x, chUtil_y, chutil_bar_width, chutil_bar_height);
const char *chUtil = "ChUtil:";
char chUtilPercentage[10];
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
// Fill progress
if (fillRight > 0) {
#if GRAPHICS_TFT_COLORING_ENABLED
uint16_t UtilizationFillColor = TFTPalette::Good;
if (raw_chutil_percent >= 60) {
UtilizationFillColor = TFTPalette::Bad;
} else if (raw_chutil_percent >= 35) {
UtilizationFillColor = TFTPalette::Medium;
int chUtil_x = (currentResolution == ScreenResolution::High) ? display->getStringWidth(chUtil) + 10
: display->getStringWidth(chUtil) + 5;
int chUtil_y = getTextPositions(display)[line] + 3;
int chutil_bar_width = (currentResolution == ScreenResolution::High) ? 100 : 50;
int chutil_bar_max_fill = chutil_bar_width - 2; // Account for border
int chutil_bar_height = (currentResolution == ScreenResolution::High) ? 12 : 7;
int extraoffset = (currentResolution == ScreenResolution::High) ? 6 : 3;
int chutil_percent = airTime->channelUtilizationPercent();
const int raw_chutil_percent = chutil_percent;
int centerofscreen = SCREEN_WIDTH / 2;
int total_line_content_width =
(chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
int starting_position = centerofscreen - total_line_content_width;
display->drawString(starting_position, getTextPositions(display)[line], chUtil);
// Force 61% or higher to show a full 100% bar, text would still show related percent.
if (chutil_percent >= 61) {
chutil_percent = 100;
}
setAndRegisterTFTColorRole(TFTColorRole::UtilizationFill, UtilizationFillColor, TFTPalette::Black,
starting_position + chUtil_x + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
#endif
display->fillRect(starting_position + chUtil_x + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
}
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
chUtilPercentage);
// Weighting for nonlinear segments
float milestone1 = 25;
float milestone2 = 40;
float weight1 = 0.45; // Weight for 0-25%
float weight2 = 0.35; // Weight for 25-40%
float weight3 = 0.20; // Weight for 40-100%
float totalWeight = weight1 + weight2 + weight3;
int seg1 = chutil_bar_max_fill * (weight1 / totalWeight);
int seg2 = chutil_bar_max_fill * (weight2 / totalWeight);
int seg3 = chutil_bar_max_fill - seg1 - seg2; // Remainder absorbs rounding errors
int fillRight = 0;
if (chutil_percent <= milestone1) {
fillRight = (seg1 * (chutil_percent / milestone1));
} else if (chutil_percent <= milestone2) {
fillRight = seg1 + (seg2 * ((chutil_percent - milestone1) / (milestone2 - milestone1)));
} else {
fillRight = seg1 + seg2 + (seg3 * ((chutil_percent - milestone2) / (100 - milestone2)));
}
// Draw outline
display->drawRect(starting_position + chUtil_x, chUtil_y, chutil_bar_width, chutil_bar_height);
// Fill progress
if (fillRight > 0) {
#if GRAPHICS_TFT_COLORING_ENABLED
uint16_t UtilizationFillColor = TFTPalette::Good;
if (raw_chutil_percent >= 60) {
UtilizationFillColor = TFTPalette::Bad;
} else if (raw_chutil_percent >= 35) {
UtilizationFillColor = TFTPalette::Medium;
}
setAndRegisterTFTColorRole(TFTColorRole::UtilizationFill, UtilizationFillColor, TFTPalette::Black,
starting_position + chUtil_x + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
#endif
display->fillRect(starting_position + chUtil_x + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
}
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
chUtilPercentage);
#endif
}
graphics::drawCommonFooter(display, x, y);
}
+37 -3
View File
@@ -148,27 +148,31 @@ void menuHandler::loraMenu()
"Radio Preset",
"Frequency Slot",
"LoRa Region",
"Transmit Enabled",
#if HAS_LORA_FEM
"FEM LNA",
#endif
};
// NOTE: "FEM LNA" must stay last; it is the only entry that can be hidden at runtime by
// trimming optionsCount, which only works for a trailing option.
enum optionsNumbers {
Back = 0,
DeviceRolePicker = 1,
RadioPresetPicker = 2,
FrequencySlot = 3,
LoraPicker = 4,
TxEnabled = 5,
#if HAS_LORA_FEM
LoraFemLna = 5
LoraFemLna = 6
#endif
};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "LoRa Actions";
bannerOptions.optionsArrayPtr = optionsArray;
#if HAS_LORA_FEM
bannerOptions.optionsCount = loraFEMInterface.isLnaCanControl() ? 6 : 5;
bannerOptions.optionsCount = loraFEMInterface.isLnaCanControl() ? 7 : 6;
#else
bannerOptions.optionsCount = 5;
bannerOptions.optionsCount = 6;
#endif
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
@@ -181,6 +185,8 @@ void menuHandler::loraMenu()
menuHandler::menuQueue = menuHandler::FrequencySlot;
} else if (selected == LoraPicker) {
menuHandler::menuQueue = menuHandler::LoraPicker;
} else if (selected == TxEnabled) {
menuHandler::menuQueue = menuHandler::TXEnabledMenu;
}
#if HAS_LORA_FEM
else if (selected == LoraFemLna) {
@@ -571,6 +577,31 @@ void menuHandler::radioPresetPicker()
screen->showOverlayBanner(buildRegionPresetBanner());
}
void menuHandler::txEnabledMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
enum optionsNumbers { Back = 0, Enabled = 1, Disabled = 2 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Transmit Enabled";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.InitialSelected = config.lora.tx_enabled ? Enabled : Disabled;
bannerOptions.bannerCallback = [](int selected) -> void {
// -1 is the timeout/dismiss case; treat it like Back so we never write config.
if (selected <= Back) {
menuHandler::menuQueue = menuHandler::LoraMenu;
screen->runNow();
return;
}
bool wanted = (selected == Enabled);
if (config.lora.tx_enabled == wanted)
return;
config.lora.tx_enabled = wanted;
service->reloadConfig(SEGMENT_CONFIG);
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::twelveHourPicker()
{
static const char *optionsArray[] = {"Back", "12-hour", "24-hour"};
@@ -2943,6 +2974,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case RadioPresetPicker:
radioPresetPicker();
break;
case TXEnabledMenu:
txEnabledMenu();
break;
case FrequencySlot:
FrequencySlotPicker();
break;
+2
View File
@@ -13,6 +13,7 @@ class menuHandler
LoraPicker,
DeviceRolePicker,
RadioPresetPicker,
TXEnabledMenu,
FrequencySlot,
NoTimeoutLoraPicker,
TzPicker,
@@ -73,6 +74,7 @@ class menuHandler
static void loraMenu();
static void deviceRolePicker();
static void radioPresetPicker();
static void txEnabledMenu();
static void FrequencySlotPicker();
static void handleMenuSwitch(OLEDDisplay *display);
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);