Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 23:13 28 Oct 2025 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

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: Netherlands
Posts: 53
Posted: 05:53pm 20 Sep 2025
Copy link to clipboard 
Print this post

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: Netherlands
Posts: 5398
Posted: 06:45pm 20 Sep 2025
Copy link to clipboard 
Print this post

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
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2025