mirror of
https://github.com/meshtastic/firmware.git
synced 2026-06-06 18:38:52 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c7b7c3f90d | |||
| fc2316b292 | |||
| 263ed1d0ce | |||
| 7d10602f6a | |||
| 6f0e3c5b68 | |||
| d05232b8b2 | |||
| e42ff3590c | |||
| 7527233130 | |||
| 197226365b |
@@ -301,10 +301,12 @@ jobs:
|
||||
id: release_notes
|
||||
run: |
|
||||
chmod +x ./bin/generate_release_notes.py
|
||||
NOTES=$(./bin/generate_release_notes.py ${{ needs.version.outputs.long }})
|
||||
NOTES=$(./bin/generate_release_notes.py ${{ needs.version.outputs.long }} --compare-ref HEAD 2>release_notes.log)
|
||||
echo "notes<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$NOTES" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
echo "### Release note range" >> $GITHUB_STEP_SUMMARY
|
||||
cat release_notes.log >> $GITHUB_STEP_SUMMARY
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -466,7 +468,7 @@ jobs:
|
||||
- name: Generate release notes
|
||||
run: |
|
||||
chmod +x ./bin/generate_release_notes.py
|
||||
./bin/generate_release_notes.py ${{ needs.version.outputs.long }} > ./publish/release_notes.md
|
||||
./bin/generate_release_notes.py ${{ needs.version.outputs.long }} --compare-ref HEAD > ./publish/release_notes.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate release notes from merged PRs on develop and master branches.
|
||||
Categorizes PRs into Enhancements and Bug Fixes/Maintenance sections.
|
||||
"""
|
||||
"""Generate release notes from the actual release commit range."""
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def get_last_release_tag():
|
||||
"""Get the most recent release tag."""
|
||||
def get_last_release_tag(compare_ref, exclude_tag=None):
|
||||
"""Get the most recent version tag merged into compare_ref."""
|
||||
result = subprocess.run(
|
||||
["git", "describe", "--tags", "--abbrev=0"],
|
||||
["git", "tag", "--merged", compare_ref, "--sort=-version:refname", "v*"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
return result.stdout.strip()
|
||||
|
||||
for line in result.stdout.splitlines():
|
||||
candidate = line.strip()
|
||||
if not candidate:
|
||||
continue
|
||||
if exclude_tag and candidate == exclude_tag:
|
||||
continue
|
||||
return candidate
|
||||
|
||||
raise subprocess.CalledProcessError(result.returncode, result.args, output=result.stdout, stderr=result.stderr)
|
||||
|
||||
|
||||
def get_tag_date(tag):
|
||||
@@ -33,18 +39,18 @@ def get_tag_date(tag):
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def get_merged_prs_since_tag(tag, branch):
|
||||
"""Get all merged PRs since the given tag on the specified branch."""
|
||||
# Get commits since tag on the branch - look for PR numbers in parentheses
|
||||
def get_merged_prs_in_range(tag, compare_ref):
|
||||
"""Get all merged PRs in the git range between tag and compare_ref."""
|
||||
result = subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"log",
|
||||
f"{tag}..origin/{branch}",
|
||||
f"{tag}..{compare_ref}",
|
||||
"--oneline",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
prs = []
|
||||
@@ -65,6 +71,25 @@ def get_merged_prs_since_tag(tag, branch):
|
||||
return prs
|
||||
|
||||
|
||||
def parse_args():
|
||||
"""Parse CLI arguments."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate release notes from the actual release commit range."
|
||||
)
|
||||
parser.add_argument("new_version", help="Version that will be tagged for this release")
|
||||
parser.add_argument(
|
||||
"--base-tag",
|
||||
dest="base_tag",
|
||||
help="Existing version tag to diff from. Defaults to the latest version tag merged into the compare ref.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--compare-ref",
|
||||
default="HEAD",
|
||||
help="Git ref to diff to. Defaults to HEAD.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def get_pr_details(pr_number):
|
||||
"""Get PR details from GitHub API via gh CLI."""
|
||||
try:
|
||||
@@ -268,28 +293,28 @@ def get_new_contributors(pr_details_list, tag, repo="meshtastic/firmware"):
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: generate_release_notes.py <new_version>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
new_version = sys.argv[1]
|
||||
args = parse_args()
|
||||
new_version = args.new_version
|
||||
compare_ref = args.compare_ref
|
||||
current_tag = f"v{new_version}"
|
||||
|
||||
# Get last release tag
|
||||
try:
|
||||
last_tag = get_last_release_tag()
|
||||
last_tag = args.base_tag or get_last_release_tag(compare_ref, exclude_tag=current_tag)
|
||||
except subprocess.CalledProcessError:
|
||||
print("Error: Could not find last release tag", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Collect PRs from both branches
|
||||
all_pr_numbers = set()
|
||||
print(
|
||||
f"Resolved release note range: {last_tag}..{compare_ref}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
for branch in ["develop", "master"]:
|
||||
try:
|
||||
prs = get_merged_prs_since_tag(last_tag, branch)
|
||||
all_pr_numbers.update(prs)
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not get PRs from {branch}: {e}", file=sys.stderr)
|
||||
try:
|
||||
all_pr_numbers = set(get_merged_prs_in_range(last_tag, compare_ref))
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error: Could not get PRs for range {last_tag}..{compare_ref}: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Get details for all PRs
|
||||
enhancements = []
|
||||
|
||||
@@ -21,8 +21,15 @@
|
||||
// How many messages are stored (RAM + flash).
|
||||
// Define -DMESSAGE_HISTORY_LIMIT=N in build_flags to control memory usage.
|
||||
#ifndef MESSAGE_HISTORY_LIMIT
|
||||
#if defined(ARCH_ESP32) && \
|
||||
!(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32S2))
|
||||
// Baseline ESP32 (non-PSRAM variants) has limited heap; reduce message history on resource-constrained builds.
|
||||
// Override with -DMESSAGE_HISTORY_LIMIT=N if needed.
|
||||
#define MESSAGE_HISTORY_LIMIT 10
|
||||
#else
|
||||
#define MESSAGE_HISTORY_LIMIT 20
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Internal alias used everywhere in code – do NOT redefine elsewhere.
|
||||
#define MAX_MESSAGES_SAVED MESSAGE_HISTORY_LIMIT
|
||||
|
||||
@@ -1,10 +1,85 @@
|
||||
#include "concurrency/BinarySemaphorePosix.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#ifndef HAS_FREE_RTOS
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
#ifdef ARCH_PORTDUINO
|
||||
|
||||
BinarySemaphorePosix::BinarySemaphorePosix()
|
||||
{
|
||||
if (pthread_mutex_init(&mutex, NULL) != 0) {
|
||||
throw std::runtime_error("pthread_mutex_init failed");
|
||||
}
|
||||
if (pthread_cond_init(&cond, NULL) != 0) {
|
||||
pthread_mutex_destroy(&mutex);
|
||||
throw std::runtime_error("pthread_cond_init failed");
|
||||
}
|
||||
signaled = false;
|
||||
}
|
||||
|
||||
BinarySemaphorePosix::~BinarySemaphorePosix()
|
||||
{
|
||||
pthread_cond_destroy(&cond);
|
||||
pthread_mutex_destroy(&mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if we timed out
|
||||
*/
|
||||
bool BinarySemaphorePosix::take(uint32_t msec)
|
||||
{
|
||||
pthread_mutex_lock(&mutex);
|
||||
|
||||
if (!signaled) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
|
||||
ts.tv_sec += msec / 1000;
|
||||
ts.tv_nsec += (msec % 1000) * 1000000L;
|
||||
if (ts.tv_nsec >= 1000000000L) {
|
||||
ts.tv_sec += 1;
|
||||
ts.tv_nsec -= 1000000000L;
|
||||
}
|
||||
|
||||
while (!signaled) {
|
||||
int rc = pthread_cond_timedwait(&cond, &mutex, &ts);
|
||||
if (rc == ETIMEDOUT)
|
||||
break;
|
||||
if (rc != 0) {
|
||||
// Some other error occurred
|
||||
pthread_mutex_unlock(&mutex);
|
||||
throw std::runtime_error("pthread_cond_timedwait failed: " + std::to_string(rc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool wasSignaled = signaled;
|
||||
signaled = false; // consume the signal (binary semaphore)
|
||||
|
||||
pthread_mutex_unlock(&mutex);
|
||||
return wasSignaled;
|
||||
}
|
||||
|
||||
void BinarySemaphorePosix::give()
|
||||
{
|
||||
pthread_mutex_lock(&mutex);
|
||||
signaled = true;
|
||||
pthread_cond_signal(&cond);
|
||||
pthread_mutex_unlock(&mutex);
|
||||
}
|
||||
|
||||
IRAM_ATTR void BinarySemaphorePosix::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken)
|
||||
{
|
||||
give();
|
||||
if (pxHigherPriorityTaskWoken)
|
||||
*pxHigherPriorityTaskWoken = true;
|
||||
}
|
||||
#else
|
||||
|
||||
BinarySemaphorePosix::BinarySemaphorePosix() {}
|
||||
|
||||
@@ -22,7 +97,8 @@ bool BinarySemaphorePosix::take(uint32_t msec)
|
||||
void BinarySemaphorePosix::give() {}
|
||||
|
||||
IRAM_ATTR void BinarySemaphorePosix::giveFromISR(BaseType_t *pxHigherPriorityTaskWoken) {}
|
||||
#endif
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include "../freertosinc.h"
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
namespace concurrency
|
||||
{
|
||||
|
||||
@@ -9,7 +13,12 @@ namespace concurrency
|
||||
|
||||
class BinarySemaphorePosix
|
||||
{
|
||||
// SemaphoreHandle_t semaphore;
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
bool signaled;
|
||||
#endif
|
||||
|
||||
public:
|
||||
BinarySemaphorePosix();
|
||||
@@ -27,4 +36,4 @@ class BinarySemaphorePosix
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace concurrency
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -422,6 +422,17 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
std::vector<bool> isMine; // track alignment
|
||||
std::vector<bool> isHeader; // track header lines
|
||||
std::vector<AckStatus> ackForLine;
|
||||
// Hard limit on total cached lines to prevent unbounded growth from a single long message.
|
||||
// Reserve to the actual cache cap up front, because a single message can expand to many more
|
||||
// wrapped display lines than a small per-message estimate would predict. For a display
|
||||
// rendering only ~5-30 lines at a time, caching more than this limit wastes heap. Stop
|
||||
// appending once we reach MAX_CACHED_LINES to prevent a single message from blowing out the
|
||||
// heap.
|
||||
constexpr size_t MAX_CACHED_LINES = 100U; // ~5-6KB for std::string overhead on 32-bit (if each ~50-60 bytes avg)
|
||||
allLines.reserve(MAX_CACHED_LINES);
|
||||
isMine.reserve(MAX_CACHED_LINES);
|
||||
isHeader.reserve(MAX_CACHED_LINES);
|
||||
ackForLine.reserve(MAX_CACHED_LINES);
|
||||
|
||||
for (auto it = filtered.rbegin(); it != filtered.rend(); ++it) {
|
||||
const auto &m = *it;
|
||||
@@ -565,16 +576,23 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
|
||||
int wrapWidth = mine ? rightTextWidth : leftTextWidth;
|
||||
std::vector<std::string> wrapped = generateLines(display, "", msgText, wrapWidth);
|
||||
// Per-message wrap-line limit: even if wrapping produces many lines, cap them to prevent
|
||||
// a single long message from consuming most or all of the cache.
|
||||
constexpr size_t MAX_WRAPPED_LINES_PER_MSG = 20U;
|
||||
size_t wrappedCount = 0;
|
||||
for (auto &ln : wrapped) {
|
||||
allLines.push_back(ln);
|
||||
if (allLines.size() >= MAX_CACHED_LINES || wrappedCount >= MAX_WRAPPED_LINES_PER_MSG)
|
||||
break; // Cache limit or per-message limit reached; stop adding lines from this message
|
||||
allLines.emplace_back(std::move(ln));
|
||||
isMine.push_back(mine);
|
||||
isHeader.push_back(false);
|
||||
ackForLine.push_back(AckStatus::NONE);
|
||||
++wrappedCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache lines and heights
|
||||
cachedLines = allLines;
|
||||
cachedLines.swap(allLines);
|
||||
cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader);
|
||||
|
||||
std::vector<MessageBlock> blocks = buildMessageBlocks(isHeader, isMine);
|
||||
|
||||
@@ -121,7 +121,6 @@ void CardputerKeyboard::pressed(uint8_t key)
|
||||
modifierFlag = 0;
|
||||
}
|
||||
|
||||
uint8_t next_key = 0;
|
||||
int row = (key - 1) / 10;
|
||||
int col = (key - 1) % 10;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user