mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-21 18:52:36 +10:00
Merge branch 'develop' into vibe-coded-dmshell
This commit is contained in:
@@ -626,10 +626,14 @@ void AdminModule::handleSetOwner(const meshtastic_User &o)
|
||||
if (*o.long_name) {
|
||||
changed |= strcmp(owner.long_name, o.long_name);
|
||||
strncpy(owner.long_name, o.long_name, sizeof(owner.long_name));
|
||||
owner.long_name[sizeof(owner.long_name) - 1] = '\0';
|
||||
sanitizeUtf8(owner.long_name, sizeof(owner.long_name));
|
||||
}
|
||||
if (*o.short_name) {
|
||||
changed |= strcmp(owner.short_name, o.short_name);
|
||||
strncpy(owner.short_name, o.short_name, sizeof(owner.short_name));
|
||||
owner.short_name[sizeof(owner.short_name) - 1] = '\0';
|
||||
sanitizeUtf8(owner.short_name, sizeof(owner.short_name));
|
||||
}
|
||||
snprintf(owner.id, sizeof(owner.id), "!%08x", nodeDB->getNodeNum());
|
||||
|
||||
@@ -1430,7 +1434,11 @@ void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p)
|
||||
|
||||
// Set call sign and override lora limitations for licensed use
|
||||
strncpy(owner.long_name, p.call_sign, sizeof(owner.long_name));
|
||||
owner.long_name[sizeof(owner.long_name) - 1] = '\0';
|
||||
sanitizeUtf8(owner.long_name, sizeof(owner.long_name));
|
||||
strncpy(owner.short_name, p.short_name, sizeof(owner.short_name));
|
||||
owner.short_name[sizeof(owner.short_name) - 1] = '\0';
|
||||
sanitizeUtf8(owner.short_name, sizeof(owner.short_name));
|
||||
owner.is_licensed = true;
|
||||
config.lora.override_duty_cycle = true;
|
||||
config.lora.tx_power = p.tx_power;
|
||||
@@ -1519,8 +1527,15 @@ void AdminModule::handleSendInputEvent(const meshtastic_AdminMessage_InputEvent
|
||||
LOG_DEBUG("Processing input event: event_code=%u, kb_char=%u, touch_x=%u, touch_y=%u", inputEvent.event_code,
|
||||
inputEvent.kb_char, inputEvent.touch_x, inputEvent.touch_y);
|
||||
|
||||
// Create InputEvent for injection
|
||||
InputEvent event = {.inputEvent = (input_broker_event)inputEvent.event_code,
|
||||
// Create InputEvent for injection.
|
||||
//
|
||||
// `.source` MUST be a non-null C string: the LOG_INFO below formats it
|
||||
// with %s, and passing NULL to the esp-log formatter crashes with
|
||||
// Guru Meditation LoadProhibited at strlen(NULL). Other InputBroker
|
||||
// sources (buttons, rotary) always set this; the admin path was the
|
||||
// only one leaving it default-null.
|
||||
InputEvent event = {.source = "admin",
|
||||
.inputEvent = (input_broker_event)inputEvent.event_code,
|
||||
.kbchar = (unsigned char)inputEvent.kb_char,
|
||||
.touchX = inputEvent.touch_x,
|
||||
.touchY = inputEvent.touch_y};
|
||||
|
||||
@@ -492,15 +492,24 @@ void PositionModule::sendLostAndFoundText()
|
||||
{
|
||||
meshtastic_MeshPacket *p = allocDataPacket();
|
||||
p->to = NODENUM_BROADCAST;
|
||||
char *message = new char[60];
|
||||
sprintf(message, "🚨I'm lost! Lat / Lon: %f, %f\a", (lastGpsLatitude * 1e-7), (lastGpsLongitude * 1e-7));
|
||||
char message[128];
|
||||
int written = snprintf(message, sizeof(message), "🚨I'm lost! Lat / Lon: %f, %f\a", (lastGpsLatitude * 1e-7),
|
||||
(lastGpsLongitude * 1e-7));
|
||||
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
|
||||
p->want_ack = false;
|
||||
p->decoded.payload.size = strlen(message);
|
||||
memcpy(p->decoded.payload.bytes, message, p->decoded.payload.size);
|
||||
if (written < 0) {
|
||||
// snprintf encoding error — send an empty payload rather than uninitialized bytes.
|
||||
p->decoded.payload.size = 0;
|
||||
} else {
|
||||
// Clamp to buffer capacity (snprintf returns "would-have-written" which can exceed the buffer).
|
||||
const size_t msg_len = std::min(static_cast<size_t>(written), sizeof(message) - 1);
|
||||
p->decoded.payload.size = msg_len;
|
||||
if (msg_len > 0) {
|
||||
memcpy(p->decoded.payload.bytes, message, msg_len);
|
||||
}
|
||||
}
|
||||
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
delete[] message;
|
||||
}
|
||||
|
||||
// Helper: return imprecise (truncated + centered) lat/lon as int32 using current precision
|
||||
@@ -580,4 +589,4 @@ void PositionModule::handleNewPosition()
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -206,7 +206,7 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
|
||||
this->packetHistory[this->packetHistoryTotalCount].hop_limit = mp.hop_limit;
|
||||
this->packetHistory[this->packetHistoryTotalCount].via_mqtt = mp.via_mqtt;
|
||||
this->packetHistory[this->packetHistoryTotalCount].transport_mechanism = mp.transport_mechanism;
|
||||
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
|
||||
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, p.payload.size);
|
||||
|
||||
this->packetHistoryTotalCount++;
|
||||
}
|
||||
|
||||
@@ -15,15 +15,6 @@
|
||||
|
||||
WaypointModule *waypointModule;
|
||||
|
||||
static inline float degToRad(float deg)
|
||||
{
|
||||
return deg * PI / 180.0f;
|
||||
}
|
||||
static inline float radToDeg(float rad)
|
||||
{
|
||||
return rad * 180.0f / PI;
|
||||
}
|
||||
|
||||
ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
@@ -91,9 +82,7 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
|
||||
|
||||
// === Header ===
|
||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||
|
||||
const int w = display->getWidth();
|
||||
const int h = display->getHeight();
|
||||
const int *textPos = graphics::getTextPositions(display);
|
||||
|
||||
// Decode the waypoint
|
||||
const meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
|
||||
@@ -108,71 +97,118 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
|
||||
getTimeAgoStr(sinceReceived(&mp), lastStr, sizeof(lastStr));
|
||||
|
||||
// Will contain distance information, passed as a field to drawColumns
|
||||
char distStr[20];
|
||||
char distStr[20] = "";
|
||||
|
||||
// Get our node, to use our own position
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
|
||||
// Dimensions / co-ordinates for the compass/circle
|
||||
const uint16_t compassDiam = graphics::CompassRenderer::getCompassDiam(w, h);
|
||||
const int16_t compassX = x + w - (compassDiam / 2) - 5;
|
||||
const int16_t compassY = (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT)
|
||||
? y + h / 2
|
||||
: y + FONT_HEIGHT_SMALL + (h - FONT_HEIGHT_SMALL) / 2;
|
||||
// Match compass sizing/placement to favorite node screen logic.
|
||||
const int w = display->getWidth();
|
||||
int16_t compassRadius = 8;
|
||||
int16_t compassX = x + w - compassRadius - 8;
|
||||
int16_t compassY = y + display->getHeight() / 2;
|
||||
|
||||
// If our node has a position:
|
||||
if (ourNode && (nodeDB->hasValidPosition(ourNode) || screen->hasHeading())) {
|
||||
const meshtastic_PositionLite &op = ourNode->position;
|
||||
float myHeading;
|
||||
if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING) {
|
||||
myHeading = 0;
|
||||
} else {
|
||||
if (screen->hasHeading())
|
||||
myHeading = degToRad(screen->getHeading());
|
||||
else
|
||||
myHeading = screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
if (SCREEN_WIDTH > SCREEN_HEIGHT) {
|
||||
const int16_t topY = textPos[1];
|
||||
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
|
||||
const int16_t usableHeight = bottomY - topY - 5;
|
||||
compassRadius = usableHeight / 2;
|
||||
if (compassRadius < 8)
|
||||
compassRadius = 8;
|
||||
compassX = x + SCREEN_WIDTH - compassRadius - 8;
|
||||
compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
|
||||
} else {
|
||||
// Waypoint content uses rows 1..4, so place the compass below that block.
|
||||
const int yBelowContent = textPos[4] + FONT_HEIGHT_SMALL + 2;
|
||||
const int margin = 4;
|
||||
#if defined(USE_EINK)
|
||||
const int iconSize = (graphics::currentResolution == graphics::ScreenResolution::High) ? 16 : 8;
|
||||
const int navBarHeight = iconSize + 6;
|
||||
#else
|
||||
const int navBarHeight = 0;
|
||||
#endif
|
||||
const int availableHeight = SCREEN_HEIGHT - yBelowContent - navBarHeight - margin;
|
||||
if (availableHeight > 0) {
|
||||
compassRadius = availableHeight / 2;
|
||||
if (compassRadius < 8)
|
||||
compassRadius = 8;
|
||||
if (compassRadius * 2 > SCREEN_WIDTH - 16)
|
||||
compassRadius = (SCREEN_WIDTH - 16) / 2;
|
||||
if (compassRadius < 8)
|
||||
compassRadius = 8;
|
||||
compassX = x + SCREEN_WIDTH / 2;
|
||||
compassY = yBelowContent + availableHeight / 2;
|
||||
}
|
||||
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, (compassDiam / 2));
|
||||
}
|
||||
const uint16_t compassDiam = compassRadius * 2;
|
||||
|
||||
// Compass bearing to waypoint
|
||||
float bearingToOther =
|
||||
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
|
||||
// If the top of the compass is a static north then bearingToOther can be drawn on the compass directly
|
||||
// If the top of the compass is not a static north we need adjust bearingToOther based on heading
|
||||
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING)
|
||||
bearingToOther -= myHeading;
|
||||
graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
|
||||
const bool hasOwnPositionFix = (ourNode && nodeDB->hasValidPosition(ourNode));
|
||||
const char *statusLine1 = nullptr;
|
||||
const char *statusLine2 = nullptr;
|
||||
|
||||
float bearingToOtherDegrees = (bearingToOther < 0) ? bearingToOther + 2 * PI : bearingToOther;
|
||||
bearingToOtherDegrees = radToDeg(bearingToOtherDegrees);
|
||||
// Distance only needs our own position fix; compass/bearing additionally needs heading.
|
||||
if (hasOwnPositionFix) {
|
||||
const meshtastic_PositionLite &op = ourNode->position;
|
||||
const float d =
|
||||
GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
|
||||
// Distance to Waypoint
|
||||
float d = GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
// Always show distance once we have an own-position fix, even without heading.
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
float feet = d * METERS_TO_FEET;
|
||||
snprintf(distStr, sizeof(distStr), feet < (2 * MILES_TO_FEET) ? "%.0fft %.0f°" : "%.1fmi %.0f°",
|
||||
feet < (2 * MILES_TO_FEET) ? feet : feet / MILES_TO_FEET, bearingToOtherDegrees);
|
||||
snprintf(distStr, sizeof(distStr), feet < (2 * MILES_TO_FEET) ? "%.0fft" : "%.1fmi",
|
||||
feet < (2 * MILES_TO_FEET) ? feet : feet / MILES_TO_FEET);
|
||||
} else {
|
||||
snprintf(distStr, sizeof(distStr), d < 2000 ? "%.0fm %.0f°" : "%.1fkm %.0f°", d < 2000 ? d : d / 1000,
|
||||
bearingToOtherDegrees);
|
||||
snprintf(distStr, sizeof(distStr), d < 2000 ? "%.0fm" : "%.1fkm", d < 2000 ? d : d / 1000);
|
||||
}
|
||||
|
||||
float myHeading = 0.0f;
|
||||
const bool hasHeading =
|
||||
graphics::CompassRenderer::getHeadingRadians(DegD(op.latitude_i), DegD(op.longitude_i), myHeading);
|
||||
if (hasHeading) {
|
||||
// Draw compass circle
|
||||
display->drawCircle(compassX, compassY, compassRadius);
|
||||
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
|
||||
|
||||
// Compass bearing to waypoint
|
||||
float bearingToOther =
|
||||
GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(wp.latitude_i), DegD(wp.longitude_i));
|
||||
bearingToOther = graphics::CompassRenderer::adjustBearingForCompassMode(bearingToOther, myHeading);
|
||||
graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassDiam, bearingToOther);
|
||||
|
||||
const float bearingToOtherDegrees = graphics::CompassRenderer::radiansToDegrees360(bearingToOther);
|
||||
|
||||
// Distance to waypoint with relative bearing when heading is available.
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||
float feet = d * METERS_TO_FEET;
|
||||
snprintf(distStr, sizeof(distStr), feet < (2 * MILES_TO_FEET) ? "%.0fft %.0f°" : "%.1fmi %.0f°",
|
||||
feet < (2 * MILES_TO_FEET) ? feet : feet / MILES_TO_FEET, bearingToOtherDegrees);
|
||||
} else {
|
||||
snprintf(distStr, sizeof(distStr), d < 2000 ? "%.0fm %.0f°" : "%.1fkm %.0f°", d < 2000 ? d : d / 1000,
|
||||
bearingToOtherDegrees);
|
||||
}
|
||||
|
||||
} else {
|
||||
statusLine1 = "No";
|
||||
statusLine2 = "Heading";
|
||||
}
|
||||
} else {
|
||||
// No own fix yet, so compass/bearing data would be misleading.
|
||||
statusLine1 = "No";
|
||||
statusLine2 = "Fix";
|
||||
}
|
||||
|
||||
else {
|
||||
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
|
||||
|
||||
// ? in the distance field
|
||||
snprintf(distStr, sizeof(distStr), "? %s ?°",
|
||||
(config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) ? "mi" : "km");
|
||||
if (statusLine1) {
|
||||
display->drawCircle(compassX, compassY, compassRadius);
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->drawString(compassX, compassY - FONT_HEIGHT_SMALL, statusLine1);
|
||||
display->drawString(compassX, compassY, statusLine2);
|
||||
}
|
||||
|
||||
// Draw compass circle
|
||||
display->drawCircle(compassX, compassY, compassDiam / 2);
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT); // Something above me changes to a different alignment, forcing a fix here!
|
||||
display->drawString(0, graphics::getTextPositions(display)[line++], lastStr);
|
||||
display->drawString(0, graphics::getTextPositions(display)[line++], wp.name);
|
||||
display->drawString(0, graphics::getTextPositions(display)[line++], wp.description);
|
||||
display->drawString(0, graphics::getTextPositions(display)[line++], distStr);
|
||||
display->drawString(0, textPos[line++], lastStr);
|
||||
display->drawString(0, textPos[line++], wp.name);
|
||||
display->drawString(0, textPos[line++], wp.description);
|
||||
if (distStr[0])
|
||||
display->drawString(0, textPos[line++], distStr);
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user