Merge branch 'master' into mqtt-fixes

This commit is contained in:
Ben Meadors 2025-07-24 20:40:39 -05:00 committed by GitHub
commit acca34321d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 692 additions and 18 deletions

View File

@ -45,7 +45,7 @@ jobs:
if [[ "$GITHUB_HEAD_REF" == "" ]]; then
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
else
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick)
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr)
fi
echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS"
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT

View File

@ -45,24 +45,28 @@ for pio_env in pio_envs:
all_envs.append(env)
# Filter outputs based on options
# Check is currently mutually exclusive with other options
# Check is mutually exclusive with other options (except 'pr')
if "check" in options:
for env in all_envs:
if env['board_check']:
outlist.append(env['name'])
if "pr" in options:
if env['board_level'] == 'pr':
outlist.append(env['name'])
else:
outlist.append(env['name'])
# Filter (non-check) builds by platform
else:
for env in all_envs:
if options[0] == env['platform']:
# If no board level is specified, always include it
if not env['board_level']:
# Always include board_level = 'pr'
if env['board_level'] == 'pr':
outlist.append(env['name'])
# Include `extra` boards when requested
# Include board_level = 'extra' when requested
elif "extra" in options and env['board_level'] == "extra":
outlist.append(env['name'])
# If no board level is specified, include in release builds (not PR)
elif "pr" not in options and not env['board_level']:
outlist.append(env['name'])
# Return as a JSON list
if ("quick" in options) and (len(outlist) > 3):
print(json.dumps(random.sample(outlist, 3)))
else:
print(json.dumps(outlist))
print(json.dumps(outlist))

View File

@ -14,6 +14,7 @@
#include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h"
#include "modules/KeyVerificationModule.h"
#include "modules/TraceRouteModule.h"
extern uint16_t TFT_MESH;
@ -428,7 +429,7 @@ void menuHandler::systemBaseMenu()
void menuHandler::favoriteBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, Remove, enumEnd };
enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd };
static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
static int optionsEnumArray[enumEnd] = {Back, Preset};
int options = 2;
@ -437,6 +438,8 @@ void menuHandler::favoriteBaseMenu()
optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext;
}
optionsArray[options] = "Trace Route";
optionsEnumArray[options++] = TraceRoute;
optionsArray[options] = "Remove Favorite";
optionsEnumArray[options++] = Remove;
@ -453,6 +456,10 @@ void menuHandler::favoriteBaseMenu()
} else if (selected == Remove) {
menuHandler::menuQueue = menuHandler::remove_favorite;
screen->runNow();
} else if (selected == TraceRoute) {
if (traceRouteModule) {
traceRouteModule->launch(graphics::UIRenderer::currentFavoriteNodeNum);
}
}
};
screen->showOverlayBanner(bannerOptions);
@ -491,12 +498,12 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu()
{
enum optionsNumbers { Back, Favorite, Verify, Reset };
static const char *optionsArray[] = {"Back", "Add Favorite", "Key Verification", "Reset NodeDB"};
enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd };
static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Action";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 4;
bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Favorite) {
menuQueue = add_favorite;
@ -507,6 +514,9 @@ void menuHandler::nodeListMenu()
} else if (selected == Reset) {
menuQueue = reset_node_db_menu;
screen->runNow();
} else if (selected == TraceRoute) {
menuQueue = trace_route_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
@ -859,6 +869,16 @@ void menuHandler::removeFavoriteMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::traceRouteMenu()
{
screen->showNodePicker("Node to Trace", 30000, [](uint32_t nodenum) -> void {
LOG_INFO("Menu: Node picker selected node 0x%08x, traceRouteModule=%p", nodenum, traceRouteModule);
if (traceRouteModule) {
traceRouteModule->startTraceRoute(nodenum);
}
});
}
void menuHandler::testMenu()
{
@ -1131,6 +1151,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case remove_favorite:
removeFavoriteMenu();
break;
case trace_route_menu:
traceRouteMenu();
break;
case test_menu:
testMenu();
break;

View File

@ -36,7 +36,8 @@ class menuHandler
system_base_menu,
key_verification_init,
key_verification_final_prompt,
throttle_message
trace_route_menu,
throttle_message,
};
static screenMenus menuQueue;
@ -64,6 +65,7 @@ class menuHandler
static void shutdownMenu();
static void addFavoriteMenu();
static void removeFavoriteMenu();
static void traceRouteMenu();
static void testMenu();
static void numberTest();
static void wifiBaseMenu();

View File

@ -1,6 +1,13 @@
#include "TraceRouteModule.h"
#include "MeshService.h"
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include "mesh/Router.h"
#include "meshUtils.h"
#include <vector>
extern graphics::Screen *screen;
TraceRouteModule *traceRouteModule;
@ -27,6 +34,123 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti
// Set updated route to the payload of the to be flooded packet
p.decoded.payload.size =
pb_encode_to_bytes(p.decoded.payload.bytes, sizeof(p.decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, r);
if (tracingNode != 0) {
// check isResponseFromTarget
bool isResponseFromTarget = (incoming.request_id != 0 && p.from == tracingNode);
bool isRequestToUs = (incoming.request_id == 0 && p.to == nodeDB->getNodeNum() && tracingNode != 0);
// Check if this is a trace route response containing our target node
bool containsTargetNode = false;
for (uint8_t i = 0; i < r->route_count; i++) {
if (r->route[i] == tracingNode) {
containsTargetNode = true;
break;
}
}
for (uint8_t i = 0; i < r->route_back_count; i++) {
if (r->route_back[i] == tracingNode) {
containsTargetNode = true;
break;
}
}
// Check if this response contains a complete route to our target
bool hasCompleteRoute = (r->route_count > 0 && r->route_back_count > 0) ||
(containsTargetNode && (r->route_count > 0 || r->route_back_count > 0));
LOG_INFO("TracRoute packet analysis: tracingNode=0x%08x, p.from=0x%08x, p.to=0x%08x, request_id=0x%08x", tracingNode,
p.from, p.to, incoming.request_id);
LOG_INFO("TracRoute conditions: isResponseFromTarget=%d, isRequestToUs=%d, containsTargetNode=%d, hasCompleteRoute=%d",
isResponseFromTarget, isRequestToUs, containsTargetNode, hasCompleteRoute);
if (isResponseFromTarget || isRequestToUs || (containsTargetNode && hasCompleteRoute)) {
LOG_INFO("TracRoute result detected: isResponseFromTarget=%d, isRequestToUs=%d", isResponseFromTarget, isRequestToUs);
LOG_INFO("SNR arrays - towards_count=%d, back_count=%d", r->snr_towards_count, r->snr_back_count);
for (int i = 0; i < r->snr_towards_count; i++) {
LOG_INFO("SNR towards[%d] = %d (%.1fdB)", i, r->snr_towards[i], (float)r->snr_towards[i] / 4.0f);
}
for (int i = 0; i < r->snr_back_count; i++) {
LOG_INFO("SNR back[%d] = %d (%.1fdB)", i, r->snr_back[i], (float)r->snr_back[i] / 4.0f);
}
String result = "";
// Show request path (from initiator to target)
if (r->route_count > 0) {
result += getNodeName(nodeDB->getNodeNum());
for (uint8_t i = 0; i < r->route_count; i++) {
result += " > ";
const char *name = getNodeName(r->route[i]);
float snr =
(i < r->snr_towards_count && r->snr_towards[i] != INT8_MIN) ? ((float)r->snr_towards[i] / 4.0f) : 0.0f;
result += name;
if (snr != 0.0f) {
result += "(";
result += String(snr, 1);
result += "dB)";
}
}
result += " > ";
result += getNodeName(tracingNode);
if (r->snr_towards_count > 0 && r->snr_towards[r->snr_towards_count - 1] != INT8_MIN) {
result += "(";
result += String((float)r->snr_towards[r->snr_towards_count - 1] / 4.0f, 1);
result += "dB)";
}
result += "\n";
} else {
// Direct connection (no intermediate hops)
result += getNodeName(nodeDB->getNodeNum());
result += " > ";
result += getNodeName(tracingNode);
if (r->snr_towards_count > 0 && r->snr_towards[0] != INT8_MIN) {
result += "(";
result += String((float)r->snr_towards[0] / 4.0f, 1);
result += "dB)";
}
result += "\n";
}
// Show response path (from target back to initiator)
if (r->route_back_count > 0) {
result += getNodeName(tracingNode);
for (int8_t i = r->route_back_count - 1; i >= 0; i--) {
result += " > ";
const char *name = getNodeName(r->route_back[i]);
float snr = (i < r->snr_back_count && r->snr_back[i] != INT8_MIN) ? ((float)r->snr_back[i] / 4.0f) : 0.0f;
result += name;
if (snr != 0.0f) {
result += "(";
result += String(snr, 1);
result += "dB)";
}
}
// add initiator node
result += " > ";
result += getNodeName(nodeDB->getNodeNum());
if (r->snr_back_count > 0 && r->snr_back[r->snr_back_count - 1] != INT8_MIN) {
result += "(";
result += String((float)r->snr_back[r->snr_back_count - 1] / 4.0f, 1);
result += "dB)";
}
} else {
// Direct return path (no intermediate hops)
result += getNodeName(tracingNode);
result += " > ";
result += getNodeName(nodeDB->getNodeNum());
if (r->snr_back_count > 0 && r->snr_back[0] != INT8_MIN) {
result += "(";
result += String((float)r->snr_back[0] / 4.0f, 1);
result += "dB)";
}
}
LOG_INFO("Trace route result: %s", result.c_str());
handleTraceRouteResult(result);
}
}
}
void TraceRouteModule::insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination)
@ -173,8 +297,467 @@ meshtastic_MeshPacket *TraceRouteModule::allocReply()
}
TraceRouteModule::TraceRouteModule()
: ProtobufModule("traceroute", meshtastic_PortNum_TRACEROUTE_APP, &meshtastic_RouteDiscovery_msg)
: ProtobufModule("traceroute", meshtastic_PortNum_TRACEROUTE_APP, &meshtastic_RouteDiscovery_msg), OSThread("TraceRoute")
{
ourPortNum = meshtastic_PortNum_TRACEROUTE_APP;
isPromiscuous = true; // We need to update the route even if it is not destined to us
}
const char *TraceRouteModule::getNodeName(NodeNum node)
{
meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(node);
if (info && info->has_user) {
if (strlen(info->user.short_name) > 0) {
return info->user.short_name;
}
if (strlen(info->user.long_name) > 0) {
return info->user.long_name;
}
}
static char fallback[12];
snprintf(fallback, sizeof(fallback), "0x%08x", node);
return fallback;
}
bool TraceRouteModule::startTraceRoute(NodeNum node)
{
LOG_INFO("=== TraceRoute startTraceRoute CALLED: node=0x%08x ===", node);
unsigned long now = millis();
if (node == 0 || node == NODENUM_BROADCAST) {
LOG_ERROR("Invalid node number for trace route: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT;
resultText = "Invalid node";
resultShowTime = millis();
tracingNode = 0;
requestFocus();
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
return false;
}
if (node == nodeDB->getNodeNum()) {
LOG_ERROR("Cannot trace route to self: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT;
resultText = "Cannot trace self";
resultShowTime = millis();
tracingNode = 0;
requestFocus();
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
return false;
}
if (!initialized) {
lastTraceRouteTime = 0;
initialized = true;
LOG_INFO("TraceRoute initialized for first time");
}
if (runState == TRACEROUTE_STATE_TRACKING) {
LOG_INFO("TraceRoute already in progress");
return false;
}
if (initialized && lastTraceRouteTime > 0 && now - lastTraceRouteTime < cooldownMs) {
// Cooldown
unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000;
bannerText = String("Wait for ") + String(wait) + String("s");
runState = TRACEROUTE_STATE_COOLDOWN;
requestFocus();
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
LOG_INFO("Cooldown active, please wait %lu seconds before starting a new trace route.", wait);
return false;
}
tracingNode = node;
lastTraceRouteTime = now;
runState = TRACEROUTE_STATE_TRACKING;
bannerText = String("Tracing ") + getNodeName(node);
LOG_INFO("TraceRoute UI: Starting trace route to node 0x%08x, requesting focus", node);
// 请求焦点然后触发UI更新事件
requestFocus();
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
// 设置定时器来处理超时检查
setIntervalFromNow(1000); // 每秒检查一次状态
meshtastic_RouteDiscovery req = meshtastic_RouteDiscovery_init_zero;
LOG_INFO("Creating RouteDiscovery protobuf...");
// Allocate a packet directly from router like the reference code
meshtastic_MeshPacket *p = router->allocForSending();
if (p) {
// Set destination and port
p->to = node;
p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP;
p->decoded.want_response = true;
// Manually encode the RouteDiscovery payload
p->decoded.payload.size =
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req);
LOG_INFO("Packet allocated successfully: to=0x%08x, portnum=%d, want_response=%d, payload_size=%d", p->to,
p->decoded.portnum, p->decoded.want_response, p->decoded.payload.size);
LOG_INFO("About to call service->sendToMesh...");
if (service) {
LOG_INFO("MeshService is available, sending packet...");
service->sendToMesh(p, RX_SRC_USER);
LOG_INFO("sendToMesh called successfully for trace route to node 0x%08x", node);
} else {
LOG_ERROR("MeshService is NULL!");
runState = TRACEROUTE_STATE_RESULT;
resultText = "Service unavailable";
resultShowTime = millis();
tracingNode = 0;
requestFocus();
UIFrameEvent e2;
e2.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e2);
return false;
}
} else {
LOG_ERROR("Failed to allocate TraceRoute packet from router");
runState = TRACEROUTE_STATE_RESULT;
resultText = "Failed to send";
resultShowTime = millis();
tracingNode = 0;
requestFocus();
UIFrameEvent e2;
e2.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e2);
return false;
}
return true;
}
void TraceRouteModule::launch(NodeNum node)
{
if (node == 0 || node == NODENUM_BROADCAST) {
LOG_ERROR("Invalid node number for trace route: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT;
resultText = "Invalid node";
resultShowTime = millis();
tracingNode = 0;
requestFocus();
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
return;
}
if (node == nodeDB->getNodeNum()) {
LOG_ERROR("Cannot trace route to self: 0x%08x", node);
runState = TRACEROUTE_STATE_RESULT;
resultText = "Cannot trace self";
resultShowTime = millis();
tracingNode = 0;
requestFocus();
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
return;
}
if (!initialized) {
lastTraceRouteTime = 0;
initialized = true;
LOG_INFO("TraceRoute initialized for first time");
}
unsigned long now = millis();
if (initialized && lastTraceRouteTime > 0 && now - lastTraceRouteTime < cooldownMs) {
unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000;
bannerText = String("Wait for ") + String(wait) + String("s");
runState = TRACEROUTE_STATE_COOLDOWN;
requestFocus();
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
LOG_INFO("Cooldown active, please wait %lu seconds before starting a new trace route.", wait);
return;
}
runState = TRACEROUTE_STATE_TRACKING;
tracingNode = node;
lastTraceRouteTime = now;
bannerText = String("Tracing ") + getNodeName(node);
requestFocus();
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
setIntervalFromNow(1000);
meshtastic_RouteDiscovery req = meshtastic_RouteDiscovery_init_zero;
LOG_INFO("Creating RouteDiscovery protobuf...");
meshtastic_MeshPacket *p = router->allocForSending();
if (p) {
p->to = node;
p->decoded.portnum = meshtastic_PortNum_TRACEROUTE_APP;
p->decoded.want_response = true;
p->decoded.payload.size =
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_RouteDiscovery_msg, &req);
LOG_INFO("Packet allocated successfully: to=0x%08x, portnum=%d, want_response=%d, payload_size=%d", p->to,
p->decoded.portnum, p->decoded.want_response, p->decoded.payload.size);
if (service) {
service->sendToMesh(p, RX_SRC_USER);
LOG_INFO("sendToMesh called successfully for trace route to node 0x%08x", node);
} else {
LOG_ERROR("MeshService is NULL!");
runState = TRACEROUTE_STATE_RESULT;
resultText = "Service unavailable";
resultShowTime = millis();
tracingNode = 0;
}
} else {
LOG_ERROR("Failed to allocate TraceRoute packet from router");
runState = TRACEROUTE_STATE_RESULT;
resultText = "Failed to send";
resultShowTime = millis();
tracingNode = 0;
}
}
void TraceRouteModule::handleTraceRouteResult(const String &result)
{
resultText = result;
runState = TRACEROUTE_STATE_RESULT;
resultShowTime = millis();
tracingNode = 0;
LOG_INFO("TraceRoute result ready, requesting focus. Result: %s", result.c_str());
setIntervalFromNow(1000);
requestFocus();
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
LOG_INFO("=== TraceRoute handleTraceRouteResult END ===");
}
bool TraceRouteModule::shouldDraw()
{
bool draw = (runState != TRACEROUTE_STATE_IDLE);
static TraceRouteRunState lastLoggedState = TRACEROUTE_STATE_IDLE;
if (runState != lastLoggedState) {
LOG_INFO("TraceRoute shouldDraw: runState=%d, draw=%d", runState, draw);
lastLoggedState = runState;
}
return draw;
}
#if HAS_SCREEN
void TraceRouteModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
LOG_DEBUG("TraceRoute drawFrame called: runState=%d", runState);
display->setTextAlignment(TEXT_ALIGN_CENTER);
if (runState == TRACEROUTE_STATE_TRACKING) {
display->setFont(FONT_MEDIUM);
int centerY = y + (display->getHeight() / 2) - (FONT_HEIGHT_MEDIUM / 2);
display->drawString(display->getWidth() / 2 + x, centerY, bannerText);
} else if (runState == TRACEROUTE_STATE_RESULT) {
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(x, y, "Route Result");
int contentStartY = y + FONT_HEIGHT_MEDIUM + 2; // Add more spacing after title
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
if (resultText.length() > 0) {
std::vector<String> lines;
String currentLine = "";
int maxWidth = display->getWidth() - 4;
int start = 0;
int newlinePos = resultText.indexOf('\n', start);
while (newlinePos != -1 || start < resultText.length()) {
String segment;
if (newlinePos != -1) {
segment = resultText.substring(start, newlinePos);
start = newlinePos + 1;
newlinePos = resultText.indexOf('\n', start);
} else {
segment = resultText.substring(start);
start = resultText.length();
}
if (display->getStringWidth(segment) <= maxWidth) {
lines.push_back(segment);
} else {
// Try to break at better positions (space, >, <, -)
String remaining = segment;
while (remaining.length() > 0) {
String tempLine = "";
int lastGoodBreak = -1;
bool lineComplete = false;
for (int i = 0; i < remaining.length(); i++) {
char ch = remaining.charAt(i);
String testLine = tempLine + ch;
if (display->getStringWidth(testLine) > maxWidth) {
if (lastGoodBreak >= 0) {
// Break at the last good position
lines.push_back(remaining.substring(0, lastGoodBreak + 1));
remaining = remaining.substring(lastGoodBreak + 1);
lineComplete = true;
break;
} else if (tempLine.length() > 0) {
lines.push_back(tempLine);
remaining = remaining.substring(i);
lineComplete = true;
break;
} else {
// Single character exceeds width
lines.push_back(String(ch));
remaining = remaining.substring(i + 1);
lineComplete = true;
break;
}
} else {
tempLine = testLine;
// Mark good break positions
if (ch == ' ' || ch == '>' || ch == '<' || ch == '-' || ch == '(' || ch == ')') {
lastGoodBreak = i;
}
}
}
if (!lineComplete) {
// Reached end of remaining text
if (tempLine.length() > 0) {
lines.push_back(tempLine);
}
break;
}
}
}
}
int lineHeight = FONT_HEIGHT_SMALL + 1; // Use proper font height with 1px spacing
for (size_t i = 0; i < lines.size(); i++) {
int lineY = contentStartY + (i * lineHeight);
if (lineY + FONT_HEIGHT_SMALL <= display->getHeight()) {
display->drawString(x + 2, lineY, lines[i]);
}
}
}
} else if (runState == TRACEROUTE_STATE_COOLDOWN) {
display->setFont(FONT_MEDIUM);
int centerY = y + (display->getHeight() / 2) - (FONT_HEIGHT_MEDIUM / 2);
display->drawString(display->getWidth() / 2 + x, centerY, bannerText);
}
}
#endif // HAS_SCREEN
int32_t TraceRouteModule::runOnce()
{
unsigned long now = millis();
if (runState == TRACEROUTE_STATE_IDLE) {
return INT32_MAX;
}
// Check for tracking timeout
if (runState == TRACEROUTE_STATE_TRACKING && now - lastTraceRouteTime > trackingTimeoutMs) {
LOG_INFO("TraceRoute timeout, no response received");
runState = TRACEROUTE_STATE_RESULT;
resultText = "No response received";
resultShowTime = now;
tracingNode = 0;
requestFocus();
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
setIntervalFromNow(resultDisplayMs);
return resultDisplayMs;
}
// Update cooldown display every second
if (runState == TRACEROUTE_STATE_COOLDOWN) {
unsigned long wait = (cooldownMs - (now - lastTraceRouteTime)) / 1000;
if (wait > 0) {
String newBannerText = String("Wait for ") + String(wait) + String("s");
bannerText = newBannerText;
LOG_INFO("TraceRoute cooldown: updating banner to %s", bannerText.c_str());
// Force flash UI
requestFocus();
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
if (screen) {
screen->forceDisplay();
}
return 1000;
} else {
// Cooldown finished
LOG_INFO("TraceRoute cooldown finished, returning to IDLE");
runState = TRACEROUTE_STATE_IDLE;
bannerText = "";
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
return INT32_MAX;
}
}
if (runState == TRACEROUTE_STATE_RESULT) {
if (now - resultShowTime >= resultDisplayMs) {
LOG_INFO("TraceRoute result display timeout, returning to IDLE");
runState = TRACEROUTE_STATE_IDLE;
resultText = "";
bannerText = "";
tracingNode = 0;
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
notifyObservers(&e);
return INT32_MAX;
} else {
return 1000;
}
}
if (runState == TRACEROUTE_STATE_TRACKING) {
return 1000;
}
return INT32_MAX;
}

View File

@ -1,16 +1,40 @@
#pragma once
#include "ProtobufModule.h"
#include "concurrency/OSThread.h"
#include "graphics/Screen.h"
#include "graphics/SharedUIDisplay.h"
#include "input/InputBroker.h"
#if HAS_SCREEN
#include "OLEDDisplayUi.h"
#endif
#define ROUTE_SIZE sizeof(((meshtastic_RouteDiscovery *)0)->route) / sizeof(((meshtastic_RouteDiscovery *)0)->route[0])
/**
* A module that traces the route to a certain destination node
*/
class TraceRouteModule : public ProtobufModule<meshtastic_RouteDiscovery>
enum TraceRouteRunState { TRACEROUTE_STATE_IDLE, TRACEROUTE_STATE_TRACKING, TRACEROUTE_STATE_RESULT, TRACEROUTE_STATE_COOLDOWN };
class TraceRouteModule : public ProtobufModule<meshtastic_RouteDiscovery>,
public Observable<const UIFrameEvent *>,
private concurrency::OSThread
{
public:
TraceRouteModule();
bool startTraceRoute(NodeNum node);
void launch(NodeNum node);
void handleTraceRouteResult(const String &result);
bool shouldDraw();
#if HAS_SCREEN
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
#endif
const char *getNodeName(NodeNum node);
virtual bool wantUIFrame() override { return shouldDraw(); }
virtual Observable<const UIFrameEvent *> *getUIFrameObservable() override { return this; }
protected:
bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_RouteDiscovery *r) override;
@ -20,6 +44,8 @@ class TraceRouteModule : public ProtobufModule<meshtastic_RouteDiscovery>
the route array containing the IDs of nodes this packet went through */
void alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) override;
virtual int32_t runOnce() override;
private:
// Call to add unknown hops (e.g. when a node couldn't decrypt it) to the route based on hopStart and current hopLimit
void insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r, bool isTowardsDestination);
@ -31,6 +57,17 @@ class TraceRouteModule : public ProtobufModule<meshtastic_RouteDiscovery>
Set origin to where the request came from.
Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */
void printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest, bool isTowardsDestination);
TraceRouteRunState runState = TRACEROUTE_STATE_IDLE;
unsigned long lastTraceRouteTime = 0;
unsigned long resultShowTime = 0;
unsigned long cooldownMs = 30000;
unsigned long resultDisplayMs = 10000;
unsigned long trackingTimeoutMs = 10000;
String bannerText;
String resultText;
NodeNum tracingNode = 0;
bool initialized = false;
};
extern TraceRouteModule *traceRouteModule;

View File

@ -1,6 +1,7 @@
[env:rak11200]
extends = esp32_base
board = wiscore_rak11200
board_level = pr
board_check = true
build_flags =
${esp32_base.build_flags}

View File

@ -2,6 +2,7 @@
[env:tbeam]
extends = esp32_base
board = ttgo-t-beam
board_level = pr
board_check = true
lib_deps =
${esp32_base.lib_deps}

View File

@ -1,6 +1,7 @@
[env:heltec-ht62-esp32c3-sx1262]
extends = esp32c3_base
board = esp32-c3-devkitm-1
board_level = pr
build_flags =
${esp32_base.build_flags}
-D HELTEC_HT62

View File

@ -1,6 +1,7 @@
[env:tlora-c6]
extends = esp32c6_base
board = esp32-c6-devkitm-1
board_level = pr
build_flags =
${esp32c6_base.build_flags}
-D TLORA_C6

View File

@ -98,6 +98,7 @@ build_flags =
[env:elecrow-adv-35-tft]
extends = crowpanel_small_esp32s3_base
board_level = pr
build_flags =
${crowpanel_small_esp32s3_base.build_flags}
-D LV_CACHE_DEF_SIZE=2097152

View File

@ -1,6 +1,7 @@
[env:heltec-v3]
extends = esp32s3_base
board = heltec_wifi_lora_32_V3
board_level = pr
board_check = true
board_build.partitions = default_8MB.csv
build_flags =

View File

@ -24,6 +24,7 @@ upload_speed = 115200
[env:heltec-vision-master-e213-inkhud]
extends = esp32s3_base, inkhud
board = heltec_vision_master_e213
board_level = pr
board_build.partitions = default_8MB.csv
build_src_filter =
${esp32_base.build_src_filter}

View File

@ -1,6 +1,7 @@
[env:rak3312]
extends = esp32s3_base
board = wiscore_rak3312
board_level = pr
board_check = true
upload_protocol = esptool

View File

@ -31,7 +31,7 @@ lib_deps = ${esp32s3_base.lib_deps}
[env:seeed-sensecap-indicator-tft]
extends = env:seeed-sensecap-indicator
board_level = main
board_level = pr
upload_speed = 460800
build_flags =

View File

@ -1,6 +1,7 @@
[env:seeed-xiao-s3]
extends = esp32s3_base
board = seeed-xiao-s3
board_level = pr
board_check = true
board_build.partitions = default_8MB.csv
upload_protocol = esptool

View File

@ -1,6 +1,7 @@
[env:station-g2]
extends = esp32s3_base
board = station-g2
board_level = pr
board_check = true
board_build.partitions = default_16MB.csv
board_build.mcu = esp32s3

View File

@ -19,6 +19,7 @@ lib_deps = ${esp32s3_base.lib_deps}
[env:t-deck-tft]
extends = env:t-deck
board_level = pr
build_flags =
${env:t-deck.build_flags}

View File

@ -1,6 +1,7 @@
[env:t-eth-elite]
extends = esp32s3_base
board = esp32s3box
board_level = pr
board_check = true
board_build.partitions = default_16MB.csv
build_flags =

View File

@ -2,6 +2,7 @@
[env:heltec-mesh-node-t114]
extends = nrf52840_base
board = heltec_mesh_node_t114
board_level = pr
debug_tool = jlink
# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling.

View File

@ -2,6 +2,7 @@
[env:rak4631]
extends = nrf52840_base
board = wiscore_rak4631
board_level = pr
board_check = true
build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/rak4631

View File

@ -2,6 +2,7 @@
[env:seeed_xiao_nrf52840_kit]
extends = nrf52840_base
board = xiao_ble_sense
board_level = pr
build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/seeed_xiao_nrf52840_kit
-Isrc/platform/nrf52/softdevice

View File

@ -2,6 +2,7 @@
[env:t-echo]
extends = nrf52840_base
board = t-echo
board_level = pr
board_check = true
debug_tool = jlink
@ -27,6 +28,7 @@ lib_deps =
[env:t-echo-inkhud]
extends = nrf52840_base, inkhud
board = t-echo
board_level = pr
board_check = true
debug_tool = jlink
build_flags =

View File

@ -1,6 +1,7 @@
[env:tracker-t1000-e]
extends = nrf52840_base
board = tracker-t1000-e
board_level = pr
build_flags = ${nrf52840_base.build_flags}
-Ivariants/nrf52840/tracker-t1000-e
-Isrc/platform/nrf52/softdevice

View File

@ -1,6 +1,7 @@
[env:rak11310]
extends = rp2040_base
board = rakwireless_rak11300
board_level = pr
upload_protocol = picotool
# add our variants files to the include and src paths
build_flags =

View File

@ -1,6 +1,7 @@
[env:pico]
extends = rp2040_base
board = rpipico
board_level = pr
upload_protocol = picotool
# add our variants files to the include and src paths

View File

@ -1,6 +1,7 @@
[env:picow]
extends = rp2040_base
board = rpipicow
board_level = pr
upload_protocol = picotool
# add our variants files to the include and src paths
build_flags =

View File

@ -1,6 +1,7 @@
[env:pico2]
extends = rp2350_base
board = rpipico2
board_level = pr
upload_protocol = picotool
# add our variants files to the include and src paths

View File

@ -1,6 +1,7 @@
[env:pico2w]
extends = rp2350_base
board = rpipico2w
board_level = pr
upload_protocol = jlink
# debug settings for external openocd with RP2040 support (custom build)
debug_tool = custom

View File

@ -1,6 +1,7 @@
[env:rak3172]
extends = stm32_base
board = wiscore_rak3172
board_level = pr
board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem
build_flags =
${stm32_base.build_flags}

View File

@ -1,6 +1,7 @@
[env:wio-e5]
extends = stm32_base
board = lora_e5_dev_board
board_level = pr
board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem
build_flags =
${stm32_base.build_flags}