|
Forum Index : Microcontroller and PC projects : pico can read meshtasic message by change firmware t-beam
| Author | Message | ||||
| tenij000 Regular Member Joined: 30/05/2025 Location: NetherlandsPosts: 53 |
video pico t-beam tx rx rx tx gnd gnd code use pico '---------------------------------------------- ' PicoMite Meshtastic UART reader ' Alleen text messages tonen, geen debug '---------------------------------------------- CONST MAXLEN = 255 DIM RX_MSG$ = "" ' Zet UART poort SetPin GP1, GP0, com1 ' GP1 = TX, GP0 = RX (pas aan naar jouw wiring) OPEN "COM1:115200" AS #1 PRINT "Slave PicoMite: Start. Wachten op text messages..." DO ' Lees 1 teken van de UART a$ = INPUT$(1, #1) IF a$ <> "" THEN ' Alleen ASCII-tekens toevoegen (printable) IF ASC(a$) >= 32 AND ASC(a$) <= 126 THEN RX_MSG$ = RX_MSG$ + a$ ENDIF ' Buffer overflow voorkomen IF LEN(RX_MSG$) >= MAXLEN THEN GOSUB CheckTextMessage RX_MSG$ = "" ENDIF ' Regel compleet? (newline) IF a$ = CHR$(10) OR a$ = CHR$(13) THEN IF LEN(RX_MSG$) > 0 THEN print RX_MSG$ GOSUB CheckTextMessage RX_MSG$ = "" ENDIF ENDIF ENDIF LOOP '---------------------------------------------- ' Subroutine: Filter alleen text messages '---------------------------------------------- CheckTextMessage: IF INSTR(RX_MSG$, "air_util_tx=") THEN PRINT ">>> TX Power info: "; RX_MSG$ ENDIF ' Alleen regels met "Received text msg" doorlaten IF INSTR(RX_MSG$, "Received text msg") THEN ' Optioneel: alleen het bericht extraheren tussen msg="..." msgStart = INSTR(RX_MSG$, "msg=""") IF msgStart > 0 THEN msgEnd = INSTR(msgStart + 5, RX_MSG$, """") IF msgEnd > 0 THEN message$ = MID$(RX_MSG$, msgStart + 5, msgEnd - msgStart - 5) ELSE message$ = RX_MSG$ ENDIF ELSE message$ = RX_MSG$ ENDIF PRINT ">>> Tekstbericht: "; message$ ENDIF RETURN file i change in meshtastic firmware the file Message Renderer.cpp void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { // Clear the unread message indicator when viewing the message hasUnreadMessage = false; const meshtastic_MeshPacket &mp = devicestate.rx_text_message; const char *msg = reinterpret_cast<const char *>(mp.decoded.payload.bytes); // Debug Serial.print("Ontvangen bericht: "); Serial.println(msg); display->clear(); display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); #if defined(M5STACK_UNITC6L) const int fixedTopHeight = 24; const int windowX = 0; const int windowY = fixedTopHeight; const int windowWidth = 64; const int windowHeight = SCREEN_HEIGHT - fixedTopHeight; #else const int navHeight = FONT_HEIGHT_SMALL; const int scrollBottom = SCREEN_HEIGHT - navHeight; const int usableHeight = scrollBottom; const int textWidth = SCREEN_WIDTH; #endif bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED); bool isBold = config.display.heading_bold; // === Set Title const char *titleStr = "Rob ten tije"; // Check if we have more than an empty message to show char messageBuf[237]; snprintf(messageBuf, sizeof(messageBuf), "%s", msg); if (strlen(messageBuf) == 0) { // === Header === graphics::drawCommonHeader(display, x, y, titleStr); const char *messageString = "No messages"; int center_text = (SCREEN_WIDTH / 2) - (display->getStringWidth(messageString) / 2); #if defined(M5STACK_UNITC6L) display->drawString(center_text, windowY + (windowHeight / 2) - (FONT_HEIGHT_SMALL / 2) - 5, messageString); #else display->drawString(center_text, getTextPositions(display)[2], messageString); #endif return; } // === Header Construction === meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); char headerStr[80]; const char *sender = "???"; #if defined(M5STACK_UNITC6L) if (node && node->has_user) sender = node->user.short_name; #else if (node && node->has_user) { if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) { sender = node->user.long_name; } else { sender = node->user.short_name; } } #endif uint32_t seconds = sinceReceived(&mp), minutes = seconds / 60, hours = minutes / 60, days = hours / 24; uint8_t timestampHours, timestampMinutes; int32_t daysAgo; bool useTimestamp = deltaToTimestamp(seconds, ×tampHours, ×tampMinutes, &daysAgo); if (useTimestamp && minutes >= 15 && daysAgo == 0) { std::string prefix = (daysAgo == 1 && SCREEN_WIDTH >= 200) ? "Yesterday" : "At"; if (config.display.use_12h_clock) { bool isPM = timestampHours >= 12; timestampHours = timestampHours % 12; if (timestampHours == 0) timestampHours = 12; snprintf(headerStr, sizeof(headerStr), "%s %d:%02d%s from %s", prefix.c_str(), timestampHours, timestampMinutes, isPM ? "p" : "a", sender); } else { snprintf(headerStr, sizeof(headerStr), "%s %d:%02d from %s", prefix.c_str(), timestampHours, timestampMinutes, sender); } } else { #if defined(M5STACK_UNITC6L) snprintf(headerStr, sizeof(headerStr), "%s from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(), sender); #else snprintf(headerStr, sizeof(headerStr), "%s ago from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(), sender); #endif } #if defined(M5STACK_UNITC6L) graphics::drawCommonHeader(display, x, y, titleStr); int headerY = getTextPositions(display)[1]; display->drawString(x, headerY, headerStr); for (int separatorX = 0; separatorX < SCREEN_WIDTH; separatorX += 2) { display->setPixel(separatorX, fixedTopHeight - 1); } cachedLines.clear(); std::string fullMsg(messageBuf); std::string currentLine; for (size_t i = 0; i < fullMsg.size();) { unsigned char c = fullMsg[i]; size_t charLen = 1; if ((c & 0xE0) == 0xC0) charLen = 2; else if ((c & 0xF0) == 0xE0) charLen = 3; else if ((c & 0xF8) == 0xF0) charLen = 4; std::string nextChar = fullMsg.substr(i, charLen); std::string testLine = currentLine + nextChar; if (display->getStringWidth(testLine.c_str()) > windowWidth) { cachedLines.push_back(currentLine); currentLine = nextChar; } else { currentLine = testLine; } i += charLen; } if (!currentLine.empty()) cachedLines.push_back(currentLine); cachedHeights = calculateLineHeights(cachedLines, emotes); int yOffset = windowY; int linesDrawn = 0; for (size_t i = 0; i < cachedLines.size(); ++i) { if (linesDrawn >= 2) break; int lineHeight = cachedHeights[i]; if (yOffset + lineHeight > windowY + windowHeight) break; drawStringWithEmotes(display, windowX, yOffset, cachedLines[i], emotes, numEmotes); yOffset += lineHeight; linesDrawn++; } screen->forceDisplay(); #else uint32_t now = millis(); #ifndef EXCLUDE_EMOJI // === Bounce animation setup === static uint32_t lastBounceTime = 0; static int bounceY = 0; const int bounceRange = 2; // Max pixels to bounce up/down const int bounceInterval = 10; // How quickly to change bounce direction (ms) if (now - lastBounceTime >= bounceInterval) { lastBounceTime = now; bounceY = (bounceY + 1) % (bounceRange * 2); } for (int i = 0; i < numEmotes; ++i) { const Emote &e = emotes[i]; if (strcmp(msg, e.label) == 0) { int headerY = getTextPositions(display)[1]; // same as scrolling header line display->drawString(x + 3, headerY, headerStr); if (isInverted && isBold) display->drawString(x + 4, headerY, headerStr); // Draw separator (same as scroll version) for (int separatorX = 1; separatorX <= (display->getStringWidth(headerStr) + 2); separatorX += 2) { display->setPixel(separatorX, headerY + ((isHighResolution) ? 19 : 13)); } // Center the emote below the header line + separator + nav int remainingHeight = SCREEN_HEIGHT - (headerY + FONT_HEIGHT_SMALL) - navHeight; int emoteY = headerY + 6 + FONT_HEIGHT_SMALL + (remainingHeight - e.height) / 2 + bounceY - bounceRange; display->drawXbm((SCREEN_WIDTH - e.width) / 2, emoteY, e.width, e.height, e.bitmap); // Draw header at the end to sort out overlapping elements graphics::drawCommonHeader(display, x, y, titleStr); return; } } #endif // === Generate the cache key === size_t currentKey = (size_t)mp.from; currentKey ^= ((size_t)mp.to << 8); currentKey ^= ((size_t)mp.rx_time << 16); currentKey ^= ((size_t)mp.id << 24); if (cachedKey != currentKey) { LOG_INFO("Onscreen message scroll cache key needs updating: cachedKey=0x%0x, currentKey=0x%x", cachedKey, currentKey); // Cache miss - regenerate lines and heights cachedLines = generateLines(display, headerStr, messageBuf, textWidth); cachedHeights = calculateLineHeights(cachedLines, emotes); cachedKey = currentKey; } else { // Cache hit but update the header line with current time information cachedLines[0] = std::string(headerStr); // The header always has a fixed height since it doesn't contain emotes // As per calculateLineHeights logic for lines without emotes: cachedHeights[0] = FONT_HEIGHT_SMALL - 2; if (cachedHeights[0] < 8) cachedHeights[0] = 8; // minimum safety } // === Scrolling logic === int totalHeight = 0; for (size_t i = 1; i < cachedHeights.size(); ++i) { totalHeight += cachedHeights[i]; } int usableScrollHeight = usableHeight - cachedHeights[0]; // remove header height int scrollStop = std::max(0, totalHeight - usableScrollHeight + cachedHeights.back()); static float scrollY = 0.0f; static uint32_t lastTime = 0, scrollStartDelay = 0, pauseStart = 0; static bool waitingToReset = false, scrollStarted = false; // === Smooth scrolling adjustment === // You can tweak this divisor to change how smooth it scrolls. // Lower = smoother, but can feel slow. float delta = (now - lastTime) / 400.0f; lastTime = now; const float scrollSpeed = 2.0f; // pixels per second // Delay scrolling start by 2 seconds if (scrollStartDelay == 0) scrollStartDelay = now; if (!scrollStarted && now - scrollStartDelay > 2000) scrollStarted = true; if (totalHeight > usableScrollHeight) { if (scrollStarted) { if (!waitingToReset) { scrollY += delta * scrollSpeed; if (scrollY >= scrollStop) { scrollY = scrollStop; waitingToReset = true; pauseStart = lastTime; } } else if (lastTime - pauseStart > 3000) { scrollY = 0; waitingToReset = false; scrollStarted = false; scrollStartDelay = lastTime; } } } else { scrollY = 0; } int scrollOffset = static_cast<int>(scrollY); int yOffset = -scrollOffset + getTextPositions(display)[1]; for (int separatorX = 1; separatorX <= (display->getStringWidth(headerStr) + 2); separatorX += 2) { display->setPixel(separatorX, yOffset + ((isHighResolution) ? 19 : 13)); } // === Render visible lines === renderMessageContent(display, cachedLines, cachedHeights, x, yOffset, scrollBottom, emotes, numEmotes, isInverted, isBold); // Draw header at the end to sort out overlapping elements graphics::drawCommonHeader(display, x, y, titleStr); #endif } std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth) { std::vector<std::string> lines; lines.push_back(std::string(headerStr)); // Header line is always first std::string line, word; for (int i = 0; messageBuf[i]; ++i) { char ch = messageBuf[i]; if ((unsigned char)messageBuf[i] == 0xE2 && (unsigned char)messageBuf[i + 1] == 0x80 && (unsigned char)messageBuf[i + 2] == 0x99) { ch = '\''; // plain apostrophe i += 2; // skip over the extra UTF-8 bytes } if (ch == '\n') { if (!word.empty()) line += word; if (!line.empty()) lines.push_back(line); line.clear(); word.clear(); } else if (ch == ' ') { line += word + ' '; word.clear(); } else { word += ch; std::string test = line + word; // Keep these lines for diagnostics // LOG_INFO("Char: '%c' (0x%02X)", ch, (unsigned char)ch); // LOG_INFO("Current String: %s", test.c_str()); // Note: there are boolean comparison uint16 (getStringWidth) with int (textWidth), hope textWidth is always positive :) #if defined(OLED_UA) || defined(OLED_RU) uint16_t strWidth = display->getStringWidth(test.c_str(), test.length(), true); #else uint16_t strWidth = display->getStringWidth(test.c_str()); #endif if (strWidth > textWidth) { if (!line.empty()) lines.push_back(line); line = word; word.clear(); } } } if (!word.empty()) line += word; if (!line.empty()) lines.push_back(line); return lines; } std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, const Emote *emotes) { std::vector<int> rowHeights; for (const auto &_line : lines) { int lineHeight = FONT_HEIGHT_SMALL; bool hasEmote = false; for (int i = 0; i < numEmotes; ++i) { const Emote &e = emotes[i]; if (_line.find(e.label) != std::string::npos) { lineHeight = std::max(lineHeight, e.height); hasEmote = true; } } // Apply tighter spacing if no emotes on this line if (!hasEmote) { lineHeight -= 2; // reduce by 2px for tighter spacing if (lineHeight < 8) lineHeight = 8; // minimum safety } rowHeights.push_back(lineHeight); } return rowHeights; } Edited 2025-09-21 04:33 by tenij000 |
||||
| Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 5398 |
Interesting technology... With an 8 node network like that you could cover the whole of Netherlands. Wauw, up to 205 miles between 2 nodes. Volhout Edited 2025-09-21 04:46 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
| The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2025 |