BaseUI: First attempt at Ham Mode implementation (#10663)
* First attempt at Ham Mode implementation * Simplify licensedOnly check * Move related code closer together * TX Disabled if N0CALL, enabled if properly set * Only disable if callsign is N0CALL, don't enable at this stage. * Allow users back to Normal mode if they don't pick an ITU region
This commit is contained in:
@@ -126,6 +126,7 @@ void launchReplyForMessage(const StoredMessage &message, bool freetext)
|
|||||||
|
|
||||||
menuHandler::screenMenus menuHandler::menuQueue = MenuNone;
|
menuHandler::screenMenus menuHandler::menuQueue = MenuNone;
|
||||||
uint32_t menuHandler::pickedNodeNum = 0;
|
uint32_t menuHandler::pickedNodeNum = 0;
|
||||||
|
meshtastic_Config_LoRaConfig_RegionCode menuHandler::pendingRegion = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
|
||||||
bool test_enabled = false;
|
bool test_enabled = false;
|
||||||
uint8_t test_count = 0;
|
uint8_t test_count = 0;
|
||||||
|
|
||||||
@@ -174,6 +175,36 @@ void menuHandler::OnboardMessage()
|
|||||||
screen->showOverlayBanner(bannerOptions);
|
screen->showOverlayBanner(bannerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void applyLoraRegion(meshtastic_Config_LoRaConfig_RegionCode region, bool isHam)
|
||||||
|
{
|
||||||
|
config.lora.region = region;
|
||||||
|
config.lora.channel_num = 0; // Reset to default channel
|
||||||
|
|
||||||
|
if (isHam && adminModule) {
|
||||||
|
meshtastic_HamParameters hamParams = meshtastic_HamParameters_init_zero;
|
||||||
|
strncpy(hamParams.call_sign, "N0CALL", sizeof(hamParams.call_sign) - 1);
|
||||||
|
strncpy(hamParams.short_name, "N0CL", sizeof(hamParams.short_name));
|
||||||
|
hamParams.tx_power = config.lora.tx_power;
|
||||||
|
hamParams.frequency = config.lora.override_frequency;
|
||||||
|
adminModule->handleSetHamMode(hamParams);
|
||||||
|
}
|
||||||
|
auto changes = SEGMENT_CONFIG;
|
||||||
|
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
|
||||||
|
if (crypto) {
|
||||||
|
crypto->ensurePkiKeys(config.security, owner);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
initRegion();
|
||||||
|
if (getEffectiveDutyCycle() < 100) {
|
||||||
|
config.lora.ignore_mqtt = true;
|
||||||
|
}
|
||||||
|
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
|
||||||
|
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
|
||||||
|
changes |= SEGMENT_MODULECONFIG;
|
||||||
|
}
|
||||||
|
service->reloadConfig(changes);
|
||||||
|
}
|
||||||
|
|
||||||
void menuHandler::LoraRegionPicker(uint32_t duration)
|
void menuHandler::LoraRegionPicker(uint32_t duration)
|
||||||
{
|
{
|
||||||
static const LoraRegionOption regionOptions[] = {
|
static const LoraRegionOption regionOptions[] = {
|
||||||
@@ -242,27 +273,20 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
config.lora.region = selectedRegion;
|
bool hamMode = getRegion(selectedRegion)->profile->licensedOnly;
|
||||||
auto changes = SEGMENT_CONFIG;
|
if (hamMode) {
|
||||||
|
LOG_INFO("User chose an amateur radio mode region");
|
||||||
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
|
pendingRegion = selectedRegion;
|
||||||
if (crypto) {
|
menuQueue = HamModeConfirm;
|
||||||
crypto->ensurePkiKeys(config.security, owner);
|
screen->runNow();
|
||||||
|
} else if (owner.is_licensed) {
|
||||||
|
LOG_INFO("Licensed user chose a non-ham region; prompting to revert licensed mode");
|
||||||
|
pendingRegion = selectedRegion;
|
||||||
|
menuQueue = LicensedToNormalConfirm;
|
||||||
|
screen->runNow();
|
||||||
|
} else {
|
||||||
|
applyLoraRegion(selectedRegion, false);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
config.lora.tx_enabled = true;
|
|
||||||
initRegion();
|
|
||||||
if (getEffectiveDutyCycle() < 100) {
|
|
||||||
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
|
|
||||||
// Default broker is in use, so subscribe to the appropriate MQTT root topic for this region
|
|
||||||
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
|
|
||||||
changes |= SEGMENT_MODULECONFIG;
|
|
||||||
}
|
|
||||||
|
|
||||||
service->reloadConfig(changes);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
bannerOptions.durationMs = duration;
|
bannerOptions.durationMs = duration;
|
||||||
@@ -279,6 +303,38 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
|
|||||||
screen->showOverlayBanner(bannerOptions);
|
screen->showOverlayBanner(bannerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void menuHandler::hamModeConfirmMenu()
|
||||||
|
{
|
||||||
|
static const char *confirmOptions[] = {"No", "Yes"};
|
||||||
|
BannerOverlayOptions confirmBanner;
|
||||||
|
confirmBanner.message = "I confirm I am a\nlicensed amateur\nradio operator";
|
||||||
|
confirmBanner.optionsArrayPtr = confirmOptions;
|
||||||
|
confirmBanner.optionsCount = 2;
|
||||||
|
confirmBanner.bannerCallback = [](int selected) {
|
||||||
|
if (selected == 1)
|
||||||
|
applyLoraRegion(pendingRegion, true);
|
||||||
|
};
|
||||||
|
screen->showOverlayBanner(confirmBanner);
|
||||||
|
}
|
||||||
|
|
||||||
|
void menuHandler::licensedToNormalConfirmMenu()
|
||||||
|
{
|
||||||
|
static const char *confirmOptions[] = {"Keep licensed", "Revert to Normal"};
|
||||||
|
BannerOverlayOptions confirmBanner;
|
||||||
|
confirmBanner.message = "Revert licensed\nmode? This will\nre-enable encryption.";
|
||||||
|
confirmBanner.optionsArrayPtr = confirmOptions;
|
||||||
|
confirmBanner.optionsCount = 2;
|
||||||
|
confirmBanner.bannerCallback = [](int selected) {
|
||||||
|
if (selected == 1) {
|
||||||
|
owner.is_licensed = false;
|
||||||
|
config.lora.override_duty_cycle = false;
|
||||||
|
service->reloadOwner(false);
|
||||||
|
}
|
||||||
|
applyLoraRegion(pendingRegion, false);
|
||||||
|
};
|
||||||
|
screen->showOverlayBanner(confirmBanner);
|
||||||
|
}
|
||||||
|
|
||||||
void menuHandler::deviceRolePicker()
|
void menuHandler::deviceRolePicker()
|
||||||
{
|
{
|
||||||
static const char *optionsArray[] = {"Back", "Client", "Client Mute", "Lost and Found", "Tracker"};
|
static const char *optionsArray[] = {"Back", "Client", "Client Mute", "Lost and Found", "Tracker"};
|
||||||
@@ -2822,6 +2878,12 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
|||||||
case ThemeMenu:
|
case ThemeMenu:
|
||||||
themeMenu();
|
themeMenu();
|
||||||
break;
|
break;
|
||||||
|
case HamModeConfirm:
|
||||||
|
hamModeConfirmMenu();
|
||||||
|
break;
|
||||||
|
case LicensedToNormalConfirm:
|
||||||
|
licensedToNormalConfirmMenu();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
menuQueue = MenuNone;
|
menuQueue = MenuNone;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,10 +55,13 @@ class menuHandler
|
|||||||
FrameToggles,
|
FrameToggles,
|
||||||
DisplayUnits,
|
DisplayUnits,
|
||||||
MessageBubblesMenu,
|
MessageBubblesMenu,
|
||||||
ThemeMenu
|
ThemeMenu,
|
||||||
|
HamModeConfirm,
|
||||||
|
LicensedToNormalConfirm
|
||||||
};
|
};
|
||||||
static screenMenus menuQueue;
|
static screenMenus menuQueue;
|
||||||
static uint32_t pickedNodeNum; // node selected by NodePicker for ManageNodeMenu
|
static uint32_t pickedNodeNum; // node selected by NodePicker for ManageNodeMenu
|
||||||
|
static meshtastic_Config_LoRaConfig_RegionCode pendingRegion;
|
||||||
|
|
||||||
static void OnboardMessage();
|
static void OnboardMessage();
|
||||||
static void LoraRegionPicker(uint32_t duration = 30000);
|
static void LoraRegionPicker(uint32_t duration = 30000);
|
||||||
@@ -111,6 +114,8 @@ class menuHandler
|
|||||||
static void messageBubblesMenu();
|
static void messageBubblesMenu();
|
||||||
static void themeMenu();
|
static void themeMenu();
|
||||||
static void textMessageMenu();
|
static void textMessageMenu();
|
||||||
|
static void hamModeConfirmMenu();
|
||||||
|
static void licensedToNormalConfirmMenu();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void saveUIConfig();
|
static void saveUIConfig();
|
||||||
|
|||||||
@@ -1144,11 +1144,16 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
bool origBold = config.display.heading_bold;
|
bool origBold = config.display.heading_bold;
|
||||||
config.display.heading_bold = false;
|
config.display.heading_bold = false;
|
||||||
|
|
||||||
// Display Region and Channel Utilization
|
if (!config.lora.tx_enabled) {
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
const char *txdisabled = "Transmit Disabled";
|
||||||
drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
display->drawString(x, getTextPositions(display)[line], txdisabled);
|
||||||
} else {
|
} else {
|
||||||
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
// Display Region and Channel Utilization
|
||||||
|
if (currentResolution == ScreenResolution::UltraLow) {
|
||||||
|
drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
||||||
|
} else {
|
||||||
|
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
char uptimeStr[32] = "";
|
char uptimeStr[32] = "";
|
||||||
if (currentResolution != ScreenResolution::UltraLow) {
|
if (currentResolution != ScreenResolution::UltraLow) {
|
||||||
|
|||||||
@@ -1463,6 +1463,10 @@ void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p)
|
|||||||
}
|
}
|
||||||
channels.onConfigChanged();
|
channels.onConfigChanged();
|
||||||
|
|
||||||
|
if (strcmp(p.call_sign, "N0CALL") == 0) {
|
||||||
|
config.lora.tx_enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
service->reloadOwner(false);
|
service->reloadOwner(false);
|
||||||
saveChanges(SEGMENT_CONFIG | SEGMENT_NODEDATABASE | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
|
saveChanges(SEGMENT_CONFIG | SEGMENT_NODEDATABASE | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,11 @@ class AdminModule : public ProtobufModule<meshtastic_AdminMessage>, public Obser
|
|||||||
private:
|
private:
|
||||||
bool handleSetModuleConfig(const meshtastic_ModuleConfig &c);
|
bool handleSetModuleConfig(const meshtastic_ModuleConfig &c);
|
||||||
void handleSetChannel();
|
void handleSetChannel();
|
||||||
|
|
||||||
|
public:
|
||||||
void handleSetHamMode(const meshtastic_HamParameters &req);
|
void handleSetHamMode(const meshtastic_HamParameters &req);
|
||||||
|
|
||||||
|
private:
|
||||||
void handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uicfg);
|
void handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uicfg);
|
||||||
void handleSendInputEvent(const meshtastic_AdminMessage_InputEvent &inputEvent);
|
void handleSendInputEvent(const meshtastic_AdminMessage_InputEvent &inputEvent);
|
||||||
void reboot(int32_t seconds);
|
void reboot(int32_t seconds);
|
||||||
|
|||||||
Reference in New Issue
Block a user