diff --git a/bin/device-install.sh b/bin/device-install.sh index 1778a952d..49427524e 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -32,6 +32,19 @@ if ! command -v jq >/dev/null 2>&1; then exit 1 fi +# esptool v5 supports commands with dashes and deprecates commands with +# underscores. Prior versions only support commands with underscores +if ${ESPTOOL_CMD} | grep --quiet write-flash +then + ESPTOOL_WRITE_FLASH=write-flash + ESPTOOL_ERASE_FLASH=erase-flash + ESPTOOL_READ_FLASH_STATUS=read-flash-status +else + ESPTOOL_WRITE_FLASH=write_flash + ESPTOOL_ERASE_FLASH=erase_flash + ESPTOOL_READ_FLASH_STATUS=read_flash_status +fi + set -e # Usage info @@ -83,8 +96,8 @@ while [ $# -gt 0 ]; do done if [[ $BPS_RESET == true ]]; then - $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status - exit 0 + $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS} + exit 0 fi [ -z "$FILENAME" ] && [ -n "$1" ] && { @@ -144,12 +157,12 @@ if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then fi echo "Trying to flash ${FILENAME}, but first erasing and writing system information" - $ESPTOOL_CMD erase-flash - $ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}" + $ESPTOOL_CMD ${ESPTOOL_ERASE_FLASH} + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $FIRMWARE_OFFSET "${FILENAME}" echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}" - $ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}" + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OTA_OFFSET "${OTAFILE}" echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}" - $ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}" + $ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OFFSET "${SPIFFSFILE}" else show_help diff --git a/bin/device-update.sh b/bin/device-update.sh index 1c3d6be70..10eb5eedd 100755 --- a/bin/device-update.sh +++ b/bin/device-update.sh @@ -20,6 +20,17 @@ else exit 1 fi +# esptool v5 supports commands with dashes and deprecates commands with +# underscores. Prior versions only support commands with underscores +if ${ESPTOOL_CMD} | grep --quiet write-flash +then + ESPTOOL_WRITE_FLASH=write-flash + ESPTOOL_READ_FLASH_STATUS=read-flash-status +else + ESPTOOL_WRITE_FLASH=write_flash + ESPTOOL_READ_FLASH_STATUS=read_flash_status +fi + # Usage info show_help() { cat << EOF @@ -69,7 +80,7 @@ done shift "$((OPTIND-1))" if [ "$CHANGE_MODE" = true ]; then - $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status + $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS} exit 0 fi @@ -80,7 +91,7 @@ fi if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then echo "Trying to flash update ${FILENAME}" - $ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}" + $ESPTOOL_CMD --baud $FLASH_BAUD ${ESPTOOL_WRITE_FLASH} $UPDATE_OFFSET "${FILENAME}" else show_help echo "Invalid file: ${FILENAME}" diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index cb8985ee6..6ad8962d1 100644 --- a/bin/org.meshtastic.meshtasticd.metainfo.xml +++ b/bin/org.meshtastic.meshtasticd.metainfo.xml @@ -87,6 +87,9 @@ + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.19 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18 diff --git a/debian/changelog b/debian/changelog index 5f25d53ad..38489b074 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +meshtasticd (2.7.19.0) unstable; urgency=medium + + * Version 2.7.19 + + -- GitHub Actions Thu, 22 Jan 2026 22:17:40 +0000 + meshtasticd (2.7.18.0) unstable; urgency=medium * Version 2.7.18 diff --git a/monitor/filter_c3_exception_decoder.py b/monitor/filter_c3_exception_decoder.py index 5e74dc2b9..fbc372bcf 100644 --- a/monitor/filter_c3_exception_decoder.py +++ b/monitor/filter_c3_exception_decoder.py @@ -43,13 +43,11 @@ class Esp32C3ExceptionDecoder(DeviceMonitorFilterBase): self.enabled = self.setup_paths() if self.config.get("env:" + self.environment, "build_type") != "debug": - print( - """ + print(""" Please build project in debug configuration to get more details about an exception. See https://docs.platformio.org/page/projectconf/build_configurations.html -""" - ) +""") return self diff --git a/protobufs b/protobufs index d9003b2b6..77c8329a5 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit d9003b2b6c9f378066979ec83b013aca441cf240 +Subproject commit 77c8329a59a9c96a61c447b5d5f1a52ca583e4f2 diff --git a/src/configuration.h b/src/configuration.h index 59bffe7be..f7b438272 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -155,6 +155,10 @@ along with this program. If not, see . #endif // Default system gain to 0 if not defined +#ifndef NUM_PA_POINTS +#define NUM_PA_POINTS 1 +#endif + #ifndef TX_GAIN_LORA #define TX_GAIN_LORA 0 #endif diff --git a/src/graphics/draw/MessageRenderer.cpp b/src/graphics/draw/MessageRenderer.cpp index 09b798e06..72746e415 100644 --- a/src/graphics/draw/MessageRenderer.cpp +++ b/src/graphics/draw/MessageRenderer.cpp @@ -6,7 +6,6 @@ #include "MessageStore.h" #include "NodeDB.h" #include "UIRenderer.h" -#include "configuration.h" #include "gps/RTC.h" #include "graphics/Screen.h" #include "graphics/ScreenFonts.h" @@ -20,7 +19,6 @@ // External declarations extern bool hasUnreadMessage; -extern meshtastic_DeviceState devicestate; extern graphics::Screen *screen; using graphics::Emote; @@ -49,7 +47,7 @@ static inline size_t utf8CharLen(uint8_t c) } // Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels -std::string normalizeEmoji(const std::string &s) +static std::string normalizeEmoji(const std::string &s) { std::string out; for (size_t i = 0; i < s.size();) { @@ -82,6 +80,7 @@ uint32_t pauseStart = 0; bool waitingToReset = false; bool scrollStarted = false; static bool didReset = false; +static constexpr int MESSAGE_BLOCK_GAP = 6; void scrollUp() { @@ -111,22 +110,6 @@ void scrollDown() void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount) { - std::string renderLine; - for (size_t i = 0; i < line.size();) { - uint8_t c = (uint8_t)line[i]; - size_t len = utf8CharLen(c); - if (c == 0xEF && i + 2 < line.size() && (uint8_t)line[i + 1] == 0xB8 && (uint8_t)line[i + 2] == 0x8F) { - i += 3; - continue; - } - if (c == 0xF0 && i + 3 < line.size() && (uint8_t)line[i + 1] == 0x9F && (uint8_t)line[i + 2] == 0x8F && - ((uint8_t)line[i + 3] >= 0xBB && (uint8_t)line[i + 3] <= 0xBF)) { - i += 4; - continue; - } - renderLine.append(line, i, len); - i += len; - } int cursorX = x; const int fontHeight = FONT_HEIGHT_SMALL; @@ -203,8 +186,7 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string // Render the emote (if found) if (matchedEmote && i == nextEmotePos) { - // Vertically center emote relative to font baseline (not just midline) - int iconY = fontY + (fontHeight - matchedEmote->height) / 2; + int iconY = y + (lineHeight - matchedEmote->height) / 2; display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap); cursorX += matchedEmote->width + 1; i += emojiLen; @@ -423,6 +405,102 @@ static inline int getRenderedLineWidth(OLEDDisplay *display, const std::string & return totalWidth; } +struct MessageBlock { + size_t start; + size_t end; + bool mine; +}; + +static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool isHeaderLine) +{ + if (isHeaderLine) { + return lineTopY + (FONT_HEIGHT_SMALL - 1); + } + + int tallest = FONT_HEIGHT_SMALL; + for (int e = 0; e < numEmotes; ++e) { + if (line.find(emotes[e].label) != std::string::npos) { + if (emotes[e].height > tallest) + tallest = emotes[e].height; + } + } + + const int lineHeight = std::max(FONT_HEIGHT_SMALL, tallest); + const int iconTop = lineTopY + (lineHeight - tallest) / 2; + + return iconTop + tallest - 1; +} + +static void drawRoundedRectOutline(OLEDDisplay *display, int x, int y, int w, int h, int r) +{ + if (w <= 1 || h <= 1) + return; + + if (r < 0) + r = 0; + + int maxR = (std::min(w, h) / 2) - 1; + if (r > maxR) + r = maxR; + + if (r == 0) { + display->drawRect(x, y, w, h); + return; + } + + const int x0 = x; + const int y0 = y; + const int x1 = x + w - 1; + const int y1 = y + h - 1; + + // sides + if (x0 + r <= x1 - r) { + display->drawLine(x0 + r, y0, x1 - r, y0); // top + display->drawLine(x0 + r, y1, x1 - r, y1); // bottom + } + if (y0 + r <= y1 - r) { + display->drawLine(x0, y0 + r, x0, y1 - r); // left + display->drawLine(x1, y0 + r, x1, y1 - r); // right + } + + // corner arcs + display->drawCircleQuads(x0 + r, y0 + r, r, 2); // top left + display->drawCircleQuads(x1 - r, y0 + r, r, 1); // top right + display->drawCircleQuads(x1 - r, y1 - r, r, 8); // bottom right + display->drawCircleQuads(x0 + r, y1 - r, r, 4); // bottom left +} + +static std::vector buildMessageBlocks(const std::vector &isHeaderVec, const std::vector &isMineVec) +{ + std::vector blocks; + if (isHeaderVec.empty()) + return blocks; + + size_t start = 0; + bool mine = isMineVec[0]; + + for (size_t i = 1; i < isHeaderVec.size(); ++i) { + if (isHeaderVec[i]) { + MessageBlock b; + b.start = start; + b.end = i - 1; + b.mine = mine; + blocks.push_back(b); + + start = i; + mine = isMineVec[i]; + } + } + + MessageBlock last; + last.start = start; + last.end = isHeaderVec.size() - 1; + last.mine = mine; + blocks.push_back(last); + + return blocks; +} + static void drawMessageScrollbar(OLEDDisplay *display, int visibleHeight, int totalHeight, int scrollOffset, int startY) { if (totalHeight <= visibleHeight) @@ -482,9 +560,14 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 constexpr int LEFT_MARGIN = 2; constexpr int RIGHT_MARGIN = 2; constexpr int SCROLLBAR_WIDTH = 3; + constexpr int BUBBLE_PAD_X = 3; + constexpr int BUBBLE_PAD_Y = 4; + constexpr int BUBBLE_RADIUS = 4; + constexpr int BUBBLE_MIN_W = 24; + constexpr int BUBBLE_TEXT_INDENT = 2; - const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN; - + // Derived widths + const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (BUBBLE_PAD_X * 2); const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH; // Title string depending on mode @@ -547,7 +630,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 char chanType[32] = ""; if (currentMode == ThreadMode::ALL) { if (m.dest == NODENUM_BROADCAST) { - snprintf(chanType, sizeof(chanType), "#%s", channels.getName(m.channelIndex)); + const char *name = channels.getName(m.channelIndex); + if (currentResolution == ScreenResolution::Low || currentResolution == ScreenResolution::UltraLow) { + if (strcmp(name, "ShortTurbo") == 0) + name = "ShortT"; + else if (strcmp(name, "ShortSlow") == 0) + name = "ShortS"; + else if (strcmp(name, "ShortFast") == 0) + name = "ShortF"; + else if (strcmp(name, "MediumSlow") == 0) + name = "MedS"; + else if (strcmp(name, "MediumFast") == 0) + name = "MedF"; + else if (strcmp(name, "LongSlow") == 0) + name = "LongS"; + else if (strcmp(name, "LongFast") == 0) + name = "LongF"; + else if (strcmp(name, "LongTurbo") == 0) + name = "LongT"; + else if (strcmp(name, "LongMod") == 0) + name = "LongM"; + } + snprintf(chanType, sizeof(chanType), "#%s", name); } else { snprintf(chanType, sizeof(chanType), "(DM)"); } @@ -614,8 +718,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 } // Shrink Sender name if needed - int availWidth = SCREEN_WIDTH - display->getStringWidth(timeBuf) - display->getStringWidth(chanType) - - display->getStringWidth(" @...") - 10; + int availWidth = (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) - + display->getStringWidth(chanType) - display->getStringWidth(" @..."); if (availWidth < 0) availWidth = 0; @@ -667,6 +771,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 cachedLines = allLines; cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader); + std::vector blocks = buildMessageBlocks(isHeader, isMine); + // Scrolling logic (unchanged) int totalHeight = 0; for (size_t i = 0; i < cachedHeights.size(); ++i) @@ -714,12 +820,123 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 int finalScroll = (int)scrollY; int yOffset = -finalScroll + getTextPositions(display)[1]; + const int contentTop = getTextPositions(display)[1]; + const int contentBottom = scrollBottom; // already excludes nav line + const int rightEdge = SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN; + const int bubbleGapY = std::max(1, MESSAGE_BLOCK_GAP / 2); + + std::vector lineTop; + lineTop.resize(cachedLines.size()); + { + int acc = 0; + for (size_t i = 0; i < cachedLines.size(); ++i) { + lineTop[i] = yOffset + acc; + acc += cachedHeights[i]; + } + } + + // Draw bubbles + for (size_t bi = 0; bi < blocks.size(); ++bi) { + const auto &b = blocks[bi]; + if (b.start >= cachedLines.size() || b.end >= cachedLines.size() || b.start > b.end) + continue; + + int visualTop = lineTop[b.start]; + + int topY; + if (isHeader[b.start]) { + // Header start + constexpr int BUBBLE_PAD_TOP_HEADER = 1; // try 1 or 2 + topY = visualTop - BUBBLE_PAD_TOP_HEADER; + } else { + // Body start + bool thisLineHasEmote = false; + for (int e = 0; e < numEmotes; ++e) { + if (cachedLines[b.start].find(emotes[e].label) != std::string::npos) { + thisLineHasEmote = true; + break; + } + } + if (thisLineHasEmote) { + constexpr int EMOTE_PADDING_ABOVE = 4; + visualTop -= EMOTE_PADDING_ABOVE; + } + topY = visualTop - BUBBLE_PAD_Y; + } + int visualBottom = getDrawnLinePixelBottom(lineTop[b.end], cachedLines[b.end], isHeader[b.end]); + int bottomY = visualBottom + BUBBLE_PAD_Y; + + if (bi + 1 < blocks.size()) { + int nextHeaderIndex = (int)blocks[bi + 1].start; + int nextTop = lineTop[nextHeaderIndex]; + int maxBottom = nextTop - 1 - bubbleGapY; + if (bottomY > maxBottom) + bottomY = maxBottom; + } + + if (bottomY <= topY + 2) + continue; + + if (bottomY < contentTop || topY > contentBottom - 1) + continue; + + int maxLineW = 0; + + for (size_t i = b.start; i <= b.end; ++i) { + int w = 0; + if (isHeader[i]) { + w = display->getStringWidth(cachedLines[i].c_str()); + if (b.mine) + w += 12; // room for ACK/NACK/relay mark + } else { + w = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); + } + if (w > maxLineW) + maxLineW = w; + } + + int bubbleW = std::max(BUBBLE_MIN_W, maxLineW + (BUBBLE_PAD_X * 2)); + int bubbleH = (bottomY - topY) + 1; + int bubbleX = 0; + if (b.mine) { + bubbleX = rightEdge - bubbleW; + } else { + bubbleX = x; + } + if (bubbleX < x) + bubbleX = x; + if (bubbleX + bubbleW > rightEdge) + bubbleW = std::max(1, rightEdge - bubbleX); + + if (bubbleW > 1 && bubbleH > 1) { + int r = BUBBLE_RADIUS; + int maxR = (std::min(bubbleW, bubbleH) / 2) - 1; + if (maxR < 0) + maxR = 0; + if (r > maxR) + r = maxR; + + drawRoundedRectOutline(display, bubbleX, topY, bubbleW, bubbleH, r); + const int extra = 3; + const int rr = r + extra; + int x1 = bubbleX + bubbleW - 1; + int y1 = topY + bubbleH - 1; + + if (!b.mine) { + // top-left corner square + display->drawLine(bubbleX, topY, bubbleX + rr, topY); + display->drawLine(bubbleX, topY, bubbleX, topY + rr); + } else { + // bottom-right corner square + display->drawLine(x1 - rr, y1, x1, y1); + display->drawLine(x1, y1 - rr, x1, y1); + } + } + } // Render visible lines + int lineY = yOffset; for (size_t i = 0; i < cachedLines.size(); ++i) { - int lineY = yOffset; - for (size_t j = 0; j < i; ++j) - lineY += cachedHeights[j]; if (lineY > -cachedHeights[i] && lineY < scrollBottom) { if (isHeader[i]) { @@ -728,14 +945,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 int headerX; if (isMine[i]) { // push header left to avoid overlap with scrollbar - headerX = SCREEN_WIDTH - w - SCROLLBAR_WIDTH - RIGHT_MARGIN; + headerX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - w - BUBBLE_TEXT_INDENT; if (headerX < LEFT_MARGIN) headerX = LEFT_MARGIN; } else { - headerX = x; + headerX = x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT; } display->drawString(headerX, lineY, cachedLines[i].c_str()); + // Draw underline just under header text + int underlineY = lineY + FONT_HEIGHT_SMALL; + + int underlineW = w; + int maxW = rightEdge - headerX; + if (maxW < 0) + maxW = 0; + if (underlineW > maxW) + underlineW = maxW; + + for (int px = 0; px < underlineW; ++px) { + display->setPixel(headerX + px, underlineY); + } + // Draw ACK/NACK mark for our own messages if (isMine[i]) { int markX = headerX - 10; @@ -753,32 +984,28 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // AckStatus::NONE → show nothing } - // Draw underline just under header text - int underlineY = lineY + FONT_HEIGHT_SMALL; - for (int px = 0; px < w; ++px) { - display->setPixel(headerX + px, underlineY); - } } else { // Render message line if (isMine[i]) { // Calculate actual rendered width including emotes int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); - int rightX = SCREEN_WIDTH - renderedWidth - SCROLLBAR_WIDTH - RIGHT_MARGIN; + int rightX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - renderedWidth - BUBBLE_TEXT_INDENT; if (rightX < LEFT_MARGIN) rightX = LEFT_MARGIN; drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes); } else { - drawStringWithEmotes(display, x, lineY, cachedLines[i], emotes, numEmotes); + drawStringWithEmotes(display, x + BUBBLE_PAD_X + BUBBLE_TEXT_INDENT, lineY, cachedLines[i], emotes, + numEmotes); } } } + + lineY += cachedHeights[i]; } - int totalContentHeight = totalHeight; - int visibleHeight = usableHeight; // Draw scrollbar - drawMessageScrollbar(display, visibleHeight, totalContentHeight, finalScroll, getTextPositions(display)[1]); + drawMessageScrollbar(display, usableHeight, totalHeight, finalScroll, getTextPositions(display)[1]); graphics::drawCommonHeader(display, x, y, titleStr); graphics::drawCommonFooter(display, x, y); } @@ -841,7 +1068,6 @@ std::vector calculateLineHeights(const std::vector &lines, con constexpr int HEADER_UNDERLINE_GAP = 0; // space between underline and first body line constexpr int HEADER_UNDERLINE_PIX = 1; // underline thickness (1px row drawn) constexpr int BODY_LINE_LEADING = -4; // default vertical leading for normal body lines - constexpr int MESSAGE_BLOCK_GAP = 4; // gap after a message block before a new header constexpr int EMOTE_PADDING_ABOVE = 4; // space above emote line (added to line above) constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line) @@ -851,6 +1077,7 @@ std::vector calculateLineHeights(const std::vector &lines, con for (size_t idx = 0; idx < lines.size(); ++idx) { const auto &line = lines[idx]; const int baseHeight = FONT_HEIGHT_SMALL; + int lineHeight = baseHeight; // Detect if THIS line or NEXT line contains an emote bool hasEmote = false; @@ -872,8 +1099,6 @@ std::vector calculateLineHeights(const std::vector &lines, con } } - int lineHeight = baseHeight; - if (isHeaderVec[idx]) { // Header line spacing lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP; @@ -922,7 +1147,7 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht // Banner logic const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from); - char longName[48] = "???"; + char longName[48] = "?"; if (node && node->user.long_name) { strncpy(longName, node->user.long_name, sizeof(longName) - 1); longName[sizeof(longName) - 1] = '\0'; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index aaaca719e..d0097a46a 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -666,18 +666,24 @@ void RadioInterface::limitPower(int8_t loraMaxPower) power = maxPower; } -#ifndef NUM_PA_POINTS - if (TX_GAIN_LORA > 0 && !devicestate.owner.is_licensed) { - LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, TX_GAIN_LORA); - power -= TX_GAIN_LORA; - } +#ifdef ARCH_PORTDUINO + size_t num_pa_points = portduino_config.num_pa_points; + const uint16_t *tx_gain = portduino_config.tx_gain_lora; #else - if (!devicestate.owner.is_licensed) { + size_t num_pa_points = NUM_PA_POINTS; + const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA}; +#endif + + if (num_pa_points == 1) { + if (tx_gain[0] > 0 && !devicestate.owner.is_licensed) { + LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[0]); + power -= tx_gain[0]; + } + } else if (!devicestate.owner.is_licensed) { // we have an array of PA gain values. Find the highest power setting that works. - const uint16_t tx_gain[NUM_PA_POINTS] = {TX_GAIN_LORA}; - for (int radio_dbm = 0; radio_dbm < NUM_PA_POINTS; radio_dbm++) { + for (int radio_dbm = 0; radio_dbm < num_pa_points; radio_dbm++) { if (((radio_dbm + tx_gain[radio_dbm]) > power) || - ((radio_dbm == (NUM_PA_POINTS - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { + ((radio_dbm == (num_pa_points - 1)) && ((radio_dbm + tx_gain[radio_dbm]) <= power))) { // we've exceeded the power limit, or hit the max we can do LOG_INFO("Requested Tx power: %d dBm; Device LoRa Tx gain: %d dB", power, tx_gain[radio_dbm]); power -= tx_gain[radio_dbm]; @@ -685,7 +691,7 @@ void RadioInterface::limitPower(int8_t loraMaxPower) } } } -#endif + if (power > loraMaxPower) // Clamp power to maximum defined level power = loraMaxPower; diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h index 26b4343e9..efdead91b 100644 --- a/src/mesh/generated/meshtastic/admin.pb.h +++ b/src/mesh/generated/meshtastic/admin.pb.h @@ -77,7 +77,9 @@ typedef enum _meshtastic_AdminMessage_ModuleConfigType { /* TODO: REPLACE */ meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG = 11, /* TODO: REPLACE */ - meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12 + meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG = 12, + /* TODO: REPLACE */ + meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG = 13 } meshtastic_AdminMessage_ModuleConfigType; typedef enum _meshtastic_AdminMessage_BackupLocation { @@ -323,8 +325,8 @@ extern "C" { #define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1)) #define _meshtastic_AdminMessage_ModuleConfigType_MIN meshtastic_AdminMessage_ModuleConfigType_MQTT_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG -#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG+1)) +#define _meshtastic_AdminMessage_ModuleConfigType_MAX meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG +#define _meshtastic_AdminMessage_ModuleConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ModuleConfigType)(meshtastic_AdminMessage_ModuleConfigType_STATUSMESSAGE_CONFIG+1)) #define _meshtastic_AdminMessage_BackupLocation_MIN meshtastic_AdminMessage_BackupLocation_FLASH #define _meshtastic_AdminMessage_BackupLocation_MAX meshtastic_AdminMessage_BackupLocation_SD diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index 409805d24..57e7df8fc 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -361,7 +361,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; /* Maximum encoded size of messages (where known) */ /* meshtastic_NodeDatabase_size depends on runtime parameters */ #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size -#define meshtastic_BackupPreferences_size 2279 +#define meshtastic_BackupPreferences_size 2362 #define meshtastic_ChannelFile_size 718 #define meshtastic_DeviceState_size 1737 #define meshtastic_NodeInfoLite_size 196 diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h index 2b44d0c9a..f11b13419 100644 --- a/src/mesh/generated/meshtastic/localonly.pb.h +++ b/src/mesh/generated/meshtastic/localonly.pb.h @@ -87,6 +87,9 @@ typedef struct _meshtastic_LocalModuleConfig { /* Paxcounter Config */ bool has_paxcounter; meshtastic_ModuleConfig_PaxcounterConfig paxcounter; + /* StatusMessage Config */ + bool has_statusmessage; + meshtastic_ModuleConfig_StatusMessageConfig statusmessage; } meshtastic_LocalModuleConfig; @@ -96,9 +99,9 @@ extern "C" { /* Initializer values for message structs */ #define meshtastic_LocalConfig_init_default {false, meshtastic_Config_DeviceConfig_init_default, false, meshtastic_Config_PositionConfig_init_default, false, meshtastic_Config_PowerConfig_init_default, false, meshtastic_Config_NetworkConfig_init_default, false, meshtastic_Config_DisplayConfig_init_default, false, meshtastic_Config_LoRaConfig_init_default, false, meshtastic_Config_BluetoothConfig_init_default, 0, false, meshtastic_Config_SecurityConfig_init_default} -#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default} +#define meshtastic_LocalModuleConfig_init_default {false, meshtastic_ModuleConfig_MQTTConfig_init_default, false, meshtastic_ModuleConfig_SerialConfig_init_default, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_default, false, meshtastic_ModuleConfig_StoreForwardConfig_init_default, false, meshtastic_ModuleConfig_RangeTestConfig_init_default, false, meshtastic_ModuleConfig_TelemetryConfig_init_default, false, meshtastic_ModuleConfig_CannedMessageConfig_init_default, 0, false, meshtastic_ModuleConfig_AudioConfig_init_default, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_default, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_default, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_default, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_default, false, meshtastic_ModuleConfig_PaxcounterConfig_init_default, false, meshtastic_ModuleConfig_StatusMessageConfig_init_default} #define meshtastic_LocalConfig_init_zero {false, meshtastic_Config_DeviceConfig_init_zero, false, meshtastic_Config_PositionConfig_init_zero, false, meshtastic_Config_PowerConfig_init_zero, false, meshtastic_Config_NetworkConfig_init_zero, false, meshtastic_Config_DisplayConfig_init_zero, false, meshtastic_Config_LoRaConfig_init_zero, false, meshtastic_Config_BluetoothConfig_init_zero, 0, false, meshtastic_Config_SecurityConfig_init_zero} -#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero} +#define meshtastic_LocalModuleConfig_init_zero {false, meshtastic_ModuleConfig_MQTTConfig_init_zero, false, meshtastic_ModuleConfig_SerialConfig_init_zero, false, meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero, false, meshtastic_ModuleConfig_StoreForwardConfig_init_zero, false, meshtastic_ModuleConfig_RangeTestConfig_init_zero, false, meshtastic_ModuleConfig_TelemetryConfig_init_zero, false, meshtastic_ModuleConfig_CannedMessageConfig_init_zero, 0, false, meshtastic_ModuleConfig_AudioConfig_init_zero, false, meshtastic_ModuleConfig_RemoteHardwareConfig_init_zero, false, meshtastic_ModuleConfig_NeighborInfoConfig_init_zero, false, meshtastic_ModuleConfig_AmbientLightingConfig_init_zero, false, meshtastic_ModuleConfig_DetectionSensorConfig_init_zero, false, meshtastic_ModuleConfig_PaxcounterConfig_init_zero, false, meshtastic_ModuleConfig_StatusMessageConfig_init_zero} /* Field tags (for use in manual encoding/decoding) */ #define meshtastic_LocalConfig_device_tag 1 @@ -124,6 +127,7 @@ extern "C" { #define meshtastic_LocalModuleConfig_ambient_lighting_tag 12 #define meshtastic_LocalModuleConfig_detection_sensor_tag 13 #define meshtastic_LocalModuleConfig_paxcounter_tag 14 +#define meshtastic_LocalModuleConfig_statusmessage_tag 15 /* Struct field encoding specification for nanopb */ #define meshtastic_LocalConfig_FIELDLIST(X, a) \ @@ -161,7 +165,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, remote_hardware, 10) \ X(a, STATIC, OPTIONAL, MESSAGE, neighbor_info, 11) \ X(a, STATIC, OPTIONAL, MESSAGE, ambient_lighting, 12) \ X(a, STATIC, OPTIONAL, MESSAGE, detection_sensor, 13) \ -X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) +X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) \ +X(a, STATIC, OPTIONAL, MESSAGE, statusmessage, 15) #define meshtastic_LocalModuleConfig_CALLBACK NULL #define meshtastic_LocalModuleConfig_DEFAULT NULL #define meshtastic_LocalModuleConfig_mqtt_MSGTYPE meshtastic_ModuleConfig_MQTTConfig @@ -177,6 +182,7 @@ X(a, STATIC, OPTIONAL, MESSAGE, paxcounter, 14) #define meshtastic_LocalModuleConfig_ambient_lighting_MSGTYPE meshtastic_ModuleConfig_AmbientLightingConfig #define meshtastic_LocalModuleConfig_detection_sensor_MSGTYPE meshtastic_ModuleConfig_DetectionSensorConfig #define meshtastic_LocalModuleConfig_paxcounter_MSGTYPE meshtastic_ModuleConfig_PaxcounterConfig +#define meshtastic_LocalModuleConfig_statusmessage_MSGTYPE meshtastic_ModuleConfig_StatusMessageConfig extern const pb_msgdesc_t meshtastic_LocalConfig_msg; extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; @@ -186,9 +192,9 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg; #define meshtastic_LocalModuleConfig_fields &meshtastic_LocalModuleConfig_msg /* Maximum encoded size of messages (where known) */ -#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size +#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size #define meshtastic_LocalConfig_size 749 -#define meshtastic_LocalModuleConfig_size 675 +#define meshtastic_LocalModuleConfig_size 758 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp index 4f7fb4776..d41c7ba0e 100644 --- a/src/platform/nrf52/NRF52Bluetooth.cpp +++ b/src/platform/nrf52/NRF52Bluetooth.cpp @@ -240,6 +240,14 @@ int NRF52Bluetooth::getRssi() { return 0; // FIXME figure out where to source this } + +// Valid BLE TX power levels as per nRF52840 Product Specification are: "-20 to +8 dBm TX power, configurable in 4 dB steps". +// See https://docs.nordicsemi.com/bundle/ps_nrf52840/page/keyfeatures_html5.html +#define VALID_BLE_TX_POWER(x) \ + ((x) == -20 || (x) == -16 || (x) == -12 || \ + (x) == -8 || (x) == -4 || (x) == 0 || \ + (x) == 4 || (x) == 8) + void NRF52Bluetooth::setup() { // Initialise the Bluefruit module @@ -251,6 +259,9 @@ void NRF52Bluetooth::setup() Bluefruit.Advertising.stop(); Bluefruit.Advertising.clearData(); Bluefruit.ScanResponse.clearData(); +#if defined(NRF52_BLE_TX_POWER) && VALID_BLE_TX_POWER(NRF52_BLE_TX_POWER) + Bluefruit.setTxPower(NRF52_BLE_TX_POWER); +#endif if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) { configuredPasskey = config.bluetooth.mode == meshtastic_Config_BluetoothConfig_PairingMode_FIXED_PIN ? config.bluetooth.fixed_pin diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index ec9bbedca..9159d5954 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -61,11 +61,12 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) { switch (key) { case 'p': - if (sscanf(arg, "%d", &TCPPort) < 1) + if (sscanf(arg, "%d", &TCPPort) < 1) { return ARGP_ERR_UNKNOWN; - else + } else { checkConfigPort = false; printf("Using config file %d\n", TCPPort); + } break; case 'c': configPath = arg; @@ -649,6 +650,19 @@ bool loadConfig(const char *configPath) if (yamlConfig["Lora"]["RF95_MAX_POWER"]) portduino_config.rf95_max_power = yamlConfig["Lora"]["RF95_MAX_POWER"].as(20); + if (yamlConfig["Lora"]["TX_GAIN_LORA"]) { + YAML::Node tx_gain_node = yamlConfig["Lora"]["TX_GAIN_LORA"]; + if (tx_gain_node.IsSequence() && tx_gain_node.size() != 0) { + portduino_config.num_pa_points = min(tx_gain_node.size(), std::size(portduino_config.tx_gain_lora)); + for (int i = 0; i < portduino_config.num_pa_points; i++) { + portduino_config.tx_gain_lora[i] = tx_gain_node[i].as(); + } + } else { + portduino_config.num_pa_points = 1; + portduino_config.tx_gain_lora[0] = tx_gain_node.as(0); + } + } + if (portduino_config.lora_module != use_autoconf && portduino_config.lora_module != use_simradio && !portduino_config.force_simradio) { portduino_config.dio2_as_rf_switch = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as(false); @@ -874,10 +888,8 @@ bool loadConfig(const char *configPath) } if (checkConfigPort) { portduino_config.api_port = (yamlConfig["General"]["APIPort"]).as(-1); - if (portduino_config.api_port != -1 && - portduino_config.api_port > 1023 && - portduino_config.api_port < 65536) { - TCPPort = (portduino_config.api_port); + if (portduino_config.api_port != -1 && portduino_config.api_port > 1023 && portduino_config.api_port < 65536) { + TCPPort = (portduino_config.api_port); } } portduino_config.mac_address = (yamlConfig["General"]["MACAddress"]).as(""); diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index 3a6887421..eb4eb8925 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -91,6 +91,8 @@ extern struct portduino_config_struct { int lora_usb_pid = 0x5512; int lora_usb_vid = 0x1A86; int spiSpeed = 2000000; + int num_pa_points = 1; // default to 1 point, with 0 gain + uint16_t tx_gain_lora[22] = {0}; pinMapping lora_cs_pin = {"Lora", "CS"}; pinMapping lora_irq_pin = {"Lora", "IRQ"}; pinMapping lora_busy_pin = {"Lora", "Busy"}; @@ -231,6 +233,17 @@ extern struct portduino_config_struct { out << YAML::Key << "LR1120_MAX_POWER" << YAML::Value << lr1120_max_power; if (rf95_max_power != 20) out << YAML::Key << "RF95_MAX_POWER" << YAML::Value << rf95_max_power; + + if (num_pa_points > 1) { + out << YAML::Key << "TX_GAIN_LORA" << YAML::Value << YAML::Flow << YAML::BeginSeq; + for (int i = 0; i < num_pa_points; i++) { + out << YAML::Value << tx_gain_lora[i]; + } + out << YAML::EndSeq; + } else if (tx_gain_lora[0] != 0) { + out << YAML::Key << "TX_GAIN_LORA" << YAML::Value << tx_gain_lora[0]; + } + out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch; if (dio3_tcxo_voltage != 0) out << YAML::Key << "DIO3_TCXO_VOLTAGE" << YAML::Value << YAML::Precision(3) << (float)dio3_tcxo_voltage / 1000; diff --git a/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h b/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h index 1448b1d74..9bb3af45a 100644 --- a/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h +++ b/variants/esp32s3/CDEBYTE_EoRa-Hub/rfswitch.h @@ -8,7 +8,8 @@ // DIO6 -> RFSW1_V2 // DIO7 -> not connected on E80 module - note that GNSS and Wifi scanning are not possible. -static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, RADIOLIB_NC}; +static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7, RADIOLIB_NC, + RADIOLIB_NC}; static const Module::RfSwitchMode_t rfswitch_table[] = { // mode DIO5 DIO6 DIO7 diff --git a/variants/nrf52840/heltec_mesh_node_t114/variant.h b/variants/nrf52840/heltec_mesh_node_t114/variant.h index bad488b35..fb7f61ac7 100644 --- a/variants/nrf52840/heltec_mesh_node_t114/variant.h +++ b/variants/nrf52840/heltec_mesh_node_t114/variant.h @@ -150,6 +150,14 @@ No longer populated on PCB #define PIN_SPI1_MOSI ST7789_SDA #define PIN_SPI1_SCK ST7789_SCK +/* + * Bluetooth + */ + +// The bluetooth transmit power on the nRF52840 is adjustable from -20dB to +8dB in steps of 4dB +// so NRF52_BLE_TX_POWER can be set to -20, -16, -12, -8, -4, 0 (default), 4, and 8. +//#define NRF52_BLE_TX_POWER 8 + /* * GPS pins */ diff --git a/variants/stm32/russell/platformio.ini b/variants/stm32/russell/platformio.ini new file mode 100644 index 000000000..0dd57a2c7 --- /dev/null +++ b/variants/stm32/russell/platformio.ini @@ -0,0 +1,21 @@ +; Russell is a board designed to mount on an ER34615/IFR32700 cell and go Up! on a balloon +; Hardware repository: https://github.com/Meshtastic-Malaysia/russell +; - RAK3172 STM32WLE5CCU6 MCU + integrated SX1262 LoRa +; - CDtop CD-PA1010D GPS +; - Bosch Sensortec BME280 sensor +; - Consonance CN3158 LiFePO4 solar charger +[env:russell] +extends = stm32_base +board = wiscore_rak3172 +board_level = extra +board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem +build_flags = + ${stm32_base.build_flags} + -Ivariants/stm32/russell + -DPRIVATE_HW +lib_deps = + ${stm32_base.lib_deps} + # renovate: datasource=custom.pio depName=Adafruit BME280 packageName=adafruit/library/Adafruit BME280 Library + adafruit/Adafruit BME280 Library@2.3.0 + +upload_port = stlink diff --git a/variants/stm32/russell/rfswitch.h b/variants/stm32/russell/rfswitch.h new file mode 100644 index 000000000..ec4829de6 --- /dev/null +++ b/variants/stm32/russell/rfswitch.h @@ -0,0 +1,7 @@ +// Pins from https://forum.rakwireless.com/t/rak3172-internal-schematic/4557/2 +// PB8, PC13 + +static const RADIOLIB_PIN_TYPE rfswitch_pins[5] = {PB8, PC13, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; + +static const Module::RfSwitchMode_t rfswitch_table[4] = { + {STM32WLx::MODE_IDLE, {LOW, LOW}}, {STM32WLx::MODE_RX, {HIGH, LOW}}, {STM32WLx::MODE_TX_HP, {LOW, HIGH}}, END_OF_MODE_TABLE}; diff --git a/variants/stm32/russell/variant.h b/variants/stm32/russell/variant.h new file mode 100644 index 000000000..796302d34 --- /dev/null +++ b/variants/stm32/russell/variant.h @@ -0,0 +1,41 @@ +#ifndef _VARIANT_RUSSELL_ +#define _VARIANT_RUSSELL_ + +#define USE_STM32WLx + +// I/O +#define LED_PIN PA0 // Red LED +#define LED_STATE_ON 1 +#define BUTTON_PIN PH3 // Shared with BOOT0 +#define BUTTON_NEED_PULLUP +// Charger IC charge/standby pins are open-drain with no hardware pull-up: +// Internal pull-up is needed on STM32 (TODO) +// #define EXT_CHRG_DETECT PA5 +// #define EXT_PWR_DETECT PA4 + +// Bosch Sensortec BME280 +#define HAS_SENSOR 1 + +// CDtop CD-PA1010D +#define ENABLE_HWSERIAL1 +#define PIN_SERIAL1_RX PB7 +#define PIN_SERIAL1_TX PB6 +#define HAS_GPS 1 +#define PIN_GPS_STANDBY PA15 +#define GPS_RX_PIN PB7 +#define GPS_TX_PIN PB6 + +// LoRa +/* + * RAK3172 (-20–85°C) -> No TCXO + * RAK3172-T (-40–85°C) -> 3.0V TCXO + * https://github.com/RAKWireless/RAK-STM32-RUI/blob/e5a28be8fab1a492bd9223dd425ca33a8a297d90/variants/WisDuo_RAK3172-T_Board/radio_conf.h#L91 + */ +#define TCXO_OPTIONAL +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 + +// Required to avoid Serial1 conflicts due to board definition here: +// https://github.com/stm32duino/Arduino_Core_STM32/blob/main/variants/STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U/variant_RAK3172_MODULE.h +#define RAK3172 + +#endif diff --git a/version.properties b/version.properties index 0a028eff0..62145da14 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 18 +build = 19