mirror of
https://github.com/meshtastic/firmware.git
synced 2025-10-28 07:13:25 +00:00
Merge branch 'develop' into work
This commit is contained in:
commit
c57b4abc5d
@ -8,15 +8,15 @@ plugins:
|
|||||||
uri: https://github.com/trunk-io/plugins
|
uri: https://github.com/trunk-io/plugins
|
||||||
lint:
|
lint:
|
||||||
enabled:
|
enabled:
|
||||||
- checkov@3.2.483
|
- checkov@3.2.486
|
||||||
- renovate@41.148.2
|
- renovate@41.157.0
|
||||||
- prettier@3.6.2
|
- prettier@3.6.2
|
||||||
- trufflehog@3.90.8
|
- trufflehog@3.90.11
|
||||||
- yamllint@1.37.1
|
- yamllint@1.37.1
|
||||||
- bandit@1.8.6
|
- bandit@1.8.6
|
||||||
- trivy@0.67.2
|
- trivy@0.67.2
|
||||||
- taplo@0.10.0
|
- taplo@0.10.0
|
||||||
- ruff@0.14.0
|
- ruff@0.14.1
|
||||||
- isort@7.0.0
|
- isort@7.0.0
|
||||||
- markdownlint@0.45.0
|
- markdownlint@0.45.0
|
||||||
- oxipng@9.1.5
|
- oxipng@9.1.5
|
||||||
|
|||||||
@ -563,6 +563,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
int line = 1;
|
int line = 1;
|
||||||
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
|
|
||||||
// === Header ===
|
// === Header ===
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
@ -740,7 +741,6 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
int yOffset = (isHighResolution) ? 0 : 5;
|
int yOffset = (isHighResolution) ? 0 : 5;
|
||||||
std::string longNameStr;
|
std::string longNameStr;
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
|
||||||
if (ourNode && ourNode->has_user && strlen(ourNode->user.long_name) > 0) {
|
if (ourNode && ourNode->has_user && strlen(ourNode->user.long_name) > 0) {
|
||||||
longNameStr = sanitizeString(ourNode->user.long_name);
|
longNameStr = sanitizeString(ourNode->user.long_name);
|
||||||
}
|
}
|
||||||
@ -1000,24 +1000,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
const char *displayLine = ""; // Initialize to empty string by default
|
const char *displayLine = ""; // Initialize to empty string by default
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
|
|
||||||
bool usePhoneGPS = (ourNode && nodeDB->hasValidPosition(ourNode) &&
|
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||||
config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED);
|
|
||||||
|
|
||||||
if (usePhoneGPS) {
|
|
||||||
// Phone-provided GPS is active
|
|
||||||
displayLine = "Phone GPS";
|
|
||||||
int yOffset = (isHighResolution) ? 3 : 1;
|
|
||||||
if (isHighResolution) {
|
|
||||||
NodeListRenderer::drawScaledXBitmap16x16(x, getTextPositions(display)[line] + yOffset - 5, imgSatellite_width,
|
|
||||||
imgSatellite_height, imgSatellite, display);
|
|
||||||
} else {
|
|
||||||
display->drawXbm(x + 1, getTextPositions(display)[line] + yOffset, imgSatellite_width, imgSatellite_height,
|
|
||||||
imgSatellite);
|
|
||||||
}
|
|
||||||
int xOffset = (isHighResolution) ? 6 : 0;
|
|
||||||
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
|
|
||||||
} else if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
|
||||||
// GPS disabled / not present
|
|
||||||
if (config.position.fixed_position) {
|
if (config.position.fixed_position) {
|
||||||
displayLine = "Fixed GPS";
|
displayLine = "Fixed GPS";
|
||||||
} else {
|
} else {
|
||||||
@ -1108,9 +1091,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
|
|
||||||
// === Final Row: Altitude ===
|
// === Final Row: Altitude ===
|
||||||
char altitudeLine[32] = {0};
|
char altitudeLine[32] = {0};
|
||||||
int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode))
|
int32_t alt = geoCoord.getAltitude();
|
||||||
? ourNode->position.altitude
|
|
||||||
: geoCoord.getAltitude();
|
|
||||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||||
snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0fft", alt * METERS_TO_FEET);
|
snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0fft", alt * METERS_TO_FEET);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -13,45 +13,147 @@ void InkHUD::MapApplet::onRender()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: draw rounded rectangle centered at x,y
|
||||||
|
auto fillRoundedRect = [&](int16_t cx, int16_t cy, int16_t w, int16_t h, int16_t r, uint16_t color) {
|
||||||
|
int16_t x = cx - (w / 2);
|
||||||
|
int16_t y = cy - (h / 2);
|
||||||
|
|
||||||
|
// center rects
|
||||||
|
fillRect(x + r, y, w - 2 * r, h, color);
|
||||||
|
fillRect(x, y + r, r, h - 2 * r, color);
|
||||||
|
fillRect(x + w - r, y + r, r, h - 2 * r, color);
|
||||||
|
|
||||||
|
// corners
|
||||||
|
fillCircle(x + r, y + r, r, color);
|
||||||
|
fillCircle(x + w - r - 1, y + r, r, color);
|
||||||
|
fillCircle(x + r, y + h - r - 1, r, color);
|
||||||
|
fillCircle(x + w - r - 1, y + h - r - 1, r, color);
|
||||||
|
};
|
||||||
|
|
||||||
// Find center of map
|
// Find center of map
|
||||||
// - latitude and longitude
|
|
||||||
// - will be placed at X(0.5), Y(0.5)
|
|
||||||
getMapCenter(&latCenter, &lngCenter);
|
getMapCenter(&latCenter, &lngCenter);
|
||||||
|
|
||||||
// Calculate North+East distance of each node to map center
|
|
||||||
// - which nodes to use controlled by virtual shouldDrawNode method
|
|
||||||
calculateAllMarkers();
|
calculateAllMarkers();
|
||||||
|
|
||||||
// Set the region shown on the map
|
|
||||||
// - default: fit all nodes, plus padding
|
|
||||||
// - maybe overriden by derived applet
|
|
||||||
// - getMapSize *sets* passed parameters (C-style)
|
|
||||||
getMapSize(&widthMeters, &heightMeters);
|
getMapSize(&widthMeters, &heightMeters);
|
||||||
|
|
||||||
// Set the metersToPx conversion value
|
|
||||||
calculateMapScale();
|
calculateMapScale();
|
||||||
|
|
||||||
// Special marker for own node
|
// Draw all markers first
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
|
||||||
if (ourNode && nodeDB->hasValidPosition(ourNode))
|
|
||||||
drawLabeledMarker(ourNode);
|
|
||||||
|
|
||||||
// Draw all markers
|
|
||||||
for (Marker m : markers) {
|
for (Marker m : markers) {
|
||||||
int16_t x = X(0.5) + (m.eastMeters * metersToPx);
|
int16_t x = X(0.5) + (m.eastMeters * metersToPx);
|
||||||
int16_t y = Y(0.5) - (m.northMeters * metersToPx);
|
int16_t y = Y(0.5) - (m.northMeters * metersToPx);
|
||||||
|
|
||||||
// Cross Size
|
// Add white halo outline first
|
||||||
constexpr uint16_t csMin = 5;
|
constexpr int outlinePad = 1;
|
||||||
constexpr uint16_t csMax = 12;
|
int boxSize = 11;
|
||||||
|
int radius = 2; // rounded corner radius
|
||||||
|
|
||||||
// Too many hops away
|
// White halo background
|
||||||
if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) // Too many mops
|
fillRoundedRect(x, y, boxSize + (outlinePad * 2), boxSize + (outlinePad * 2), radius + 1, WHITE);
|
||||||
printAt(x, y, "!", CENTER, MIDDLE);
|
|
||||||
else if (!m.hasHopsAway) // Unknown hops
|
// Draw inner box
|
||||||
drawCross(x, y, csMin);
|
fillRoundedRect(x, y, boxSize, boxSize, radius, BLACK);
|
||||||
else // The fewer hops, the larger the cross
|
|
||||||
drawCross(x, y, map(m.hopsAway, 0, config.lora.hop_limit, csMax, csMin));
|
// Text inside
|
||||||
|
setFont(fontSmall);
|
||||||
|
setTextColor(WHITE);
|
||||||
|
|
||||||
|
// Draw actual marker on top
|
||||||
|
if (m.hasHopsAway && m.hopsAway > config.lora.hop_limit) {
|
||||||
|
printAt(x + 1, y + 1, "X", CENTER, MIDDLE);
|
||||||
|
} else if (!m.hasHopsAway) {
|
||||||
|
printAt(x + 1, y + 1, "?", CENTER, MIDDLE);
|
||||||
|
} else {
|
||||||
|
char hopStr[4];
|
||||||
|
snprintf(hopStr, sizeof(hopStr), "%d", m.hopsAway);
|
||||||
|
printAt(x, y + 1, hopStr, CENTER, MIDDLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore default font and color
|
||||||
|
setFont(fontSmall);
|
||||||
|
setTextColor(BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dual map scale bars
|
||||||
|
int16_t horizPx = width() * 0.25f;
|
||||||
|
int16_t vertPx = height() * 0.25f;
|
||||||
|
float horizMeters = horizPx / metersToPx;
|
||||||
|
float vertMeters = vertPx / metersToPx;
|
||||||
|
|
||||||
|
auto formatDistance = [&](float meters, char *out, size_t len) {
|
||||||
|
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||||
|
float feet = meters * 3.28084f;
|
||||||
|
if (feet < 528)
|
||||||
|
snprintf(out, len, "%.0f ft", feet);
|
||||||
|
else {
|
||||||
|
float miles = feet / 5280.0f;
|
||||||
|
snprintf(out, len, miles < 10 ? "%.1f mi" : "%.0f mi", miles);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (meters >= 1000)
|
||||||
|
snprintf(out, len, "%.1f km", meters / 1000.0f);
|
||||||
|
else
|
||||||
|
snprintf(out, len, "%.0f m", meters);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Horizontal scale bar
|
||||||
|
int16_t horizBarY = height() - 2;
|
||||||
|
int16_t horizBarX = 1;
|
||||||
|
drawLine(horizBarX, horizBarY, horizBarX + horizPx, horizBarY, BLACK);
|
||||||
|
drawLine(horizBarX, horizBarY - 3, horizBarX, horizBarY + 3, BLACK);
|
||||||
|
drawLine(horizBarX + horizPx, horizBarY - 3, horizBarX + horizPx, horizBarY + 3, BLACK);
|
||||||
|
|
||||||
|
char horizLabel[32];
|
||||||
|
formatDistance(horizMeters, horizLabel, sizeof(horizLabel));
|
||||||
|
int16_t horizLabelW = getTextWidth(horizLabel);
|
||||||
|
int16_t horizLabelH = getFont().lineHeight();
|
||||||
|
int16_t horizLabelX = horizBarX + horizPx + 4;
|
||||||
|
int16_t horizLabelY = horizBarY - horizLabelH + 1;
|
||||||
|
fillRect(horizLabelX - 2, horizLabelY - 1, horizLabelW + 4, horizLabelH + 2, WHITE);
|
||||||
|
printAt(horizLabelX, horizBarY, horizLabel, LEFT, BOTTOM);
|
||||||
|
|
||||||
|
// Vertical scale bar
|
||||||
|
int16_t vertBarX = 1;
|
||||||
|
int16_t vertBarBottom = horizBarY;
|
||||||
|
int16_t vertBarTop = vertBarBottom - vertPx;
|
||||||
|
drawLine(vertBarX, vertBarBottom, vertBarX, vertBarTop, BLACK);
|
||||||
|
drawLine(vertBarX - 3, vertBarBottom, vertBarX + 3, vertBarBottom, BLACK);
|
||||||
|
drawLine(vertBarX - 3, vertBarTop, vertBarX + 3, vertBarTop, BLACK);
|
||||||
|
|
||||||
|
char vertTopLabel[32];
|
||||||
|
formatDistance(vertMeters, vertTopLabel, sizeof(vertTopLabel));
|
||||||
|
int16_t topLabelY = vertBarTop - getFont().lineHeight() - 2;
|
||||||
|
int16_t topLabelW = getTextWidth(vertTopLabel);
|
||||||
|
int16_t topLabelH = getFont().lineHeight();
|
||||||
|
fillRect(vertBarX - 2, topLabelY - 1, topLabelW + 6, topLabelH + 2, WHITE);
|
||||||
|
printAt(vertBarX + (topLabelW / 2) + 1, topLabelY + (topLabelH / 2), vertTopLabel, CENTER, MIDDLE);
|
||||||
|
|
||||||
|
char vertBottomLabel[32];
|
||||||
|
formatDistance(vertMeters, vertBottomLabel, sizeof(vertBottomLabel));
|
||||||
|
int16_t bottomLabelY = vertBarBottom + 4;
|
||||||
|
int16_t bottomLabelW = getTextWidth(vertBottomLabel);
|
||||||
|
int16_t bottomLabelH = getFont().lineHeight();
|
||||||
|
fillRect(vertBarX - 2, bottomLabelY - 1, bottomLabelW + 6, bottomLabelH + 2, WHITE);
|
||||||
|
printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE);
|
||||||
|
|
||||||
|
// Draw our node LAST with full white fill + outline
|
||||||
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
|
if (ourNode && nodeDB->hasValidPosition(ourNode)) {
|
||||||
|
Marker self = calculateMarker(ourNode->position.latitude_i * 1e-7, ourNode->position.longitude_i * 1e-7, false, 0);
|
||||||
|
|
||||||
|
int16_t centerX = X(0.5) + (self.eastMeters * metersToPx);
|
||||||
|
int16_t centerY = Y(0.5) - (self.northMeters * metersToPx);
|
||||||
|
|
||||||
|
// White fill background + halo
|
||||||
|
fillCircle(centerX, centerY, 8, WHITE); // big white base
|
||||||
|
drawCircle(centerX, centerY, 8, WHITE); // crisp edge
|
||||||
|
|
||||||
|
// Black bullseye on top
|
||||||
|
drawCircle(centerX, centerY, 6, BLACK);
|
||||||
|
fillCircle(centerX, centerY, 2, BLACK);
|
||||||
|
|
||||||
|
// Crosshairs
|
||||||
|
drawLine(centerX - 8, centerY, centerX + 8, centerY, BLACK);
|
||||||
|
drawLine(centerX, centerY - 8, centerX, centerY + 8, BLACK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,110 +165,123 @@ void InkHUD::MapApplet::onRender()
|
|||||||
|
|
||||||
void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
||||||
{
|
{
|
||||||
// Find mean lat long coords
|
// If we have a valid position for our own node, use that as the anchor
|
||||||
// ============================
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
// - assigning X, Y and Z values to position on Earth's surface in 3D space, relative to center of planet
|
if (ourNode && nodeDB->hasValidPosition(ourNode)) {
|
||||||
// - averages the x, y and z coords
|
*lat = ourNode->position.latitude_i * 1e-7;
|
||||||
// - uses tan to find angles for lat / long degrees
|
*lng = ourNode->position.longitude_i * 1e-7;
|
||||||
// - longitude: triangle formed by x and y (on plane of the equator)
|
} else {
|
||||||
// - latitude: triangle formed by z (north south),
|
// Find mean lat long coords
|
||||||
// and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's surface
|
// ============================
|
||||||
|
// - assigning X, Y and Z values to position on Earth's surface in 3D space, relative to center of planet
|
||||||
|
// - averages the x, y and z coords
|
||||||
|
// - uses tan to find angles for lat / long degrees
|
||||||
|
// - longitude: triangle formed by x and y (on plane of the equator)
|
||||||
|
// - latitude: triangle formed by z (north south),
|
||||||
|
// and the line along plane of equator which stretches from earth's axis to where point xyz intersects planet's
|
||||||
|
// surface
|
||||||
|
|
||||||
// Working totals, averaged after nodeDB processed
|
// Working totals, averaged after nodeDB processed
|
||||||
uint32_t positionCount = 0;
|
uint32_t positionCount = 0;
|
||||||
float xAvg = 0;
|
float xAvg = 0;
|
||||||
float yAvg = 0;
|
float yAvg = 0;
|
||||||
float zAvg = 0;
|
float zAvg = 0;
|
||||||
|
|
||||||
// For each node in db
|
// For each node in db
|
||||||
for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||||
|
|
||||||
// Skip if no position
|
// Skip if no position
|
||||||
if (!nodeDB->hasValidPosition(node))
|
if (!nodeDB->hasValidPosition(node))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Skip if derived applet doesn't want to show this node on the map
|
// Skip if derived applet doesn't want to show this node on the map
|
||||||
if (!shouldDrawNode(node))
|
if (!shouldDrawNode(node))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Latitude and Longitude of node, in radians
|
// Latitude and Longitude of node, in radians
|
||||||
float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
|
float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
|
||||||
float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD;
|
float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD;
|
||||||
|
|
||||||
// Convert to cartesian points, with center of earth at 0, 0, 0
|
// Convert to cartesian points, with center of earth at 0, 0, 0
|
||||||
// Exact distance from center is irrelevant, as we're only interested in the vector
|
// Exact distance from center is irrelevant, as we're only interested in the vector
|
||||||
float x = cos(latRad) * cos(lngRad);
|
float x = cos(latRad) * cos(lngRad);
|
||||||
float y = cos(latRad) * sin(lngRad);
|
float y = cos(latRad) * sin(lngRad);
|
||||||
float z = sin(latRad);
|
float z = sin(latRad);
|
||||||
|
|
||||||
// To find mean values shortly
|
// To find mean values shortly
|
||||||
xAvg += x;
|
xAvg += x;
|
||||||
yAvg += y;
|
yAvg += y;
|
||||||
zAvg += z;
|
zAvg += z;
|
||||||
positionCount++;
|
positionCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All NodeDB processed, find mean values
|
||||||
|
xAvg /= positionCount;
|
||||||
|
yAvg /= positionCount;
|
||||||
|
zAvg /= positionCount;
|
||||||
|
|
||||||
|
// Longitude from cartesian coords
|
||||||
|
// (Angle from 3D coords describing a point of globe's surface)
|
||||||
|
/*
|
||||||
|
UK
|
||||||
|
/-------\
|
||||||
|
(Top View) /- -\
|
||||||
|
/- (You) -\
|
||||||
|
/- . -\
|
||||||
|
/- . X -\
|
||||||
|
Asia - ... - USA
|
||||||
|
\- Y -/
|
||||||
|
\- -/
|
||||||
|
\- -/
|
||||||
|
\- -/
|
||||||
|
\- -----/
|
||||||
|
Pacific
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
*lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
|
||||||
|
|
||||||
|
// Latitude from cartesian coords
|
||||||
|
// (Angle from 3D coords describing a point on the globe's surface)
|
||||||
|
// As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
|
||||||
|
// Means we need to first find the hypotenuse which becomes base of our triangle in the second step
|
||||||
|
/*
|
||||||
|
UK North
|
||||||
|
/-------\ (Front View) /-------\
|
||||||
|
(Top View) /- -\ /- -\
|
||||||
|
/- (You) -\ /-(You) -\
|
||||||
|
/- /. -\ /- . -\
|
||||||
|
/- √X²+Y²/ . X -\ /- Z . -\
|
||||||
|
Asia - /... - USA - ..... -
|
||||||
|
\- Y -/ \- √X²+Y² -/
|
||||||
|
\- -/ \- -/
|
||||||
|
\- -/ \- -/
|
||||||
|
\- -/ \- -/
|
||||||
|
\- -----/ \- -----/
|
||||||
|
Pacific South
|
||||||
|
*/
|
||||||
|
|
||||||
|
float hypotenuse = sqrt((xAvg * xAvg) + (yAvg * yAvg)); // Distance from globe's north-south axis to surface intersect
|
||||||
|
*lat = atan2(zAvg, hypotenuse) * RAD_TO_DEG;
|
||||||
}
|
}
|
||||||
|
|
||||||
// All NodeDB processed, find mean values
|
// Use either our node position, or the mean fallback as the center
|
||||||
xAvg /= positionCount;
|
latCenter = *lat;
|
||||||
yAvg /= positionCount;
|
lngCenter = *lng;
|
||||||
zAvg /= positionCount;
|
|
||||||
|
|
||||||
// Longitude from cartesian coords
|
|
||||||
// (Angle from 3D coords describing a point of globe's surface)
|
|
||||||
/*
|
|
||||||
UK
|
|
||||||
/-------\
|
|
||||||
(Top View) /- -\
|
|
||||||
/- (You) -\
|
|
||||||
/- . -\
|
|
||||||
/- . X -\
|
|
||||||
Asia - ... - USA
|
|
||||||
\- Y -/
|
|
||||||
\- -/
|
|
||||||
\- -/
|
|
||||||
\- -/
|
|
||||||
\- -----/
|
|
||||||
Pacific
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
*lng = atan2(yAvg, xAvg) * RAD_TO_DEG;
|
|
||||||
|
|
||||||
// Latitude from cartesian coords
|
|
||||||
// (Angle from 3D coords describing a point on the globe's surface)
|
|
||||||
// As latitude increases, distance from the Earth's north-south axis out to our surface point decreases.
|
|
||||||
// Means we need to first find the hypotenuse which becomes base of our triangle in the second step
|
|
||||||
/*
|
|
||||||
UK North
|
|
||||||
/-------\ (Front View) /-------\
|
|
||||||
(Top View) /- -\ /- -\
|
|
||||||
/- (You) -\ /-(You) -\
|
|
||||||
/- /. -\ /- . -\
|
|
||||||
/- √X²+Y²/ . X -\ /- Z . -\
|
|
||||||
Asia - /... - USA - ..... -
|
|
||||||
\- Y -/ \- √X²+Y² -/
|
|
||||||
\- -/ \- -/
|
|
||||||
\- -/ \- -/
|
|
||||||
\- -/ \- -/
|
|
||||||
\- -----/ \- -----/
|
|
||||||
Pacific South
|
|
||||||
*/
|
|
||||||
|
|
||||||
float hypotenuse = sqrt((xAvg * xAvg) + (yAvg * yAvg)); // Distance from globe's north-south axis to surface intersect
|
|
||||||
*lat = atan2(zAvg, hypotenuse) * RAD_TO_DEG;
|
|
||||||
|
|
||||||
// ----------------------------------------------
|
// ----------------------------------------------
|
||||||
// This has given us the "mean position"
|
// This has given us either:
|
||||||
// This will be a position *somewhere* near the center of our nodes.
|
// - our actual position (preferred), or
|
||||||
// What we actually want is to place our center so that our outermost nodes end up on the border of our map.
|
// - a mean position (fallback if we had no fix)
|
||||||
// The only real use of our "mean position" is to give us a reference frame:
|
//
|
||||||
// which direction is east, and which is west.
|
// What we actually want is to place our center so that our outermost nodes
|
||||||
|
// end up on the border of our map. The only real use of our "center" is to give
|
||||||
|
// us a reference frame: which direction is east, and which is west.
|
||||||
//------------------------------------------------
|
//------------------------------------------------
|
||||||
|
|
||||||
// Find furthest nodes from "mean lat long"
|
// Find furthest nodes from our center
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
float northernmost = latCenter;
|
float northernmost = latCenter;
|
||||||
float southernmost = latCenter;
|
float southernmost = latCenter;
|
||||||
float easternmost = lngCenter;
|
float easternmost = lngCenter;
|
||||||
@ -184,14 +299,14 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check for a new top or bottom latitude
|
// Check for a new top or bottom latitude
|
||||||
float lat = node->position.latitude_i * 1e-7;
|
float latNode = node->position.latitude_i * 1e-7;
|
||||||
northernmost = max(northernmost, lat);
|
northernmost = max(northernmost, latNode);
|
||||||
southernmost = min(southernmost, lat);
|
southernmost = min(southernmost, latNode);
|
||||||
|
|
||||||
// Longitude is trickier
|
// Longitude is trickier
|
||||||
float lng = node->position.longitude_i * 1e-7;
|
float lngNode = node->position.longitude_i * 1e-7;
|
||||||
float degEastward = fmod(((lng - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
|
float degEastward = fmod(((lngNode - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
|
||||||
float degWestward = abs(fmod(((lng - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
|
float degWestward = abs(fmod(((lngNode - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
|
||||||
if (degEastward < degWestward)
|
if (degEastward < degWestward)
|
||||||
easternmost = max(easternmost, lngCenter + degEastward);
|
easternmost = max(easternmost, lngCenter + degEastward);
|
||||||
else
|
else
|
||||||
@ -250,7 +365,6 @@ InkHUD::MapApplet::Marker InkHUD::MapApplet::calculateMarker(float lat, float ln
|
|||||||
m.hopsAway = hopsAway;
|
m.hopsAway = hopsAway;
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw a marker on the map for a node, with a shortname label, and backing box
|
// Draw a marker on the map for a node, with a shortname label, and backing box
|
||||||
void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
||||||
{
|
{
|
||||||
@ -324,6 +438,18 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
|||||||
textX = labelX + paddingW;
|
textX = labelX + paddingW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent overlap with scale bars and their labels
|
||||||
|
// Define a "safe zone" in the bottom-left where the scale bars and text are drawn
|
||||||
|
constexpr int16_t safeZoneHeight = 28; // adjust based on your label font height
|
||||||
|
constexpr int16_t safeZoneWidth = 60; // adjust based on horizontal label width zone
|
||||||
|
bool overlapsScale = (labelY + labelH > height() - safeZoneHeight) && (labelX < safeZoneWidth);
|
||||||
|
|
||||||
|
// If it overlaps, shift label upward slightly above the safe zone
|
||||||
|
if (overlapsScale) {
|
||||||
|
labelY = height() - safeZoneHeight - labelH - 2;
|
||||||
|
textY = labelY + (labelH / 2);
|
||||||
|
}
|
||||||
|
|
||||||
// Backing box
|
// Backing box
|
||||||
fillRect(labelX, labelY, labelW, labelH, WHITE);
|
fillRect(labelX, labelY, labelW, labelH, WHITE);
|
||||||
drawRect(labelX, labelY, labelW, labelH, BLACK);
|
drawRect(labelX, labelY, labelW, labelH, BLACK);
|
||||||
|
|||||||
@ -127,6 +127,11 @@ void InkHUD::NodeListApplet::onRender()
|
|||||||
// Y value (top) of the current card. Increases as we draw.
|
// Y value (top) of the current card. Increases as we draw.
|
||||||
uint16_t cardTopY = headerDivY + padDivH;
|
uint16_t cardTopY = headerDivY + padDivH;
|
||||||
|
|
||||||
|
// Clean up deleted nodes before drawing
|
||||||
|
cards.erase(
|
||||||
|
std::remove_if(cards.begin(), cards.end(), [](const CardInfo &c) { return nodeDB->getMeshNode(c.nodeNum) == nullptr; }),
|
||||||
|
cards.end());
|
||||||
|
|
||||||
// -- Each node in list --
|
// -- Each node in list --
|
||||||
for (auto card = cards.begin(); card != cards.end(); ++card) {
|
for (auto card = cards.begin(); card != cards.end(); ++card) {
|
||||||
|
|
||||||
@ -141,6 +146,11 @@ void InkHUD::NodeListApplet::onRender()
|
|||||||
|
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
|
||||||
|
|
||||||
|
// Skip deleted nodes
|
||||||
|
if (!node) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// -- Shortname --
|
// -- Shortname --
|
||||||
// Parse special chars in the short name
|
// Parse special chars in the short name
|
||||||
// Use "?" if unknown
|
// Use "?" if unknown
|
||||||
@ -188,7 +198,7 @@ void InkHUD::NodeListApplet::onRender()
|
|||||||
drawSignalIndicator(signalX, signalY, signalW, signalH, signal);
|
drawSignalIndicator(signalX, signalY, signalW, signalH, signal);
|
||||||
}
|
}
|
||||||
// Otherwise, print "hops away" info, if available
|
// Otherwise, print "hops away" info, if available
|
||||||
else if (hopsAway != CardInfo::HOPS_UNKNOWN) {
|
else if (hopsAway != CardInfo::HOPS_UNKNOWN && node) {
|
||||||
std::string hopString = to_string(node->hops_away);
|
std::string hopString = to_string(node->hops_away);
|
||||||
hopString += " Hop";
|
hopString += " Hop";
|
||||||
if (node->hops_away != 1)
|
if (node->hops_away != 1)
|
||||||
|
|||||||
@ -436,6 +436,12 @@ void setup()
|
|||||||
|
|
||||||
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
|
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
|
||||||
|
|
||||||
|
#if defined(DEBUG_MUTE) && defined(DEBUG_PORT)
|
||||||
|
DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n");
|
||||||
|
DEBUG_PORT.printf("Version %s for %s from %s\r\n", optstr(APP_VERSION), optstr(APP_ENV), optstr(APP_REPO));
|
||||||
|
DEBUG_PORT.printf("Debug mute is enabled, there will be no serial output.\r\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
initDeepSleep();
|
initDeepSleep();
|
||||||
|
|
||||||
#if defined(MODEM_POWER_EN)
|
#if defined(MODEM_POWER_EN)
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
#ifdef USERPREFS_RINGTONE_NAG_SECS
|
#ifdef USERPREFS_RINGTONE_NAG_SECS
|
||||||
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
|
#define default_ringtone_nag_secs USERPREFS_RINGTONE_NAG_SECS
|
||||||
#else
|
#else
|
||||||
#define default_ringtone_nag_secs 60
|
#define default_ringtone_nag_secs 15
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define default_mqtt_address "mqtt.meshtastic.org"
|
#define default_mqtt_address "mqtt.meshtastic.org"
|
||||||
|
|||||||
@ -272,10 +272,10 @@ uint32_t RadioInterface::getTxDelayMsec()
|
|||||||
uint8_t RadioInterface::getCWsize(float snr)
|
uint8_t RadioInterface::getCWsize(float snr)
|
||||||
{
|
{
|
||||||
// The minimum value for a LoRa SNR
|
// The minimum value for a LoRa SNR
|
||||||
const uint32_t SNR_MIN = -20;
|
const int32_t SNR_MIN = -20;
|
||||||
|
|
||||||
// The maximum value for a LoRa SNR
|
// The maximum value for a LoRa SNR
|
||||||
const uint32_t SNR_MAX = 10;
|
const int32_t SNR_MAX = 10;
|
||||||
|
|
||||||
return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
|
return map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -289,12 +289,7 @@ void RadioLibInterface::onNotify(uint32_t notification)
|
|||||||
// actual transmission as short as possible
|
// actual transmission as short as possible
|
||||||
txp = txQueue.dequeue();
|
txp = txQueue.dequeue();
|
||||||
assert(txp);
|
assert(txp);
|
||||||
bool sent = startSend(txp);
|
startSend(txp);
|
||||||
if (sent) {
|
|
||||||
// Packet has been sent, count it toward our TX airtime utilization.
|
|
||||||
uint32_t xmitMsec = getPacketTime(txp);
|
|
||||||
airTime->logAirtime(TX_LOG, xmitMsec);
|
|
||||||
}
|
|
||||||
LOG_DEBUG("%d packets remain in the TX queue", txQueue.getMaxLen() - txQueue.getFree());
|
LOG_DEBUG("%d packets remain in the TX queue", txQueue.getMaxLen() - txQueue.getFree());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -413,6 +408,10 @@ void RadioLibInterface::completeSending()
|
|||||||
sendingPacket = NULL;
|
sendingPacket = NULL;
|
||||||
|
|
||||||
if (p) {
|
if (p) {
|
||||||
|
// Packet has been sent, count it toward our TX airtime utilization.
|
||||||
|
uint32_t xmitMsec = getPacketTime(p);
|
||||||
|
airTime->logAirtime(TX_LOG, xmitMsec);
|
||||||
|
|
||||||
txGood++;
|
txGood++;
|
||||||
if (!isFromUs(p))
|
if (!isFromUs(p))
|
||||||
txRelay++;
|
txRelay++;
|
||||||
|
|||||||
@ -35,6 +35,15 @@
|
|||||||
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
|
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
|
||||||
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
|
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
|
||||||
|
|
||||||
|
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
|
||||||
|
Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
|
||||||
|
#elif defined(ARCH_STM32WL)
|
||||||
|
// On STM32 there isn't enough heap left over for the rest of the firmware if we allocate this statically.
|
||||||
|
// For now, make it dynamic again.
|
||||||
|
#define MAX_PACKETS \
|
||||||
|
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
|
||||||
|
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
|
||||||
|
|
||||||
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
|
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
|
||||||
Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
|
Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
|
||||||
#else
|
#else
|
||||||
|
|||||||
@ -94,8 +94,8 @@ int32_t ExternalNotificationModule::runOnce()
|
|||||||
// audioThread->isPlaying() also handles actually playing the RTTTL, needs to be called in loop
|
// audioThread->isPlaying() also handles actually playing the RTTTL, needs to be called in loop
|
||||||
isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
|
isRtttlPlaying = isRtttlPlaying || audioThread->isPlaying();
|
||||||
#endif
|
#endif
|
||||||
if ((nagCycleCutoff < millis()) && !isRtttlPlaying) {
|
if ((nagCycleCutoff <= millis())) {
|
||||||
// let the song finish if we reach timeout
|
// Turn off external notification immediately when timeout is reached, regardless of song state
|
||||||
nagCycleCutoff = UINT32_MAX;
|
nagCycleCutoff = UINT32_MAX;
|
||||||
LOG_INFO("Turning off external notification: ");
|
LOG_INFO("Turning off external notification: ");
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
@ -103,7 +103,6 @@ int32_t ExternalNotificationModule::runOnce()
|
|||||||
externalTurnedOn[i] = 0;
|
externalTurnedOn[i] = 0;
|
||||||
LOG_INFO("%d ", i);
|
LOG_INFO("%d ", i);
|
||||||
}
|
}
|
||||||
LOG_INFO("");
|
|
||||||
#ifdef HAS_I2S
|
#ifdef HAS_I2S
|
||||||
// GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
|
// GPIO0 is used as mclk for I2S audio and set to OUTPUT by the sound library
|
||||||
// T-Deck uses GPIO0 as trackball button, so restore the mode
|
// T-Deck uses GPIO0 as trackball button, so restore the mode
|
||||||
|
|||||||
@ -159,6 +159,7 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
|
|||||||
LOG_DEBUG("---- Received Packet:");
|
LOG_DEBUG("---- Received Packet:");
|
||||||
LOG_DEBUG("mp.from %d", mp.from);
|
LOG_DEBUG("mp.from %d", mp.from);
|
||||||
LOG_DEBUG("mp.rx_snr %f", mp.rx_snr);
|
LOG_DEBUG("mp.rx_snr %f", mp.rx_snr);
|
||||||
|
LOG_DEBUG("mp.rx_rssi %f", mp.rx_rssi);
|
||||||
LOG_DEBUG("mp.hop_limit %d", mp.hop_limit);
|
LOG_DEBUG("mp.hop_limit %d", mp.hop_limit);
|
||||||
LOG_DEBUG("---- Node Information of Received Packet (mp.from):");
|
LOG_DEBUG("---- Node Information of Received Packet (mp.from):");
|
||||||
LOG_DEBUG("n->user.long_name %s", n->user.long_name);
|
LOG_DEBUG("n->user.long_name %s", n->user.long_name);
|
||||||
@ -234,8 +235,8 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print the CSV header
|
// Print the CSV header
|
||||||
if (fileToWrite.println(
|
if (fileToWrite.println("time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx "
|
||||||
"time,from,sender name,sender lat,sender long,rx lat,rx long,rx elevation,rx snr,distance,hop limit,payload")) {
|
"snr,distance,hop limit,payload,rx rssi")) {
|
||||||
LOG_INFO("File was written");
|
LOG_INFO("File was written");
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR("File write failed");
|
LOG_ERROR("File write failed");
|
||||||
@ -297,6 +298,8 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
|
|||||||
|
|
||||||
// TODO: If quotes are found in the payload, it has to be escaped.
|
// TODO: If quotes are found in the payload, it has to be escaped.
|
||||||
fileToAppend.printf("\"%s\"\n", p.payload.bytes);
|
fileToAppend.printf("\"%s\"\n", p.payload.bytes);
|
||||||
|
fileToAppend.printf("%i,", mp.rx_rssi); // RX RSSI
|
||||||
|
|
||||||
fileToAppend.flush();
|
fileToAppend.flush();
|
||||||
fileToAppend.close();
|
fileToAppend.close();
|
||||||
|
|
||||||
|
|||||||
@ -49,7 +49,7 @@ NimBLECharacteristic *logRadioCharacteristic;
|
|||||||
NimBLEServer *bleServer;
|
NimBLEServer *bleServer;
|
||||||
|
|
||||||
static bool passkeyShowing;
|
static bool passkeyShowing;
|
||||||
static std::atomic<int32_t> nimbleBluetoothConnHandle{-1}; // actual handles are uint16_t, so -1 means "no connection"
|
static std::atomic<uint16_t> nimbleBluetoothConnHandle{BLE_HS_CONN_HANDLE_NONE}; // BLE_HS_CONN_HANDLE_NONE means "no connection"
|
||||||
|
|
||||||
class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
||||||
{
|
{
|
||||||
@ -144,21 +144,28 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
|||||||
protected:
|
protected:
|
||||||
virtual int32_t runOnce() override
|
virtual int32_t runOnce() override
|
||||||
{
|
{
|
||||||
bool shouldBreakAndRetryLater = false;
|
|
||||||
|
|
||||||
while (runOnceHasWorkToDo()) {
|
while (runOnceHasWorkToDo()) {
|
||||||
// Important that we service onRead first, because the onRead callback blocks NimBLE until we clear
|
/*
|
||||||
// onReadCallbackIsWaitingForData.
|
PROCESS fromPhoneQueue BEFORE toPhoneQueue:
|
||||||
shouldBreakAndRetryLater = runOnceHandleToPhoneQueue(); // push data from getFromRadio to onRead
|
|
||||||
runOnceHandleFromPhoneQueue(); // pull data from onWrite to handleToRadio
|
|
||||||
|
|
||||||
if (shouldBreakAndRetryLater) {
|
In normal STATE_SEND_PACKETS operation, it's unlikely that we'll have both writes and reads to process at the same
|
||||||
// onRead still wants data, but it's not available yet. Return so we can try again when a packet may be ready.
|
time, because either onWrite or onRead will trigger this runOnce. And in STATE_SEND_PACKETS, it's generally ok to
|
||||||
#ifdef DEBUG_NIMBLE_ON_READ_TIMING
|
service either the reads or writes first.
|
||||||
LOG_INFO("BLE runOnce breaking to retry later (leaving onRead waiting)");
|
|
||||||
#endif
|
However, during the initial setup wantConfig packet, the clients send a write and immediately send a read, and they
|
||||||
return 100; // try again in 100ms
|
expect the read will respond to the write. (This also happens when a client goes from STATE_SEND_PACKETS back to
|
||||||
}
|
another wantConfig, like the iOS client does when requesting the nodedb after requesting the main config only.)
|
||||||
|
|
||||||
|
So it's safest to always service writes (fromPhoneQueue) before reads (toPhoneQueue), so that any "synchronous"
|
||||||
|
write-then-read sequences from the client work as expected, even if this means we block onRead for a while: this is
|
||||||
|
what the client wants!
|
||||||
|
*/
|
||||||
|
|
||||||
|
// PHONE -> RADIO:
|
||||||
|
runOnceHandleFromPhoneQueue(); // pull data from onWrite to handleToRadio
|
||||||
|
|
||||||
|
// RADIO -> PHONE:
|
||||||
|
runOnceHandleToPhoneQueue(); // push data from getFromRadio to onRead
|
||||||
}
|
}
|
||||||
|
|
||||||
// the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
|
// the run is triggered via NimbleBluetoothToRadioCallback and NimbleBluetoothFromRadioCallback
|
||||||
@ -171,9 +178,9 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
|||||||
|
|
||||||
// Prefer high throughput during config/setup, at the cost of high power consumption (for a few seconds)
|
// Prefer high throughput during config/setup, at the cost of high power consumption (for a few seconds)
|
||||||
if (bleServer && isConnected()) {
|
if (bleServer && isConnected()) {
|
||||||
int32_t conn_handle = nimbleBluetoothConnHandle.load();
|
uint16_t conn_handle = nimbleBluetoothConnHandle.load();
|
||||||
if (conn_handle != -1) {
|
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||||
requestHighThroughputConnection(static_cast<uint16_t>(conn_handle));
|
requestHighThroughputConnection(conn_handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,9 +191,9 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
|||||||
|
|
||||||
// Switch to lower power consumption BLE connection params for steady-state use after config/setup is complete
|
// Switch to lower power consumption BLE connection params for steady-state use after config/setup is complete
|
||||||
if (bleServer && isConnected()) {
|
if (bleServer && isConnected()) {
|
||||||
int32_t conn_handle = nimbleBluetoothConnHandle.load();
|
uint16_t conn_handle = nimbleBluetoothConnHandle.load();
|
||||||
if (conn_handle != -1) {
|
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||||
requestLowerPowerConnection(static_cast<uint16_t>(conn_handle));
|
requestLowerPowerConnection(conn_handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,12 +227,8 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool runOnceHandleToPhoneQueue()
|
void runOnceHandleToPhoneQueue()
|
||||||
{
|
{
|
||||||
// Returns false normally.
|
|
||||||
// Returns true if we should break out of runOnce and retry later, such as setup states where getFromRadio returns 0
|
|
||||||
// bytes.
|
|
||||||
|
|
||||||
// Stack buffer for getFromRadio packet
|
// Stack buffer for getFromRadio packet
|
||||||
uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
|
uint8_t fromRadioBytes[meshtastic_FromRadio_size] = {0};
|
||||||
size_t numBytes = 0;
|
size_t numBytes = 0;
|
||||||
@ -234,28 +237,15 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
|||||||
numBytes = getFromRadio(fromRadioBytes);
|
numBytes = getFromRadio(fromRadioBytes);
|
||||||
|
|
||||||
if (numBytes == 0) {
|
if (numBytes == 0) {
|
||||||
// Client expected a read, but we have nothing to send.
|
/*
|
||||||
// Returning a 0-byte packet breaks clients during the config phase, so we have to block onRead until there's a
|
Client expected a read, but we have nothing to send.
|
||||||
// packet ready.
|
|
||||||
if (isSendingPackets()) {
|
|
||||||
// In STATE_SEND_PACKETS, it is 100% OK to return a 0-byte response, as we expect clients to do read beyond
|
|
||||||
// notifies regularly, to make sure they have nothing else to read.
|
|
||||||
#ifdef DEBUG_NIMBLE_ON_READ_TIMING
|
|
||||||
LOG_DEBUG("BLE getFromRadio returned numBytes=0, but in STATE_SEND_PACKETS, so clearing "
|
|
||||||
"onReadCallbackIsWaitingForData flag");
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
// In other states, this breaks clients.
|
|
||||||
// Return early, leaving onReadCallbackIsWaitingForData==true so onRead knows to try again.
|
|
||||||
// This gives runOnce a chance to handleToRadio and produce a response.
|
|
||||||
#ifdef DEBUG_NIMBLE_ON_READ_TIMING
|
|
||||||
LOG_DEBUG("BLE getFromRadio returned numBytes=0. Blocking onRead until we have data");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Return true to tell runOnce to shouldBreakAndRetryLater, so we don't busy-loop in runOnce even though
|
In STATE_SEND_PACKETS, it is 100% OK to return a 0-byte response, as we expect clients to do read beyond
|
||||||
// onRead is still waiting!
|
notifies regularly, to make sure they have nothing else to read.
|
||||||
return true;
|
|
||||||
}
|
In other states, this is fine **so long as we've already processed pending onWrites first**, because the client
|
||||||
|
may requesting wantConfig and immediately doing a read.
|
||||||
|
*/
|
||||||
} else {
|
} else {
|
||||||
// Push to toPhoneQueue, protected by toPhoneMutex. Hold the mutex as briefly as possible.
|
// Push to toPhoneQueue, protected by toPhoneMutex. Hold the mutex as briefly as possible.
|
||||||
if (toPhoneQueueSize < NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE) {
|
if (toPhoneQueueSize < NIMBLE_BLUETOOTH_TO_PHONE_QUEUE_SIZE) {
|
||||||
@ -282,8 +272,6 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
|
|||||||
// Clear the onReadCallbackIsWaitingForData flag so onRead knows it can proceed.
|
// Clear the onReadCallbackIsWaitingForData flag so onRead knows it can proceed.
|
||||||
onReadCallbackIsWaitingForData = false; // only clear this flag AFTER the push
|
onReadCallbackIsWaitingForData = false; // only clear this flag AFTER the push
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool runOnceHasWorkFromPhone() { return fromPhoneQueueSize > 0; }
|
bool runOnceHasWorkFromPhone() { return fromPhoneQueueSize > 0; }
|
||||||
@ -466,10 +454,6 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
|
|||||||
virtual void onRead(NimBLECharacteristic *pCharacteristic)
|
virtual void onRead(NimBLECharacteristic *pCharacteristic)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
// In some cases, it seems a new connection starts with a read.
|
|
||||||
// The API has no bytes to send, leading to a timeout. This short-circuits this problem.
|
|
||||||
if (!bluetoothPhoneAPI->isConnected())
|
|
||||||
return;
|
|
||||||
// CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
|
// CAUTION: This callback runs in the NimBLE task!!! Don't do anything except communicate with the main task's runOnce.
|
||||||
|
|
||||||
int currentReadCount = bluetoothPhoneAPI->readCount.fetch_add(1);
|
int currentReadCount = bluetoothPhoneAPI->readCount.fetch_add(1);
|
||||||
@ -726,7 +710,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
|
|||||||
// Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
|
// Clear the last ToRadio packet buffer to avoid rejecting first packet from new connection
|
||||||
memset(lastToRadio, 0, sizeof(lastToRadio));
|
memset(lastToRadio, 0, sizeof(lastToRadio));
|
||||||
|
|
||||||
nimbleBluetoothConnHandle = -1; // -1 means "no connection"
|
nimbleBluetoothConnHandle = BLE_HS_CONN_HANDLE_NONE; // BLE_HS_CONN_HANDLE_NONE means "no connection"
|
||||||
|
|
||||||
#ifdef NIMBLE_TWO
|
#ifdef NIMBLE_TWO
|
||||||
// Restart Advertising
|
// Restart Advertising
|
||||||
|
|||||||
@ -244,6 +244,10 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
|
|||||||
// pinMode(PIN_POWER_EN1, INPUT_PULLDOWN);
|
// pinMode(PIN_POWER_EN1, INPUT_PULLDOWN);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef RAK_WISMESH_TAP_V2
|
||||||
|
digitalWrite(SDCARD_CS, LOW);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef TRACKER_T1000_E
|
#ifdef TRACKER_T1000_E
|
||||||
#ifdef GNSS_AIROHA
|
#ifdef GNSS_AIROHA
|
||||||
digitalWrite(GPS_VRTC_EN, LOW);
|
digitalWrite(GPS_VRTC_EN, LOW);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user