mirror of
https://github.com/meshtastic/firmware.git
synced 2025-10-28 15:22:55 +00:00
Ack on messages sent
This commit is contained in:
parent
07d3726cde
commit
28502c93c9
@ -59,6 +59,9 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa
|
||||
sm.dest = packet.decoded.dest;
|
||||
sm.type = MessageType::DM_TO_US;
|
||||
}
|
||||
|
||||
// Outgoing messages start as UNKNOWN until ACK/NACK arrives
|
||||
sm.ackStatus = AckStatus::UNKNOWN;
|
||||
} else {
|
||||
// Normal incoming
|
||||
sm.sender = packet.from;
|
||||
@ -72,6 +75,9 @@ const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &pa
|
||||
sm.dest = NODENUM_BROADCAST; // fallback
|
||||
sm.type = MessageType::BROADCAST;
|
||||
}
|
||||
|
||||
// Received messages don’t wait for ACK mark as ACKED
|
||||
sm.ackStatus = AckStatus::ACKED;
|
||||
}
|
||||
|
||||
addLiveMessage(sm);
|
||||
@ -103,6 +109,9 @@ void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const st
|
||||
sm.dest = NODENUM_BROADCAST;
|
||||
sm.type = MessageType::BROADCAST;
|
||||
|
||||
// Manual/outgoing messages start as UNKNOWN until ACK/NACK arrives
|
||||
sm.ackStatus = AckStatus::UNKNOWN;
|
||||
|
||||
addLiveMessage(sm);
|
||||
}
|
||||
|
||||
@ -131,8 +140,12 @@ void MessageStore::saveToFlash()
|
||||
f.write((uint8_t *)&m.dest, sizeof(m.dest));
|
||||
f.write((uint8_t *)m.text.c_str(), std::min(static_cast<size_t>(MAX_MESSAGE_SIZE), m.text.size()));
|
||||
f.write('\0'); // null terminator
|
||||
|
||||
uint8_t bootFlag = m.isBootRelative ? 1 : 0;
|
||||
f.write(&bootFlag, 1); // persist boot-relative flag
|
||||
|
||||
uint8_t statusByte = static_cast<uint8_t>(m.ackStatus);
|
||||
f.write(&statusByte, 1); // persist ackStatus
|
||||
}
|
||||
spiLock->unlock();
|
||||
|
||||
@ -189,6 +202,18 @@ void MessageStore::loadFromFlash()
|
||||
m.isBootRelative = (m.timestamp < 60u * 60u * 24u * 7u);
|
||||
}
|
||||
|
||||
// Try to read ackStatus (newer format)
|
||||
if (f.available() > 0) {
|
||||
uint8_t statusByte = 0;
|
||||
if (f.readBytes((char *)&statusByte, 1) == 1) {
|
||||
m.ackStatus = static_cast<AckStatus>(statusByte);
|
||||
} else {
|
||||
m.ackStatus = AckStatus::UNKNOWN; // fallback
|
||||
}
|
||||
} else {
|
||||
m.ackStatus = AckStatus::UNKNOWN; // legacy files
|
||||
}
|
||||
|
||||
// Recompute type from dest
|
||||
if (m.dest == NODENUM_BROADCAST) {
|
||||
m.type = MessageType::BROADCAST;
|
||||
|
||||
@ -11,6 +11,14 @@ constexpr size_t MAX_MESSAGE_SIZE = 220; // safe bound for text payload
|
||||
// Explicit message classification
|
||||
enum class MessageType : uint8_t { BROADCAST = 0, DM_TO_US = 1 };
|
||||
|
||||
// Delivery status for messages we sent
|
||||
enum class AckStatus : uint8_t {
|
||||
UNKNOWN = 0, // default (not yet resolved)
|
||||
ACKED = 1, // got a valid ACK
|
||||
NACKED = 2, // explicitly failed
|
||||
TIMEOUT = 3 // no ACK after retry window
|
||||
};
|
||||
|
||||
struct StoredMessage {
|
||||
uint32_t timestamp; // When message was created (secs since boot/RTC)
|
||||
uint32_t sender; // NodeNum of sender
|
||||
@ -29,10 +37,13 @@ struct StoredMessage {
|
||||
// (true = millis()/1000 fallback, false = epoch/RTC absolute)
|
||||
bool isBootRelative;
|
||||
|
||||
// Delivery status (only meaningful for our own sent messages)
|
||||
AckStatus ackStatus;
|
||||
|
||||
// Default constructor to initialize all fields safely
|
||||
StoredMessage()
|
||||
: timestamp(0), sender(0), channelIndex(0), text(""), dest(0xffffffff), type(MessageType::BROADCAST),
|
||||
isBootRelative(false)
|
||||
isBootRelative(false), ackStatus(AckStatus::UNKNOWN)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
@ -233,6 +233,33 @@ const std::vector<uint32_t> &getSeenPeers()
|
||||
return seenPeers;
|
||||
}
|
||||
|
||||
// Helpers for drawing status marks
|
||||
void drawCheckMark(OLEDDisplay *display, int x, int y, int size = 8)
|
||||
{
|
||||
int h = size;
|
||||
int w = size;
|
||||
|
||||
// Center mark vertically with the text row
|
||||
int midY = y + (FONT_HEIGHT_SMALL / 2);
|
||||
int topY = midY - (h / 2);
|
||||
|
||||
display->drawLine(x, topY + h / 2, x + w / 3, topY + h);
|
||||
display->drawLine(x + w / 3, topY + h, x + w, topY);
|
||||
}
|
||||
|
||||
void drawXMark(OLEDDisplay *display, int x, int y, int size = 8)
|
||||
{
|
||||
int h = size;
|
||||
int w = size;
|
||||
|
||||
// Center mark vertically with the text row
|
||||
int midY = y + (FONT_HEIGHT_SMALL / 2);
|
||||
int topY = midY - (h / 2);
|
||||
|
||||
display->drawLine(x, topY, x + w, topY + h);
|
||||
display->drawLine(x + w, topY, x, topY + h);
|
||||
}
|
||||
|
||||
void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
// Ensure any boot-relative timestamps are upgraded if RTC is valid
|
||||
@ -329,6 +356,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
std::vector<std::string> allLines;
|
||||
std::vector<bool> isMine; // track alignment
|
||||
std::vector<bool> isHeader; // track header lines
|
||||
std::vector<AckStatus> ackForLine;
|
||||
|
||||
for (auto it = filtered.rbegin(); it != filtered.rend(); ++it) {
|
||||
const auto &m = *it;
|
||||
@ -417,6 +445,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
allLines.push_back(std::string(headerStr));
|
||||
isMine.push_back(mine);
|
||||
isHeader.push_back(true);
|
||||
ackForLine.push_back(m.ackStatus);
|
||||
|
||||
// Split message text into wrapped lines
|
||||
std::vector<std::string> wrapped = generateLines(display, "", m.text.c_str(), textWidth);
|
||||
@ -424,6 +453,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
allLines.push_back(ln);
|
||||
isMine.push_back(mine);
|
||||
isHeader.push_back(false);
|
||||
ackForLine.push_back(AckStatus::UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,6 +514,17 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
int headerX = isMine[i] ? (SCREEN_WIDTH - w - 2) : x;
|
||||
display->drawString(headerX, lineY, cachedLines[i].c_str());
|
||||
|
||||
// Draw ACK/NACK mark for our own messages
|
||||
if (isMine[i]) {
|
||||
int markX = headerX - 10;
|
||||
int markY = lineY;
|
||||
if (ackForLine[i] == AckStatus::ACKED) {
|
||||
drawCheckMark(display, markX, markY, 8);
|
||||
} else if (ackForLine[i] == AckStatus::NACKED || ackForLine[i] == AckStatus::TIMEOUT) {
|
||||
drawXMark(display, markX, markY, 8);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw underline just under header text
|
||||
int underlineY = lineY + FONT_HEIGHT_SMALL;
|
||||
for (int px = 0; px < w; ++px) {
|
||||
|
||||
@ -1008,6 +1008,7 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
|
||||
sm.dest = dest;
|
||||
sm.type = MessageType::DM_TO_US;
|
||||
}
|
||||
sm.ackStatus = AckStatus::UNKNOWN;
|
||||
|
||||
messageStore.addLiveMessage(sm);
|
||||
|
||||
@ -2185,6 +2186,20 @@ ProcessMessage CannedMessageModule::handleReceived(const meshtastic_MeshPacket &
|
||||
|
||||
waitingForAck = false;
|
||||
|
||||
// Update last sent StoredMessage with ACK/NACK result
|
||||
if (!messageStore.getMessages().empty()) {
|
||||
StoredMessage &last = const_cast<StoredMessage &>(messageStore.getMessages().back());
|
||||
if (last.sender == nodeDB->getNodeNum()) { // only update our own messages
|
||||
if (this->ack) {
|
||||
last.ackStatus = AckStatus::ACKED;
|
||||
} else {
|
||||
// If error_reason was explicit, you can map to NACKED; otherwise TIMEOUT
|
||||
last.ackStatus =
|
||||
(decoded.error_reason == meshtastic_Routing_Error_NONE) ? AckStatus::ACKED : AckStatus::NACKED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Capture radio metrics from this ACK/NACK packet
|
||||
this->lastRxRssi = mp.rx_rssi;
|
||||
this->lastRxSnr = mp.rx_snr;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user