mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-14 15:31:31 +10:00
InkHUD: GPS improvements (#10846)
* GPS improvements * Update * Update MenuApplet.cpp * Remove fixed position * Update MenuApplet.cpp
This commit is contained in:
@@ -26,6 +26,11 @@ enum MenuAction {
|
||||
NEXT_TILE,
|
||||
TOGGLE_BACKLIGHT,
|
||||
TOGGLE_GPS,
|
||||
TOGGLE_SMART_POSITION,
|
||||
SET_POSITION_BROADCAST_INTERVAL,
|
||||
SET_SMART_BROADCAST_INTERVAL,
|
||||
SET_SMART_BROADCAST_DISTANCE,
|
||||
SET_GPS_UPDATE_INTERVAL,
|
||||
ENABLE_BLUETOOTH,
|
||||
TOGGLE_APPLET,
|
||||
TOGGLE_AUTOSHOW_APPLET,
|
||||
|
||||
@@ -36,11 +36,36 @@ struct DisplayTimeoutOption {
|
||||
const char *label;
|
||||
};
|
||||
|
||||
struct UInt32Option {
|
||||
uint32_t value;
|
||||
const char *label;
|
||||
};
|
||||
|
||||
static constexpr DisplayTimeoutOption DISPLAY_TIMEOUT_OPTIONS[] = {
|
||||
{0, "Forever"}, {30, "30 secs"}, {60, "1 min"}, {5 * 60, "5 min"},
|
||||
{15 * 60, "15 min"}, {30 * 60, "30 min"}, {60 * 60, "1 hr"},
|
||||
};
|
||||
|
||||
static constexpr UInt32Option POSITION_BROADCAST_OPTIONS[] = {
|
||||
{60, "1 min"}, {90, "90 sec"}, {5 * 60, "5 min"}, {15 * 60, "15 min"},
|
||||
{60 * 60, "1 hr"}, {2 * 60 * 60, "2 hr"}, {3 * 60 * 60, "3 hr"}, {4 * 60 * 60, "4 hr"},
|
||||
{5 * 60 * 60, "5 hr"}, {6 * 60 * 60, "6 hr"}, {12 * 60 * 60, "12 hr"}, {18 * 60 * 60, "18 hr"},
|
||||
{24 * 60 * 60, "24 hr"}, {36 * 60 * 60, "36 hr"}, {48 * 60 * 60, "48 hr"}, {72 * 60 * 60, "72 hr"},
|
||||
};
|
||||
|
||||
static constexpr UInt32Option GPS_UPDATE_INTERVAL_OPTIONS[] = {
|
||||
{8, "8 sec"}, {20, "20 sec"}, {40, "40 sec"}, {60, "1 min"}, {80, "80 sec"},
|
||||
{2 * 60, "2 min"}, {5 * 60, "5 min"}, {10 * 60, "10 min"}, {15 * 60, "15 min"}, {30 * 60, "30 min"},
|
||||
{60 * 60, "1 hr"}, {6 * 60 * 60, "6 hr"}, {12 * 60 * 60, "12 hr"}, {24 * 60 * 60, "24 hr"}, {2147483647UL, "At Boot"},
|
||||
};
|
||||
|
||||
static constexpr UInt32Option SMART_INTERVAL_OPTIONS[] = {
|
||||
{5 * 60, "5 min"}, {10 * 60, "10 min"}, {15 * 60, "15 min"}, {30 * 60, "30 min"}, {60 * 60, "1 hr"},
|
||||
{2 * 60 * 60, "2 hr"}, {6 * 60 * 60, "6 hr"}, {12 * 60 * 60, "12 hr"}, {24 * 60 * 60, "24 hr"},
|
||||
};
|
||||
|
||||
static constexpr uint32_t SMART_DISTANCE_OPTIONS[] = {20, 50, 100, 250, 500, 1000, 2000, 5000};
|
||||
|
||||
struct PositionPrecisionOption {
|
||||
uint8_t value; // proto value
|
||||
const char *metric;
|
||||
@@ -65,6 +90,30 @@ static const char *getDisplayTimeoutLabel(uint32_t timeoutSeconds)
|
||||
return "Custom";
|
||||
}
|
||||
|
||||
static std::string getUInt32OptionLabel(const UInt32Option *options, uint8_t optionCount, uint32_t value,
|
||||
const char *zeroLabel = nullptr)
|
||||
{
|
||||
for (uint8_t i = 0; i < optionCount; i++) {
|
||||
if (options[i].value == value) {
|
||||
return options[i].label;
|
||||
}
|
||||
}
|
||||
|
||||
if (value == 0) {
|
||||
return zeroLabel ? zeroLabel : "0 sec";
|
||||
}
|
||||
if (value == 2147483647UL) {
|
||||
return "At Boot";
|
||||
}
|
||||
if (value % (60 * 60) == 0) {
|
||||
return std::to_string(value / (60 * 60)) + " hr";
|
||||
}
|
||||
if (value % 60 == 0) {
|
||||
return std::to_string(value / 60) + " min";
|
||||
}
|
||||
return std::to_string(value) + " sec";
|
||||
}
|
||||
|
||||
static bool supportsFreeTextKeyboard(const InkHUD::InkHUD *inkhud, const InkHUD::Persistence::Settings *settings)
|
||||
{
|
||||
return !inkhud->twoWayRocker && (settings->joystick.enabled || inkhud->hasTouchEnabledProvider());
|
||||
@@ -333,6 +382,17 @@ static void applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset preset)
|
||||
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
|
||||
}
|
||||
|
||||
static void applyConfigReload(uint32_t changes = SEGMENT_CONFIG, bool reboot = false)
|
||||
{
|
||||
nodeDB->saveToDisk(changes);
|
||||
service->reloadConfig(changes);
|
||||
|
||||
if (reboot) {
|
||||
InkHUD::InkHUD::getInstance()->notifyApplyingChanges();
|
||||
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *getTimezoneLabelFromValue(const char *tzdef)
|
||||
{
|
||||
if (!tzdef || !*tzdef)
|
||||
@@ -559,6 +619,51 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
||||
#endif
|
||||
break;
|
||||
|
||||
case TOGGLE_SMART_POSITION:
|
||||
config.position.position_broadcast_smart_enabled = !config.position.position_broadcast_smart_enabled;
|
||||
applyConfigReload(SEGMENT_CONFIG, true);
|
||||
break;
|
||||
|
||||
case SET_POSITION_BROADCAST_INTERVAL: {
|
||||
const uint8_t index = cursor - 1;
|
||||
constexpr uint8_t optionCount = sizeof(POSITION_BROADCAST_OPTIONS) / sizeof(POSITION_BROADCAST_OPTIONS[0]);
|
||||
if (index < optionCount && config.position.position_broadcast_secs != POSITION_BROADCAST_OPTIONS[index].value) {
|
||||
config.position.position_broadcast_secs = POSITION_BROADCAST_OPTIONS[index].value;
|
||||
applyConfigReload(SEGMENT_CONFIG, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_SMART_BROADCAST_INTERVAL: {
|
||||
const uint8_t index = cursor - 1;
|
||||
constexpr uint8_t optionCount = sizeof(SMART_INTERVAL_OPTIONS) / sizeof(SMART_INTERVAL_OPTIONS[0]);
|
||||
if (index < optionCount && config.position.broadcast_smart_minimum_interval_secs != SMART_INTERVAL_OPTIONS[index].value) {
|
||||
config.position.broadcast_smart_minimum_interval_secs = SMART_INTERVAL_OPTIONS[index].value;
|
||||
applyConfigReload(SEGMENT_CONFIG, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_SMART_BROADCAST_DISTANCE: {
|
||||
const uint8_t index = cursor - 1;
|
||||
constexpr uint8_t optionCount = sizeof(SMART_DISTANCE_OPTIONS) / sizeof(SMART_DISTANCE_OPTIONS[0]);
|
||||
if (index < optionCount && config.position.broadcast_smart_minimum_distance != SMART_DISTANCE_OPTIONS[index]) {
|
||||
config.position.broadcast_smart_minimum_distance = SMART_DISTANCE_OPTIONS[index];
|
||||
applyConfigReload(SEGMENT_CONFIG, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SET_GPS_UPDATE_INTERVAL: {
|
||||
const uint8_t index = cursor - 1;
|
||||
constexpr uint8_t optionCount = sizeof(GPS_UPDATE_INTERVAL_OPTIONS) / sizeof(GPS_UPDATE_INTERVAL_OPTIONS[0]);
|
||||
if (index < optionCount && config.position.gps_update_interval != GPS_UPDATE_INTERVAL_OPTIONS[index].value) {
|
||||
config.position.gps_update_interval = GPS_UPDATE_INTERVAL_OPTIONS[index].value;
|
||||
applyConfigReload(SEGMENT_CONFIG, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ENABLE_BLUETOOTH:
|
||||
// This helps users recover from a bad wifi config
|
||||
LOG_INFO("Enabling Bluetooth");
|
||||
@@ -1199,6 +1304,9 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
||||
case NODE_CONFIG_POSITION: {
|
||||
previousPage = MenuPage::NODE_CONFIG;
|
||||
items.push_back(MenuItem("Back", previousPage));
|
||||
|
||||
items.push_back(MenuItem::Header("Device GPS"));
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS
|
||||
const auto mode = config.position.gps_mode;
|
||||
if (mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
|
||||
@@ -1206,12 +1314,93 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
||||
} else {
|
||||
gpsEnabled = (mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED);
|
||||
items.push_back(MenuItem("GPS", MenuAction::TOGGLE_GPS, MenuPage::NODE_CONFIG_POSITION, &gpsEnabled));
|
||||
|
||||
nodeConfigLabels.emplace_back(
|
||||
"GPS Poll: " + getUInt32OptionLabel(GPS_UPDATE_INTERVAL_OPTIONS,
|
||||
sizeof(GPS_UPDATE_INTERVAL_OPTIONS) / sizeof(GPS_UPDATE_INTERVAL_OPTIONS[0]),
|
||||
config.position.gps_update_interval, "Default"));
|
||||
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION,
|
||||
MenuPage::NODE_CONFIG_POSITION_GPS_UPDATE_INTERVAL));
|
||||
}
|
||||
#endif
|
||||
|
||||
items.push_back(MenuItem::Header("Position Packet"));
|
||||
|
||||
nodeConfigLabels.emplace_back(
|
||||
"Broadcast: " + getUInt32OptionLabel(POSITION_BROADCAST_OPTIONS,
|
||||
sizeof(POSITION_BROADCAST_OPTIONS) / sizeof(POSITION_BROADCAST_OPTIONS[0]),
|
||||
config.position.position_broadcast_secs));
|
||||
items.push_back(
|
||||
MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_POSITION_BROADCAST_INTERVAL));
|
||||
|
||||
items.push_back(MenuItem("Smart Pos", MenuAction::TOGGLE_SMART_POSITION, MenuPage::NODE_CONFIG_POSITION,
|
||||
&config.position.position_broadcast_smart_enabled));
|
||||
|
||||
if (config.position.position_broadcast_smart_enabled) {
|
||||
nodeConfigLabels.emplace_back("Smart Int: " +
|
||||
getUInt32OptionLabel(SMART_INTERVAL_OPTIONS,
|
||||
sizeof(SMART_INTERVAL_OPTIONS) / sizeof(SMART_INTERVAL_OPTIONS[0]),
|
||||
config.position.broadcast_smart_minimum_interval_secs));
|
||||
items.push_back(
|
||||
MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_POSITION_SMART_INTERVAL));
|
||||
|
||||
nodeConfigLabels.emplace_back("Smart Dist: " + localizeDistance(config.position.broadcast_smart_minimum_distance));
|
||||
items.push_back(
|
||||
MenuItem(nodeConfigLabels.back().c_str(), MenuAction::NO_ACTION, MenuPage::NODE_CONFIG_POSITION_SMART_DISTANCE));
|
||||
}
|
||||
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
}
|
||||
|
||||
case NODE_CONFIG_POSITION_BROADCAST_INTERVAL:
|
||||
previousPage = MenuPage::NODE_CONFIG_POSITION;
|
||||
items.push_back(MenuItem("Back", previousPage));
|
||||
items.push_back(MenuItem::Header("Max time between sends"));
|
||||
for (const auto &option : POSITION_BROADCAST_OPTIONS) {
|
||||
nodeConfigLabels.emplace_back(option.label);
|
||||
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::SET_POSITION_BROADCAST_INTERVAL,
|
||||
MenuPage::NODE_CONFIG_POSITION));
|
||||
}
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
|
||||
case NODE_CONFIG_POSITION_SMART_INTERVAL:
|
||||
previousPage = MenuPage::NODE_CONFIG_POSITION;
|
||||
items.push_back(MenuItem("Back", previousPage));
|
||||
items.push_back(MenuItem::Header("Fastest smart resend"));
|
||||
for (const auto &option : SMART_INTERVAL_OPTIONS) {
|
||||
nodeConfigLabels.emplace_back(option.label);
|
||||
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::SET_SMART_BROADCAST_INTERVAL,
|
||||
MenuPage::NODE_CONFIG_POSITION));
|
||||
}
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
|
||||
case NODE_CONFIG_POSITION_SMART_DISTANCE:
|
||||
previousPage = MenuPage::NODE_CONFIG_POSITION;
|
||||
items.push_back(MenuItem("Back", previousPage));
|
||||
items.push_back(MenuItem::Header("Move this far to send"));
|
||||
for (const auto &option : SMART_DISTANCE_OPTIONS) {
|
||||
nodeConfigLabels.emplace_back(localizeDistance(option));
|
||||
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::SET_SMART_BROADCAST_DISTANCE,
|
||||
MenuPage::NODE_CONFIG_POSITION));
|
||||
}
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
|
||||
case NODE_CONFIG_POSITION_GPS_UPDATE_INTERVAL:
|
||||
previousPage = MenuPage::NODE_CONFIG_POSITION;
|
||||
items.push_back(MenuItem("Back", previousPage));
|
||||
items.push_back(MenuItem::Header("GPS poll cadence"));
|
||||
for (const auto &option : GPS_UPDATE_INTERVAL_OPTIONS) {
|
||||
nodeConfigLabels.emplace_back(option.label);
|
||||
items.push_back(
|
||||
MenuItem(nodeConfigLabels.back().c_str(), MenuAction::SET_GPS_UPDATE_INTERVAL, MenuPage::NODE_CONFIG_POSITION));
|
||||
}
|
||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||
break;
|
||||
|
||||
case NODE_CONFIG_POWER: {
|
||||
previousPage = MenuPage::NODE_CONFIG;
|
||||
items.push_back(MenuItem("Back", previousPage));
|
||||
@@ -2324,26 +2513,29 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t
|
||||
const int16_t divY = top + height;
|
||||
height += fontSmall.lineHeight() * 0.2; // Padding *below* the divider. (Above first menu item)
|
||||
|
||||
// Create a variable number of columns
|
||||
// Either 3 or 4, depending on whether we have GPS
|
||||
// Todo
|
||||
constexpr uint8_t N_COL = 3;
|
||||
int16_t colL[N_COL];
|
||||
int16_t colC[N_COL];
|
||||
int16_t colR[N_COL];
|
||||
for (uint8_t i = 0; i < N_COL; i++) {
|
||||
colL[i] = left + ((width / N_COL) * i);
|
||||
colC[i] = colL[i] + ((width / N_COL) / 2);
|
||||
colR[i] = colL[i] + (width / N_COL);
|
||||
// Create a variable number of columns.
|
||||
#if !MESHTASTIC_EXCLUDE_GPS && HAS_GPS
|
||||
const bool showGpsInfo = config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT;
|
||||
#else
|
||||
const bool showGpsInfo = false;
|
||||
#endif
|
||||
const uint8_t columnCount = showGpsInfo ? 4 : 3;
|
||||
int16_t colL[4] = {};
|
||||
int16_t colC[4] = {};
|
||||
int16_t colR[4] = {};
|
||||
for (uint8_t i = 0; i < columnCount; i++) {
|
||||
colL[i] = left + ((width / columnCount) * i);
|
||||
colC[i] = colL[i] + ((width / columnCount) / 2);
|
||||
colR[i] = colL[i] + (width / columnCount);
|
||||
}
|
||||
|
||||
// Info blocks, left to right
|
||||
|
||||
// Voltage
|
||||
float voltage = powerStatus->getBatteryVoltageMv() / 1000.0;
|
||||
char voltageStr[6]; // "XX.XV"
|
||||
sprintf(voltageStr, "%.2fV", voltage);
|
||||
printAt(colC[0], labelT, "Bat", CENTER, TOP);
|
||||
char voltageStr[8]; // e.g. "4.12"
|
||||
snprintf(voltageStr, sizeof(voltageStr), "%.2f", voltage);
|
||||
printAt(colC[0], labelT, "Bat V", CENTER, TOP);
|
||||
printAt(colC[0], valT, voltageStr, CENTER, TOP);
|
||||
|
||||
// Divider
|
||||
@@ -2351,8 +2543,8 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t
|
||||
drawPixel(colR[0], y, BLACK);
|
||||
|
||||
// Channel Util
|
||||
char chUtilStr[4]; // "XX%"
|
||||
sprintf(chUtilStr, "%2.f%%", airTime->channelUtilizationPercent());
|
||||
char chUtilStr[8]; // e.g. "100%"
|
||||
snprintf(chUtilStr, sizeof(chUtilStr), "%2.f%%", airTime->channelUtilizationPercent());
|
||||
printAt(colC[1], labelT, "Ch", CENTER, TOP);
|
||||
printAt(colC[1], valT, chUtilStr, CENTER, TOP);
|
||||
|
||||
@@ -2361,20 +2553,34 @@ void InkHUD::MenuApplet::drawSystemInfoPanel(int16_t left, int16_t top, uint16_t
|
||||
drawPixel(colR[1], y, BLACK);
|
||||
|
||||
// Duty Cycle (AirTimeTx)
|
||||
char dutyUtilStr[4]; // "XX%"
|
||||
sprintf(dutyUtilStr, "%2.f%%", airTime->utilizationTXPercent());
|
||||
char dutyUtilStr[8]; // e.g. "100%"
|
||||
snprintf(dutyUtilStr, sizeof(dutyUtilStr), "%2.f%%", airTime->utilizationTXPercent());
|
||||
printAt(colC[2], labelT, "Duty", CENTER, TOP);
|
||||
printAt(colC[2], valT, dutyUtilStr, CENTER, TOP);
|
||||
|
||||
/*
|
||||
// Divider
|
||||
for (int16_t y = valT; y <= divY; y += 3)
|
||||
drawPixel(colR[2], y, BLACK);
|
||||
if (showGpsInfo) {
|
||||
// Divider
|
||||
for (int16_t y = valT; y <= divY; y += 3)
|
||||
drawPixel(colR[2], y, BLACK);
|
||||
|
||||
// GPS satellites - todo
|
||||
printAt(colC[3], labelT, "Sats", CENTER, TOP);
|
||||
printAt(colC[3], valT, "ToDo", CENTER, TOP);
|
||||
*/
|
||||
const bool gpsDisabled = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED;
|
||||
const char *gpsLabel = "GPS";
|
||||
printAt(colC[3], labelT, gpsLabel, CENTER, TOP);
|
||||
|
||||
if (gpsDisabled) {
|
||||
const int16_t labelW = getTextWidth(gpsLabel);
|
||||
const int16_t strikeY = labelT + (fontSmall.lineHeight() / 2);
|
||||
drawLine(colC[3] - (labelW / 2), strikeY, colC[3] + ((labelW - 1) / 2), strikeY, BLACK);
|
||||
}
|
||||
|
||||
if (!gpsDisabled && gpsStatus != nullptr && gpsStatus->getIsConnected()) {
|
||||
char satsStr[12];
|
||||
snprintf(satsStr, sizeof(satsStr), "%lu", (unsigned long)gpsStatus->getNumSatellites());
|
||||
printAt(colC[3], valT, satsStr, CENTER, TOP);
|
||||
} else {
|
||||
printAt(colC[3], valT, "--", CENTER, TOP);
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal divider, at bottom of system info panel
|
||||
for (int16_t x = 0; x < width; x += 2) // Divider, centered in the padding between first system panel and first item
|
||||
|
||||
@@ -35,6 +35,10 @@ enum MenuPage : uint8_t {
|
||||
NODE_CONFIG_DISPLAY_TIMEOUT,
|
||||
NODE_CONFIG_BLUETOOTH,
|
||||
NODE_CONFIG_POSITION,
|
||||
NODE_CONFIG_POSITION_BROADCAST_INTERVAL,
|
||||
NODE_CONFIG_POSITION_SMART_INTERVAL,
|
||||
NODE_CONFIG_POSITION_SMART_DISTANCE,
|
||||
NODE_CONFIG_POSITION_GPS_UPDATE_INTERVAL,
|
||||
NODE_CONFIG_ADMIN_RESET,
|
||||
NODE_CONFIG_ADMIN_MESSAGES,
|
||||
TIMEZONE,
|
||||
|
||||
@@ -268,6 +268,13 @@ Tiles are 256×256 pixels, 1-bit (column-major bit packing), compressed per tile
|
||||
|
||||
When the menu is opened from a map applet, zoom controls appear automatically. Zoom In and Zoom Out step through the available tile zoom levels. Reset Zoom returns to the default auto-fit behavior, where the map scales to show all visible nodes.
|
||||
|
||||
##### Position Menu
|
||||
|
||||
InkHUD's `Node Config -> Position` page now exposes the common position controls directly in the menu applet, using the same compact selector pattern as other InkHUD config pages.
|
||||
|
||||
- Device GPS controls: GPS enable/disable, GPS polling interval
|
||||
- Position packet controls: broadcast interval, smart position toggle, smart minimum interval, smart minimum distance
|
||||
|
||||
#### System Applets
|
||||
|
||||
So far, we have been talking about "user applets". We also recognize a separate category of "system applets". These handle things like the menu, and the boot screen. These often need special handling, and need to be implemented manually.
|
||||
|
||||
Reference in New Issue
Block a user