From 9ded6a52153d894880a98298ee4896d4316a2c46 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 8 Sep 2025 18:39:36 -0500 Subject: [PATCH 01/27] Pull in panel_sdl directly and drop native-sdl target --- src/graphics/Panel_sdl.cpp | 687 +++++++++++++++++++++++ src/graphics/Panel_sdl.hpp | 165 ++++++ src/graphics/TFTDisplay.cpp | 32 +- src/main.cpp | 5 +- variants/native/portduino/platformio.ini | 20 +- 5 files changed, 871 insertions(+), 38 deletions(-) create mode 100644 src/graphics/Panel_sdl.cpp create mode 100644 src/graphics/Panel_sdl.hpp diff --git a/src/graphics/Panel_sdl.cpp b/src/graphics/Panel_sdl.cpp new file mode 100644 index 000000000..bad6072f9 --- /dev/null +++ b/src/graphics/Panel_sdl.cpp @@ -0,0 +1,687 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) + +Porting for SDL: + [imliubo](https://github.com/imliubo) +/----------------------------------------------------------------------------*/ +#include "Panel_sdl.hpp" + +#if defined(SDL_h_) + +// #include "../common.hpp" +// #include "../../misc/common_function.hpp" +// #include "../../Bus.hpp" + +#include +#include +#include +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace lgfx +{ +inline namespace v1 +{ +SDL_Keymod Panel_sdl::_keymod = KMOD_NONE; +static SDL_semaphore *_update_in_semaphore = nullptr; +static SDL_semaphore *_update_out_semaphore = nullptr; +volatile static uint32_t _in_step_exec = 0; +volatile static uint32_t _msec_step_exec = 512; +static bool _inited = false; +static bool _all_close = false; + +volatile uint8_t Panel_sdl::_gpio_dummy_values[EMULATED_GPIO_MAX]; + +static inline void *heap_alloc_dma(size_t length) +{ + return malloc(length); +} // aligned_alloc(16, length); +static inline void heap_free(void *buf) +{ + free(buf); +} + +static std::list _list_monitor; + +static monitor_t *const getMonitorByWindowID(uint32_t windowID) +{ + for (auto &m : _list_monitor) { + if (SDL_GetWindowID(m->window) == windowID) { + return m; + } + } + return nullptr; +} +//---------------------------------------------------------------------------- + +static std::vector _key_code_map; + +void Panel_sdl::addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio) +{ + if (gpio > EMULATED_GPIO_MAX) + return; + KeyCodeMapping_t map; + map.keycode = keyCode; + map.gpio = gpio; + _key_code_map.push_back(map); +} + +int Panel_sdl::getKeyCodeMapping(SDL_KeyCode keyCode) +{ + for (const auto &i : _key_code_map) { + if (i.keycode == keyCode) + return i.gpio; + } + return -1; +} + +void Panel_sdl::_event_proc(void) +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + if ((event.type == SDL_KEYDOWN) || (event.type == SDL_KEYUP)) { + auto mon = getMonitorByWindowID(event.button.windowID); + int gpio = -1; + + /// Check key mapping + gpio = getKeyCodeMapping((SDL_KeyCode)event.key.keysym.sym); + if (gpio < 0) { + switch (event.key.keysym.sym) { /// M5StackのBtnA~BtnCのエミュレート; + // case SDLK_LEFT: gpio = 39; break; + // case SDLK_DOWN: gpio = 38; break; + // case SDLK_RIGHT: gpio = 37; break; + // case SDLK_UP: gpio = 36; break; + + /// L/Rキーで画面回転 + case SDLK_r: + case SDLK_l: + if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) { + if (mon != nullptr) { + mon->frame_rotation = (mon->frame_rotation += event.key.keysym.sym == SDLK_r ? 1 : -1); + int x, y, w, h; + SDL_GetWindowSize(mon->window, &w, &h); + SDL_GetWindowPosition(mon->window, &x, &y); + SDL_SetWindowSize(mon->window, h, w); + SDL_SetWindowPosition(mon->window, x + (w - h) / 2, y + (h - w) / 2); + mon->panel->sdl_invalidate(); + } + } + break; + + /// 1~6キーで画面拡大率変更 + case SDLK_1: + case SDLK_2: + case SDLK_3: + case SDLK_4: + case SDLK_5: + case SDLK_6: + if (event.type == SDL_KEYDOWN && event.key.keysym.mod == _keymod) { + if (mon != nullptr) { + int size = 1 + (event.key.keysym.sym - SDLK_1); + _update_scaling(mon, size, size); + } + } + break; + default: + continue; + } + } + + if (event.type == SDL_KEYDOWN) { + Panel_sdl::gpio_lo(gpio); + } else { + Panel_sdl::gpio_hi(gpio); + } + } else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION) { + auto mon = getMonitorByWindowID(event.button.windowID); + if (mon != nullptr) { + { + int x, y, w, h; + SDL_GetWindowSize(mon->window, &w, &h); + SDL_GetMouseState(&x, &y); + float sf = sinf(mon->frame_angle * M_PI / 180); + float cf = cosf(mon->frame_angle * M_PI / 180); + x -= w / 2.0f; + y -= h / 2.0f; + float nx = y * sf + x * cf; + float ny = y * cf - x * sf; + if (mon->frame_rotation & 1) { + std::swap(w, h); + } + x = (nx * mon->frame_width / w) + (mon->frame_width >> 1); + y = (ny * mon->frame_height / h) + (mon->frame_height >> 1); + mon->touch_x = x - mon->frame_inner_x; + mon->touch_y = y - mon->frame_inner_y; + } + if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) { + mon->touched = true; + } + if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) { + mon->touched = false; + } + } + } else if (event.type == SDL_WINDOWEVENT) { + auto monitor = getMonitorByWindowID(event.window.windowID); + if (monitor) { + if (event.window.event == SDL_WINDOWEVENT_RESIZED) { + int mw, mh; + SDL_GetRendererOutputSize(monitor->renderer, &mw, &mh); + if (monitor->frame_rotation & 1) { + std::swap(mw, mh); + } + monitor->scaling_x = (mw * 2 / monitor->frame_width) / 2.0f; + monitor->scaling_y = (mh * 2 / monitor->frame_height) / 2.0f; + monitor->panel->sdl_invalidate(); + } else if (event.window.event == SDL_WINDOWEVENT_CLOSE) { + monitor->closing = true; + } + } + } else if (event.type == SDL_QUIT) { + for (auto &m : _list_monitor) { + m->closing = true; + } + } + } +} + +/// デバッガでステップ実行されていることを検出するスレッド用関数。 +static int detectDebugger(bool *running) +{ + uint32_t prev_ms = SDL_GetTicks(); + do { + SDL_Delay(1); + uint32_t ms = SDL_GetTicks(); + /// 時間間隔が広すぎる場合はステップ実行中 (ブレークポイントで止まった)と判断する。 + /// また、解除されたと判断した後も1023msecほど状態を維持する。 + if (ms - prev_ms > 64) { + _in_step_exec = _msec_step_exec; + } else if (_in_step_exec) { + --_in_step_exec; + } + prev_ms = ms; + } while (*running); + return 0; +} + +void Panel_sdl::_update_proc(void) +{ + for (auto it = _list_monitor.begin(); it != _list_monitor.end();) { + if ((*it)->closing) { + if ((*it)->texture_frameimage) { + SDL_DestroyTexture((*it)->texture_frameimage); + } + SDL_DestroyTexture((*it)->texture); + SDL_DestroyRenderer((*it)->renderer); + SDL_DestroyWindow((*it)->window); + _list_monitor.erase(it++); + if (_list_monitor.empty()) { + _all_close = true; + return; + } + continue; + } + (*it)->panel->sdl_update(); + ++it; + } +} + +int Panel_sdl::setup(void) +{ + if (_inited) + return 1; + _inited = true; + + /// Add default keycode mapping + /// M5StackのBtnA~BtnCのエミュレート; + addKeyCodeMapping(SDLK_LEFT, 39); + addKeyCodeMapping(SDLK_DOWN, 38); + addKeyCodeMapping(SDLK_RIGHT, 37); + addKeyCodeMapping(SDLK_UP, 36); + + SDL_CreateThread((SDL_ThreadFunction)detectDebugger, "dbg", &_inited); + + _update_in_semaphore = SDL_CreateSemaphore(0); + _update_out_semaphore = SDL_CreateSemaphore(0); + for (size_t pin = 0; pin < EMULATED_GPIO_MAX; ++pin) { + gpio_hi(pin); + } + /*Initialize the SDL*/ + SDL_Init(SDL_INIT_VIDEO); + SDL_StartTextInput(); + + // SDL_SetThreadPriority(SDL_ThreadPriority::SDL_THREAD_PRIORITY_HIGH); + return 0; +} + +int Panel_sdl::loop(void) +{ + if (!_inited) + return 1; + + _event_proc(); + SDL_SemWaitTimeout(_update_in_semaphore, 1); + _update_proc(); + _event_proc(); + if (SDL_SemValue(_update_out_semaphore) == 0) { + SDL_SemPost(_update_out_semaphore); + } + + return _all_close; +} + +int Panel_sdl::close(void) +{ + if (!_inited) + return 1; + _inited = false; + + SDL_StopTextInput(); + SDL_DestroySemaphore(_update_in_semaphore); + SDL_DestroySemaphore(_update_out_semaphore); + SDL_Quit(); + return 0; +} + +int Panel_sdl::main(int (*fn)(bool *), uint32_t msec_step_exec) +{ + _msec_step_exec = msec_step_exec; + + /// SDLの準備 + if (0 != Panel_sdl::setup()) { + return 1; + } + + /// ユーザコード関数の動作・停止フラグ + bool running = true; + + /// ユーザコード関数を起動する + auto thread = SDL_CreateThread((SDL_ThreadFunction)fn, "fn", &running); + + /// 全部のウィンドウが閉じられるまでSDLのイベント・描画処理を継続 + while (0 == Panel_sdl::loop()) { + }; + + /// ユーザコード関数を終了する + running = false; + SDL_WaitThread(thread, nullptr); + + /// SDLを終了する + return Panel_sdl::close(); +} + +void Panel_sdl::setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y) +{ + monitor.scaling_x = scaling_x; + monitor.scaling_y = scaling_y; +} + +void Panel_sdl::setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y) +{ + monitor.frame_image = frame_image; + monitor.frame_width = frame_width; + monitor.frame_height = frame_height; + monitor.frame_inner_x = inner_x; + monitor.frame_inner_y = inner_y; +} + +void Panel_sdl::setFrameRotation(uint_fast16_t frame_rotation) +{ + monitor.frame_rotation = frame_rotation; + monitor.frame_angle = (monitor.frame_rotation) * 90; +} + +Panel_sdl::~Panel_sdl(void) +{ + _list_monitor.remove(&monitor); + SDL_DestroyMutex(_sdl_mutex); +} + +Panel_sdl::Panel_sdl(void) : Panel_FrameBufferBase() +{ + _sdl_mutex = SDL_CreateMutex(); + _auto_display = true; + monitor.panel = this; +} + +bool Panel_sdl::init(bool use_reset) +{ + initFrameBuffer(_cfg.panel_width * 4, _cfg.panel_height); + bool res = Panel_FrameBufferBase::init(use_reset); + + _list_monitor.push_back(&monitor); + + return res; +} + +color_depth_t Panel_sdl::setColorDepth(color_depth_t depth) +{ + auto bits = depth & color_depth_t::bit_mask; + if (bits >= 16) { + depth = (bits > 16) ? rgb888_3Byte : rgb565_2Byte; + } else { + depth = (depth == color_depth_t::grayscale_8bit) ? grayscale_8bit : rgb332_1Byte; + } + _write_depth = depth; + _read_depth = depth; + + return depth; +} + +Panel_sdl::lock_t::lock_t(Panel_sdl *parent) : _parent{parent} +{ + SDL_LockMutex(parent->_sdl_mutex); +}; + +Panel_sdl::lock_t::~lock_t(void) +{ + ++_parent->_modified_counter; + SDL_UnlockMutex(_parent->_sdl_mutex); + if (SDL_SemValue(_update_in_semaphore) < 2) { + SDL_SemPost(_update_in_semaphore); + if (!_in_step_exec) { + SDL_SemWaitTimeout(_update_out_semaphore, 1); + } + } +}; + +void Panel_sdl::drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) +{ + lock_t lock(this); + Panel_FrameBufferBase::drawPixelPreclipped(x, y, rawcolor); +} + +void Panel_sdl::writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) +{ + lock_t lock(this); + Panel_FrameBufferBase::writeFillRectPreclipped(x, y, w, h, rawcolor); +} + +void Panel_sdl::writeBlock(uint32_t rawcolor, uint32_t length) +{ + // lock_t lock(this); + Panel_FrameBufferBase::writeBlock(rawcolor, length); +} + +void Panel_sdl::writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param, bool use_dma) +{ + lock_t lock(this); + Panel_FrameBufferBase::writeImage(x, y, w, h, param, use_dma); +} + +void Panel_sdl::writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param) +{ + lock_t lock(this); + Panel_FrameBufferBase::writeImageARGB(x, y, w, h, param); +} + +void Panel_sdl::writePixels(pixelcopy_t *param, uint32_t len, bool use_dma) +{ + lock_t lock(this); + Panel_FrameBufferBase::writePixels(param, len, use_dma); +} + +void Panel_sdl::display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) +{ + (void)x; + (void)y; + (void)w; + (void)h; + if (_in_step_exec) { + if (_display_counter != _modified_counter) { + do { + SDL_SemPost(_update_in_semaphore); + SDL_SemWaitTimeout(_update_out_semaphore, 1); + } while (_display_counter != _modified_counter); + SDL_Delay(1); + } + } +} + +uint_fast8_t Panel_sdl::getTouchRaw(touch_point_t *tp, uint_fast8_t count) +{ + (void)count; + tp->x = monitor.touch_x; + tp->y = monitor.touch_y; + tp->size = monitor.touched ? 1 : 0; + tp->id = 0; + return monitor.touched; +} + +void Panel_sdl::setWindowTitle(const char *title) +{ + _window_title = title; + if (monitor.window) { + SDL_SetWindowTitle(monitor.window, _window_title); + } +} + +void Panel_sdl::_update_scaling(monitor_t *mon, float sx, float sy) +{ + mon->scaling_x = sx; + mon->scaling_y = sy; + int nw = mon->frame_width; + int nh = mon->frame_height; + if (mon->frame_rotation & 1) { + std::swap(nw, nh); + } + + int x, y, w, h; + int rw, rh; + SDL_GetRendererOutputSize(mon->renderer, &rw, &rh); + SDL_GetWindowSize(mon->window, &w, &h); + nw = nw * sx * w / rw; + nh = nh * sy * h / rh; + SDL_GetWindowPosition(mon->window, &x, &y); + SDL_SetWindowSize(mon->window, nw, nh); + SDL_SetWindowPosition(mon->window, x + (w - nw) / 2, y + (h - nh) / 2); + mon->panel->sdl_invalidate(); +} + +void Panel_sdl::sdl_create(monitor_t *m) +{ + int flag = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; +#if SDL_FULLSCREEN + flag |= SDL_WINDOW_FULLSCREEN; +#endif + + if (m->frame_width < _cfg.panel_width) { + m->frame_width = _cfg.panel_width; + } + if (m->frame_height < _cfg.panel_height) { + m->frame_height = _cfg.panel_height; + } + + int window_width = m->frame_width * m->scaling_x; + int window_height = m->frame_height * m->scaling_y; + int scaling_x = m->scaling_x; + int scaling_y = m->scaling_y; + if (m->frame_rotation & 1) { + std::swap(window_width, window_height); + std::swap(scaling_x, scaling_y); + } + + { + m->window = SDL_CreateWindow(_window_title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, window_width, window_height, + flag); /*last param. SDL_WINDOW_BORDERLESS to hide borders*/ + } + m->renderer = SDL_CreateRenderer(m->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + m->texture = + SDL_CreateTexture(m->renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, _cfg.panel_width, _cfg.panel_height); + SDL_SetTextureBlendMode(m->texture, SDL_BLENDMODE_NONE); + + if (m->frame_image) { + // 枠画像用のサーフェイスを作成 + auto sf = SDL_CreateRGBSurfaceFrom((void *)m->frame_image, m->frame_width, m->frame_height, 32, m->frame_width * 4, + 0xFF000000, 0xFF0000, 0xFF00, 0xFF); + if (sf != nullptr) { + // 枠画像からテクスチャを作成 + m->texture_frameimage = SDL_CreateTextureFromSurface(m->renderer, sf); + SDL_FreeSurface(sf); + } + } + SDL_SetTextureBlendMode(m->texture_frameimage, SDL_BLENDMODE_BLEND); + _update_scaling(m, scaling_x, scaling_y); +} + +void Panel_sdl::sdl_update(void) +{ + if (monitor.renderer == nullptr) { + sdl_create(&monitor); + } + + bool step_exec = _in_step_exec; + + if (_texupdate_counter != _modified_counter) { + pixelcopy_t pc(nullptr, color_depth_t::rgb888_3Byte, _write_depth, false); + if (_write_depth == rgb565_2Byte) { + pc.fp_copy = pixelcopy_t::copy_rgb_fast; + } else if (_write_depth == rgb888_3Byte) { + pc.fp_copy = pixelcopy_t::copy_rgb_fast; + } else if (_write_depth == rgb332_1Byte) { + pc.fp_copy = pixelcopy_t::copy_rgb_fast; + } else if (_write_depth == grayscale_8bit) { + pc.fp_copy = pixelcopy_t::copy_rgb_fast; + } + + if (0 == SDL_LockMutex(_sdl_mutex)) { + _texupdate_counter = _modified_counter; + for (int y = 0; y < _cfg.panel_height; ++y) { + pc.src_x32 = 0; + pc.src_data = _lines_buffer[y]; + pc.fp_copy(&_texturebuf[y * _cfg.panel_width], 0, _cfg.panel_width, &pc); + } + SDL_UnlockMutex(_sdl_mutex); + SDL_UpdateTexture(monitor.texture, nullptr, _texturebuf, _cfg.panel_width * sizeof(rgb888_t)); + } + } + + int angle = monitor.frame_angle; + int target = (monitor.frame_rotation) * 90; + angle = (((target * 4) + (angle * 4) + (angle < target ? 8 : 0)) >> 3); + + if (monitor.frame_angle != angle) { // 表示する向きを変える + monitor.frame_angle = angle; + sdl_invalidate(); + } else if (monitor.frame_rotation & ~3u) { + monitor.frame_rotation &= 3; + monitor.frame_angle = (monitor.frame_rotation) * 90; + sdl_invalidate(); + } + + if (_invalidated || (_display_counter != _texupdate_counter)) { + SDL_RendererInfo info; + if (0 == SDL_GetRendererInfo(monitor.renderer, &info)) { + // ステップ実行中はVSYNCを待機しない + if (((bool)(info.flags & SDL_RENDERER_PRESENTVSYNC)) == step_exec) { + SDL_RenderSetVSync(monitor.renderer, !step_exec); + } + } + { + int red = 0; + int green = 0; + int blue = 0; +#if defined(M5GFX_BACK_COLOR) + red = ((M5GFX_BACK_COLOR) >> 16) & 0xFF; + green = ((M5GFX_BACK_COLOR) >> 8) & 0xFF; + blue = ((M5GFX_BACK_COLOR)) & 0xFF; +#endif + SDL_SetRenderDrawColor(monitor.renderer, red, green, blue, 0xFF); + } + SDL_RenderClear(monitor.renderer); + if (_invalidated) { + _invalidated = false; + int mw, mh; + SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh); + } + render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height, angle); + render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle); + SDL_RenderPresent(monitor.renderer); + _display_counter = _texupdate_counter; + if (_invalidated) { + _invalidated = false; + SDL_SetRenderDrawColor(monitor.renderer, 0, 0, 0, 0xFF); + SDL_RenderClear(monitor.renderer); + render_texture(monitor.texture, monitor.frame_inner_x, monitor.frame_inner_y, _cfg.panel_width, _cfg.panel_height, + angle); + render_texture(monitor.texture_frameimage, 0, 0, monitor.frame_width, monitor.frame_height, angle); + SDL_RenderPresent(monitor.renderer); + } + } +} + +void Panel_sdl::render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle) +{ + SDL_Point pivot; + pivot.x = (monitor.frame_width / 2.0f - tx) * (float)monitor.scaling_x; + pivot.y = (monitor.frame_height / 2.0f - ty) * (float)monitor.scaling_y; + SDL_Rect dstrect; + dstrect.w = tw * monitor.scaling_x; + dstrect.h = th * monitor.scaling_y; + int mw, mh; + SDL_GetRendererOutputSize(monitor.renderer, &mw, &mh); + dstrect.x = mw / 2.0f - pivot.x; + dstrect.y = mh / 2.0f - pivot.y; + SDL_RenderCopyEx(monitor.renderer, texture, nullptr, &dstrect, angle, &pivot, SDL_RendererFlip::SDL_FLIP_NONE); +} + +bool Panel_sdl::initFrameBuffer(size_t width, size_t height) +{ + uint8_t **lineArray = (uint8_t **)heap_alloc_dma(height * sizeof(uint8_t *)); + if (nullptr == lineArray) { + return false; + } + + _texturebuf = (rgb888_t *)heap_alloc_dma(width * height * sizeof(rgb888_t)); + + /// 8byte alignment; + width = (width + 7) & ~7u; + + _lines_buffer = lineArray; + memset(lineArray, 0, height * sizeof(uint8_t *)); + + uint8_t *framebuffer = (uint8_t *)heap_alloc_dma(width * height + 16); + + auto fb = framebuffer; + { + for (size_t y = 0; y < height; ++y) { + lineArray[y] = fb; + fb += width; + } + } + return true; +} + +void Panel_sdl::deinitFrameBuffer(void) +{ + auto lines = _lines_buffer; + _lines_buffer = nullptr; + if (lines != nullptr) { + heap_free(lines[0]); + heap_free(lines); + } + if (_texturebuf) { + heap_free(_texturebuf); + _texturebuf = nullptr; + } +} + +//---------------------------------------------------------------------------- +} // namespace v1 +} // namespace lgfx + +#endif diff --git a/src/graphics/Panel_sdl.hpp b/src/graphics/Panel_sdl.hpp new file mode 100644 index 000000000..c80c27b73 --- /dev/null +++ b/src/graphics/Panel_sdl.hpp @@ -0,0 +1,165 @@ +/*----------------------------------------------------------------------------/ + Lovyan GFX - Graphics library for embedded devices. + +Original Source: + https://github.com/lovyan03/LovyanGFX/ + +Licence: + [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt) + +Author: + [lovyan03](https://twitter.com/lovyan03) + +Contributors: + [ciniml](https://github.com/ciniml) + [mongonta0716](https://github.com/mongonta0716) + [tobozo](https://github.com/tobozo) + +Porting for SDL: + [imliubo](https://github.com/imliubo) +/----------------------------------------------------------------------------*/ +#pragma once + +#define SDL_MAIN_HANDLED +#if __has_include() +#include +#include +#elif __has_include() +#include +#include +#endif + +#if defined(SDL_h_) +#include "lgfx/v1/Touch.hpp" +#include "lgfx/v1/misc/range.hpp" +#include "lgfx/v1/panel/Panel_FrameBufferBase.hpp" +#include + +namespace lgfx +{ +inline namespace v1 +{ + +struct Panel_sdl; +struct monitor_t { + SDL_Window *window = nullptr; + SDL_Renderer *renderer = nullptr; + SDL_Texture *texture = nullptr; + SDL_Texture *texture_frameimage = nullptr; + Panel_sdl *panel = nullptr; + + // 外枠 + const void *frame_image = 0; + uint_fast16_t frame_width = 0; + uint_fast16_t frame_height = 0; + uint_fast16_t frame_inner_x = 0; + uint_fast16_t frame_inner_y = 0; + int_fast16_t frame_rotation = 0; + int_fast16_t frame_angle = 0; + + float scaling_x = 1; + float scaling_y = 1; + int_fast16_t touch_x, touch_y; + bool touched = false; + bool closing = false; +}; +//---------------------------------------------------------------------------- + +struct Touch_sdl : public ITouch { + bool init(void) override { return true; } + void wakeup(void) override {} + void sleep(void) override {} + bool isEnable(void) override { return true; }; + uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override { return 0; } +}; + +//---------------------------------------------------------------------------- + +struct Panel_sdl : public Panel_FrameBufferBase { + static constexpr size_t EMULATED_GPIO_MAX = 128; + static volatile uint8_t _gpio_dummy_values[EMULATED_GPIO_MAX]; + + public: + Panel_sdl(void); + virtual ~Panel_sdl(void); + + bool init(bool use_reset) override; + + color_depth_t setColorDepth(color_depth_t depth) override; + + void display(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h) override; + + // void setInvert(bool invert) override {} + void drawPixelPreclipped(uint_fast16_t x, uint_fast16_t y, uint32_t rawcolor) override; + void writeFillRectPreclipped(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, uint32_t rawcolor) override; + void writeBlock(uint32_t rawcolor, uint32_t length) override; + void writeImage(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param, + bool use_dma) override; + void writeImageARGB(uint_fast16_t x, uint_fast16_t y, uint_fast16_t w, uint_fast16_t h, pixelcopy_t *param) override; + void writePixels(pixelcopy_t *param, uint32_t len, bool use_dma) override; + + uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override; + + void setWindowTitle(const char *title); + void setScaling(uint_fast8_t scaling_x, uint_fast8_t scaling_y); + void setFrameImage(const void *frame_image, int frame_width, int frame_height, int inner_x, int inner_y); + void setFrameRotation(uint_fast16_t frame_rotaion); + void setBrightness(uint8_t brightness) override{}; + + static volatile void gpio_hi(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 1; } + static volatile void gpio_lo(uint32_t pin) { _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)] = 0; } + static volatile bool gpio_in(uint32_t pin) { return _gpio_dummy_values[pin & (EMULATED_GPIO_MAX - 1)]; } + + static int setup(void); + static int loop(void); + static int close(void); + + static int main(int (*fn)(bool *), uint32_t msec_step_exec = 512); + + static void setShortcutKeymod(SDL_Keymod keymod) { _keymod = keymod; } + + struct KeyCodeMapping_t { + SDL_KeyCode keycode = SDLK_UNKNOWN; + uint8_t gpio = 0; + }; + static void addKeyCodeMapping(SDL_KeyCode keyCode, uint8_t gpio); + static int getKeyCodeMapping(SDL_KeyCode keyCode); + + protected: + const char *_window_title = "LGFX Simulator"; + SDL_mutex *_sdl_mutex = nullptr; + + void sdl_create(monitor_t *m); + void sdl_update(void); + + touch_point_t _touch_point; + monitor_t monitor; + + rgb888_t *_texturebuf = nullptr; + uint_fast16_t _modified_counter; + uint_fast16_t _texupdate_counter; + uint_fast16_t _display_counter; + bool _invalidated; + + static void _event_proc(void); + static void _update_proc(void); + static void _update_scaling(monitor_t *m, float sx, float sy); + void sdl_invalidate(void) { _invalidated = true; } + void render_texture(SDL_Texture *texture, int tx, int ty, int tw, int th, float angle); + bool initFrameBuffer(size_t width, size_t height); + void deinitFrameBuffer(void); + + static SDL_Keymod _keymod; + + struct lock_t { + lock_t(Panel_sdl *parent); + ~lock_t(); + + protected: + Panel_sdl *_parent; + }; +}; +//---------------------------------------------------------------------------- +} // namespace v1 +} // namespace lgfx +#endif \ No newline at end of file diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index 3eeb17ef0..0663602d9 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -751,10 +751,8 @@ static LGFX *tft = nullptr; static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h #elif ARCH_PORTDUINO +#include "Panel_sdl.hpp" #include // Graphics and font library for ST7735 driver chip -#if defined(LGFX_SDL) -#include -#endif class LGFX : public lgfx::LGFX_Device { @@ -783,10 +781,10 @@ class LGFX : public lgfx::LGFX_Device _panel_instance = new lgfx::Panel_ILI9488; else if (portduino_config.displayPanel == hx8357d) _panel_instance = new lgfx::Panel_HX8357D; -#if defined(LGFX_SDL) - else if (portduino_config.displayPanel == x11) { +#if defined(SDL_h_) + + else if (portduino_config.displayPanel == x11) _panel_instance = new lgfx::Panel_sdl; - } #endif else { _panel_instance = new lgfx::Panel_NULL; @@ -799,8 +797,9 @@ class LGFX : public lgfx::LGFX_Device buscfg.pin_dc = portduino_config.displayDC.pin; // Set SPI DC pin number (-1 = disable) - _bus_instance.config(buscfg); // applies the set value to the bus. - _panel_instance->setBus(&_bus_instance); // set the bus on the panel. + _bus_instance.config(buscfg); // applies the set value to the bus. + if (portduino_config.displayPanel != x11) + _panel_instance->setBus(&_bus_instance); // set the bus on the panel. auto cfg = _panel_instance->config(); // Gets a structure for display panel settings. LOG_DEBUG("Width: %d, Height: %d", portduino_config.displayWidth, portduino_config.displayHeight); @@ -848,7 +847,7 @@ class LGFX : public lgfx::LGFX_Device _touch_instance->config(touch_cfg); _panel_instance->setTouch(_touch_instance); } -#if defined(LGFX_SDL) +#if defined(SDL_h_) if (portduino_config.displayPanel == x11) { lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance; sdl_panel_->setup(); @@ -1237,7 +1236,7 @@ void TFTDisplay::display(bool fromBlank) void TFTDisplay::sdlLoop() { -#if defined(LGFX_SDL) +#if defined(SDL_h_) static int lastPressed = 0; static int shuttingDown = false; if (portduino_config.displayPanel == x11) { @@ -1247,27 +1246,26 @@ void TFTDisplay::sdlLoop() InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); } - // debounce - if (lastPressed != 0 && !lgfx::v1::gpio_in(lastPressed)) + if (lastPressed != 0 && !sdl_panel_->gpio_in(lastPressed)) return; - if (!lgfx::v1::gpio_in(37)) { + if (!sdl_panel_->gpio_in(37)) { lastPressed = 37; InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_RIGHT, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); - } else if (!lgfx::v1::gpio_in(36)) { + } else if (!sdl_panel_->gpio_in(36)) { lastPressed = 36; InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_UP, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); - } else if (!lgfx::v1::gpio_in(38)) { + } else if (!sdl_panel_->gpio_in(38)) { lastPressed = 38; InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_DOWN, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); - } else if (!lgfx::v1::gpio_in(39)) { + } else if (!sdl_panel_->gpio_in(39)) { lastPressed = 39; InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_LEFT, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); - } else if (!lgfx::v1::gpio_in(SDL_SCANCODE_KP_ENTER)) { + } else if (!sdl_panel_->gpio_in(SDL_SCANCODE_KP_ENTER)) { lastPressed = SDL_SCANCODE_KP_ENTER; InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SELECT, .kbchar = 0, .touchX = 0, .touchY = 0}; inputBroker->injectInputEvent(&event); diff --git a/src/main.cpp b/src/main.cpp index b0f086f14..029b8d708 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1604,8 +1604,9 @@ void loop() if (inputBroker) inputBroker->processInputEventQueue(); #endif -#if defined(LGFX_SDL) - if (screen) { +#if ARCH_PORTDUINO && HAS_TFT + if (screen && portduino_config.displayPanel == x11 && + config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) { auto dispdev = screen->getDisplayDevice(); if (dispdev) static_cast(dispdev)->sdlLoop(); diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini index c47ab8bf1..61eadb459 100644 --- a/variants/native/portduino/platformio.ini +++ b/variants/native/portduino/platformio.ini @@ -40,28 +40,10 @@ build_flags = ${native_base.build_flags} -Os -lX11 -linput -lxkbcommon -ffunctio -D VIEW_320x240 !pkg-config --libs libulfius --silence-errors || : !pkg-config --libs openssl --silence-errors || : + !pkg-config --cflags --libs sdl2 --silence-errors || : build_src_filter = ${native_base.build_src_filter} -[env:native-sdl] -extends = native_base -build_type = release -lib_deps = - ${env.lib_deps} - ${networking_base.lib_deps} - ${radiolib_base.lib_deps} - ${environmental_base.lib_deps} - # renovate: datasource=custom.pio depName=rweather/Crypto packageName=rweather/library/Crypto - rweather/Crypto@0.4.0 - # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main - https://github.com/pine64/libch341-spi-userspace/archive/af9bc27c9c30fa90772279925b7c5913dff789b4.zip - # renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library - adafruit/Adafruit seesaw Library@1.7.9 - https://github.com/jp-bennett/LovyanGFX/archive/7458f84a126c1f8fdc7b038074f71be903f6e4c0.zip -build_flags = ${native_base.build_flags} - !pkg-config --cflags --libs sdl2 --silence-errors || : - -D LGFX_SDL=1 - [env:native-fb] extends = native_base build_type = release From cbd30f95f351c2f94a35a855ed42461e583e9cb6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 8 Sep 2025 20:08:16 -0500 Subject: [PATCH 02/27] Portduino: Only short-circuit hardware support when forcing sim mode --- src/platform/portduino/PortduinoGlue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index b11d2547b..dbc90f9a4 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -202,7 +202,7 @@ void portduinoSetup() exit(EXIT_SUCCESS); } - if (portduino_config.lora_module == use_simradio) { + if (portduino_config.force_simradio) { std::cout << "Running in simulated mode." << std::endl; portduino_config.MaxNodes = 200; // Default to 200 nodes // Set the random seed equal to TCPPort to have a different seed per instance From 6022b749bab67f23cbf75482fd0cf64ec41010d0 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 8 Sep 2025 20:08:31 -0500 Subject: [PATCH 03/27] Don't forget to break! --- src/platform/portduino/PortduinoGlue.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/platform/portduino/PortduinoGlue.h b/src/platform/portduino/PortduinoGlue.h index ec6209487..3fe017d5e 100644 --- a/src/platform/portduino/PortduinoGlue.h +++ b/src/platform/portduino/PortduinoGlue.h @@ -444,12 +444,16 @@ extern struct portduino_config_struct { switch (configDisplayMode) { case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR: out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR"; + break; case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED: out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED"; + break; case meshtastic_Config_DisplayConfig_DisplayMode_COLOR: out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR"; + break; case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT: out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT"; + break; } out << YAML::EndMap; // Config From 7c4367cddc1e68945131ffe2e2a7c6acea2a7bb1 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 8 Sep 2025 20:32:07 -0500 Subject: [PATCH 04/27] Cppckeck suppress bogus error --- src/graphics/Panel_sdl.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graphics/Panel_sdl.hpp b/src/graphics/Panel_sdl.hpp index c80c27b73..802c6c5dc 100644 --- a/src/graphics/Panel_sdl.hpp +++ b/src/graphics/Panel_sdl.hpp @@ -21,6 +21,7 @@ Porting for SDL: #pragma once #define SDL_MAIN_HANDLED +// cppcheck-suppress preprocessorErrorDirective #if __has_include() #include #include From de6a02756dacf6bf6a879beffafb1de9dce6f3ef Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 5 Oct 2025 14:03:55 +0200 Subject: [PATCH 05/27] De-duplicate handling upgraded packet and rebroadcasting logic --- src/mesh/FloodingRouter.cpp | 98 ++++++++++++++----------------------- src/mesh/FloodingRouter.h | 15 ++++-- src/mesh/NextHopRouter.cpp | 91 ++++++++++++++++------------------ src/mesh/NextHopRouter.h | 6 +-- src/mesh/PacketHistory.cpp | 1 - 5 files changed, 92 insertions(+), 119 deletions(-) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 1d8ac247f..1225263a2 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -31,33 +31,8 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) wasSeenRecently(p, true, nullptr, nullptr, &wasUpgraded); // Updates history; returns false when an upgrade is detected // Handle hop_limit upgrade scenario for rebroadcasters - // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages - if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) { - // wasSeenRecently() reports false in upgrade cases so we handle replacement before the duplicate short-circuit - // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to - // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead. - uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining - if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) { - LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id, - p->hop_limit, dropThreshold); - - if (nodeDB) - nodeDB->updateFrom(*p); -#if !MESHTASTIC_EXCLUDE_TRACEROUTE - if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && - p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP) - traceRouteModule->processUpgradedPacket(*p); -#endif - - perhapsRebroadcast(p); - - // We already enqueued the improved copy, so make sure the incoming packet stops here. - return true; - } - - // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid - // delivering the same packet to applications/phone twice with different hop limits. - seenRecently = true; + if (wasUpgraded && perhapsHandleUpgradedPacket(p)) { + return true; // we handled it, so stop processing } if (seenRecently) { @@ -82,6 +57,40 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) return Router::shouldFilterReceived(p); } +bool FloodingRouter::perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p) +{ + // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages + if (isRebroadcaster() && iface && p->hop_limit > 0) { + // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to + // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead. + uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining + if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) { + LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id, + p->hop_limit, dropThreshold); + + reprocessPacket(p); + perhapsRebroadcast(p); + + rxDupe++; + // We already enqueued the improved copy, so make sure the incoming packet stops here. + return true; + } + } + + return false; +} + +void FloodingRouter::reprocessPacket(const meshtastic_MeshPacket *p) +{ + if (nodeDB) + nodeDB->updateFrom(*p); +#if !MESHTASTIC_EXCLUDE_TRACEROUTE + if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && + p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP) + traceRouteModule->processUpgradedPacket(*p); +#endif +} + bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p) { if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || @@ -121,41 +130,6 @@ bool FloodingRouter::isRebroadcaster() config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE; } -void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p) -{ - if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) { - if (p->id != 0) { - if (isRebroadcaster()) { - meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it - - // Use shared logic to determine if hop_limit should be decremented - if (shouldDecrementHopLimit(p)) { - tosend->hop_limit--; // bump down the hop count - } else { - LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit"); - } -#if USERPREFS_EVENT_MODE - if (tosend->hop_limit > 2) { - // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away. - tosend->hop_start -= (tosend->hop_limit - 2); - tosend->hop_limit = 2; - } -#endif - - tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case - - LOG_INFO("Rebroadcast received floodmsg"); - // Note: we are careful to resend using the original senders node id - send(tosend); - } else { - LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE"); - } - } else { - LOG_DEBUG("Ignore 0 id broadcast"); - } - } -} - void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) { bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index eaf71d294..e8a2e9685 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -27,10 +27,6 @@ */ class FloodingRouter : public Router { - private: - /* Check if we should rebroadcast this packet, and do so if needed */ - void perhapsRebroadcast(const meshtastic_MeshPacket *p); - public: /** * Constructor @@ -59,6 +55,17 @@ class FloodingRouter : public Router */ virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override; + /* Check if we should rebroadcast this packet, and do so if needed */ + virtual bool perhapsRebroadcast(const meshtastic_MeshPacket *p) = 0; + + /* Check if we should handle an upgraded packet (with higher hop_limit) + * @return true if we handled it (so stop processing) + */ + bool perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p); + + /* Call when we receive a packet that needs some reprocessing, but afterwards should be filtered */ + void reprocessPacket(const meshtastic_MeshPacket *p); + // Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of // the same packet bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p); diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp index 0461d7eb6..7340c0e87 100644 --- a/src/mesh/NextHopRouter.cpp +++ b/src/mesh/NextHopRouter.cpp @@ -43,31 +43,8 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) &wasUpgraded); // Updates history; returns false when an upgrade is detected // Handle hop_limit upgrade scenario for rebroadcasters - // isRebroadcaster() is duplicated in perhapsRelay(), but this avoids confusing log messages - if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) { - // Upgrade detection bypasses the duplicate short-circuit so we replace the queued packet before exiting - uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining - if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) { - LOG_DEBUG("Processing upgraded packet 0x%08x for relay with hop limit %d (dropping queued < %d)", p->id, p->hop_limit, - dropThreshold); - - if (nodeDB) - nodeDB->updateFrom(*p); -#if !MESHTASTIC_EXCLUDE_TRACEROUTE - if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && - p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP) - traceRouteModule->processUpgradedPacket(*p); -#endif - - perhapsRelay(p); - - // We already enqueued the improved copy, so make sure the incoming packet stops here. - return true; - } - - // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid - // delivering the same packet to applications/phone twice with different hop limits. - seenRecently = true; + if (wasUpgraded && perhapsHandleUpgradedPacket(p)) { + return true; // we handled it, so stop processing } if (seenRecently) { @@ -107,13 +84,14 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0 || p->decoded.reply_id != 0); if (isAckorReply) { - // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is - // not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination + // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" + // is not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the + // destination if (p->from != 0) { meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from); if (origTx) { - // Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came directly - // from the destination + // Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came + // directly from the destination bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to); bool weWereSoleRelayer = false; bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer); @@ -134,34 +112,49 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast } } - perhapsRelay(p); + perhapsRebroadcast(p); // handle the packet as normal Router::sniffReceived(p, c); } -/* Check if we should be relaying this packet if so, do so. */ -bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p) +/* Check if we should be rebroadcasting this packet if so, do so. */ +bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p) { if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) { - if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) { + if (p->id != 0) { if (isRebroadcaster()) { - meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it - LOG_INFO("Relaying received message coming from %x", p->relay_node); + if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) { + meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it + LOG_INFO("Rebroadcast received message coming from %x", p->relay_node); - // Use shared logic to determine if hop_limit should be decremented - if (shouldDecrementHopLimit(p)) { - tosend->hop_limit--; // bump down the hop count - } else { - LOG_INFO("Router/CLIENT_BASE-to-favorite-router/CLIENT_BASE relay: preserving hop_limit"); + // Use shared logic to determine if hop_limit should be decremented + if (shouldDecrementHopLimit(p)) { + tosend->hop_limit--; // bump down the hop count + } else { + LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit"); + } +#if USERPREFS_EVENT_MODE + if (tosend->hop_limit > 2) { + // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away. + tosend->hop_start -= (tosend->hop_limit - 2); + tosend->hop_limit = 2; + } +#endif + + if (p->next_hop == NO_NEXT_HOP_PREFERENCE) { + FloodingRouter::send(tosend); + } else { + NextHopRouter::send(tosend); + } + + return true; } - - NextHopRouter::send(tosend); - - return true; } else { - LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE"); + LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE"); } + } else { + LOG_DEBUG("Ignore 0 id broadcast"); } } @@ -231,13 +224,13 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key) } } - // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't - // get scheduled again. (This is the core of stopRetransmission.) + // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it + // doesn't get scheduled again. (This is the core of stopRetransmission.) auto numErased = pending.erase(key); assert(numErased == 1); - // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call - // to startRetransmission. + // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the + // call to startRetransmission. packetPool.release(p); return true; diff --git a/src/mesh/NextHopRouter.h b/src/mesh/NextHopRouter.h index 0022644e9..c1df3596b 100644 --- a/src/mesh/NextHopRouter.h +++ b/src/mesh/NextHopRouter.h @@ -148,7 +148,7 @@ class NextHopRouter : public FloodingRouter */ uint8_t getNextHop(NodeNum to, uint8_t relay_node); - /** Check if we should be relaying this packet if so, do so. - * @return true if we did relay */ - bool perhapsRelay(const meshtastic_MeshPacket *p); + /** Check if we should be rebroadcasting this packet if so, do so. + * @return true if we did rebroadcast */ + bool perhapsRebroadcast(const meshtastic_MeshPacket *p) override; }; \ No newline at end of file diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 49d581d9a..b4af707ae 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -94,7 +94,6 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, found->hop_limit, p->hop_limit); *wasUpgraded = true; - seenRecently = false; // Allow router processing but prevent duplicate app delivery } else if (wasUpgraded) { *wasUpgraded = false; // Initialize to false if not an upgrade } From 7c373b76c40cf74dd69cf9c3cd07f1729cc0702d Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 5 Oct 2025 14:04:35 +0200 Subject: [PATCH 06/27] Reprocess repeated packets also --- src/mesh/FloodingRouter.cpp | 4 +++- src/mesh/NextHopRouter.cpp | 14 ++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 1225263a2..032be241b 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -45,8 +45,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) if (isRepeated) { LOG_DEBUG("Repeated reliable tx"); // Check if it's still in the Tx queue, if not, we have to relay it again - if (!findInTxQueue(p->from, p->id)) + if (!findInTxQueue(p->from, p->id)) { + reprocessPacket(p); perhapsRebroadcast(p); + } } else { perhapsCancelDupe(p); } diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp index 7340c0e87..1ab4b43ed 100644 --- a/src/mesh/NextHopRouter.cpp +++ b/src/mesh/NextHopRouter.cpp @@ -59,14 +59,20 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) if (wasFallback) { LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node); // Check if it's still in the Tx queue, if not, we have to relay it again - if (!findInTxQueue(p->from, p->id)) - perhapsRelay(p); + if (!findInTxQueue(p->from, p->id)) { + reprocessPacket(p); + perhapsRebroadcast(p); + } } else { bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit; // If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again if (isRepeated) { - if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack) - sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0); + if (!findInTxQueue(p->from, p->id)) { + reprocessPacket(p); + if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) { + sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0); + } + } } else if (!weWereNextHop) { perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay } From f7cf5e6b0ad11fc17eed25f20cb99517ebaf6b06 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 5 Oct 2025 15:56:45 +0200 Subject: [PATCH 07/27] Change to "rebroadcast" --- src/mesh/NextHopRouter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp index 1ab4b43ed..afdb4d096 100644 --- a/src/mesh/NextHopRouter.cpp +++ b/src/mesh/NextHopRouter.cpp @@ -138,7 +138,7 @@ bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p) if (shouldDecrementHopLimit(p)) { tosend->hop_limit--; // bump down the hop count } else { - LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit"); + LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit"); } #if USERPREFS_EVENT_MODE if (tosend->hop_limit > 2) { From c147ce9a85396a38527a250d586ae2d380786a10 Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 5 Oct 2025 16:58:42 +0200 Subject: [PATCH 08/27] Update next-hops based on traceroute result --- src/modules/TraceRouteModule.cpp | 64 ++++++++++++++++++++++++++++++++ src/modules/TraceRouteModule.h | 6 +++ 2 files changed, 70 insertions(+) diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index fc2cc232b..c2c5669c9 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -21,6 +21,11 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti { const meshtastic_Data &incoming = p.decoded; + // Update next-hops using returned route + if (incoming.request_id) { + updateNextHops(p, r); + } + // Insert unknown hops if necessary insertUnknownHops(p, r, !incoming.request_id); @@ -153,6 +158,65 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti } } +void TraceRouteModule::updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) +{ + // E.g. if the route is A->B->C->D and we are B, we can set C as next-hop for C and D + // Similarly, if we are C, we can set D as next-hop for D + // If we are A, we can set B as next-hop for B, C and D + + // First check if we were the original sender or in the original route + int8_t nextHopIndex = -1; + if (isToUs(&p)) { + nextHopIndex = 0; // We are the original sender, next hop is first in route + } else { + // Check if we are in the original route + for (uint8_t i = 0; i < r->route_count; i++) { + if (r->route[i] == nodeDB->getNodeNum()) { + nextHopIndex = i + 1; // Next hop is the one after us + break; + } + } + } + + // If we are in the original route, update the next hops + if (nextHopIndex != -1) { + // For every node after us, we can set the next-hop to the first node after us + NodeNum nextHop; + if (nextHopIndex == r->route_count) { + nextHop = p.from; // We are the last in the route, next hop is destination + } else { + nextHop = r->route[nextHopIndex]; + } + + if (nextHop == NODENUM_BROADCAST) { + return; + } + uint8_t nextHopByte = nodeDB->getLastByteOfNodeNum(nextHop); + + // For the rest of the nodes in the route, set their next-hop + // Note: if we are the last in the route, this loop will not run + for (int8_t i = nextHopIndex; i < r->route_count; i++) { + NodeNum targetNode = r->route[i]; + maybeSetNextHop(targetNode, nextHopByte); + } + + // Also set next-hop for the destination node + maybeSetNextHop(p.from, nextHopByte); + } +} + +void TraceRouteModule::maybeSetNextHop(NodeNum target, uint8_t nextHopByte) +{ + if (target == NODENUM_BROADCAST) + return; + + meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(target); + if (node && node->next_hop != nextHopByte) { + LOG_INFO("Updating next-hop for 0x%08x to 0x%08x based on traceroute", target, nextHopByte); + node->next_hop = nextHopByte; + } +} + void TraceRouteModule::processUpgradedPacket(const meshtastic_MeshPacket &mp) { if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag || mp.decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP) diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h index a069f7157..dac422388 100644 --- a/src/modules/TraceRouteModule.h +++ b/src/modules/TraceRouteModule.h @@ -55,6 +55,12 @@ class TraceRouteModule : public ProtobufModule, // Call to add your ID to the route array of a RouteDiscovery message void appendMyIDandSNR(meshtastic_RouteDiscovery *r, float snr, bool isTowardsDestination, bool SNRonly); + // Update next-hops in the routing table based on the returned route + void updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r); + + // Helper to update next-hop for a single node + void maybeSetNextHop(NodeNum target, uint8_t nextHopByte); + /* Call to print the route array of a RouteDiscovery message. Set origin to where the request came from. Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */ From 5c2997ef535c8abeb5ac58c42abbdaed7ea2d6fc Mon Sep 17 00:00:00 2001 From: GUVWAF Date: Sun, 5 Oct 2025 17:03:52 +0200 Subject: [PATCH 09/27] Print only one byte --- src/modules/TraceRouteModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index c2c5669c9..5bdde1919 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -212,7 +212,7 @@ void TraceRouteModule::maybeSetNextHop(NodeNum target, uint8_t nextHopByte) meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(target); if (node && node->next_hop != nextHopByte) { - LOG_INFO("Updating next-hop for 0x%08x to 0x%08x based on traceroute", target, nextHopByte); + LOG_INFO("Updating next-hop for 0x%08x to 0x%02x based on traceroute", target, nextHopByte); node->next_hop = nextHopByte; } } From 036a58735e582a1622e42786777a8a0726378824 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 05:50:16 -0500 Subject: [PATCH 10/27] Upgrade trunk (#8229) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index 74b850b64..8c981850d 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -16,7 +16,7 @@ lint: - bandit@1.8.6 - trivy@0.67.0 - taplo@0.10.0 - - ruff@0.13.2 + - ruff@0.13.3 - isort@6.1.0 - markdownlint@0.45.0 - oxipng@9.1.5 From 29f4d99bf64e503f3cf9cb9dcc33d037ea15cf86 Mon Sep 17 00:00:00 2001 From: Dmitry Dubinin <4762973+capricornusx@users.noreply.github.com> Date: Mon, 6 Oct 2025 13:52:40 +0300 Subject: [PATCH 11/27] Add Adaptive Polling Intervals to WebServer (#7864) * feat: add adaptive polling intervals to WebServer Replace fixed 5ms polling with adaptive intervals based on HTTP activity: - 50ms during active periods (first 5 seconds after request) - 200ms during medium activity (5-30 seconds) - 1000ms during idle periods (30+ seconds) Reduces CPU usage significantly during idle periods while maintaining responsiveness when handling HTTP requests. * Fix integer overflow and magic numbers in WebServer - Handle millis() overflow in getAdaptiveInterval() - Replace magic numbers with named constants - Improve code readability and maintainability --- src/mesh/http/ContentHandler.cpp | 5 +++++ src/mesh/http/WebServer.cpp | 35 ++++++++++++++++++++++++++++++-- src/mesh/http/WebServer.h | 4 ++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index f87c6e3b0..7b7ebb595 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -148,6 +148,8 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) { + if (webServerThread) + webServerThread->markActivity(); LOG_DEBUG("webAPI handleAPIv1FromRadio"); @@ -391,6 +393,9 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) void handleStatic(HTTPRequest *req, HTTPResponse *res) { + if (webServerThread) + webServerThread->markActivity(); + // Get access to the parameters ResourceParameters *params = req->getParams(); diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index bf170de59..3a264fa5a 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -49,6 +49,12 @@ Preferences prefs; using namespace httpsserver; #include "mesh/http/ContentHandler.h" +static const uint32_t ACTIVE_THRESHOLD_MS = 5000; +static const uint32_t MEDIUM_THRESHOLD_MS = 30000; +static const int32_t ACTIVE_INTERVAL_MS = 50; +static const int32_t MEDIUM_INTERVAL_MS = 200; +static const int32_t IDLE_INTERVAL_MS = 1000; + static SSLCert *cert; static HTTPSServer *secureServer; static HTTPServer *insecureServer; @@ -175,6 +181,32 @@ WebServerThread::WebServerThread() : concurrency::OSThread("WebServer") if (!config.network.wifi_enabled && !config.network.eth_enabled) { disable(); } + lastActivityTime = millis(); +} + +void WebServerThread::markActivity() +{ + lastActivityTime = millis(); +} + +int32_t WebServerThread::getAdaptiveInterval() +{ + uint32_t currentTime = millis(); + uint32_t timeSinceActivity; + + if (currentTime >= lastActivityTime) { + timeSinceActivity = currentTime - lastActivityTime; + } else { + timeSinceActivity = (UINT32_MAX - lastActivityTime) + currentTime + 1; + } + + if (timeSinceActivity < ACTIVE_THRESHOLD_MS) { + return ACTIVE_INTERVAL_MS; + } else if (timeSinceActivity < MEDIUM_THRESHOLD_MS) { + return MEDIUM_INTERVAL_MS; + } else { + return IDLE_INTERVAL_MS; + } } int32_t WebServerThread::runOnce() @@ -189,8 +221,7 @@ int32_t WebServerThread::runOnce() ESP.restart(); } - // Loop every 5ms. - return (5); + return getAdaptiveInterval(); } void initWebServer() diff --git a/src/mesh/http/WebServer.h b/src/mesh/http/WebServer.h index 815d87432..e7a29a5a7 100644 --- a/src/mesh/http/WebServer.h +++ b/src/mesh/http/WebServer.h @@ -10,13 +10,17 @@ void createSSLCert(); class WebServerThread : private concurrency::OSThread { + private: + uint32_t lastActivityTime = 0; public: WebServerThread(); uint32_t requestRestart = 0; + void markActivity(); protected: virtual int32_t runOnce() override; + int32_t getAdaptiveInterval(); }; extern WebServerThread *webServerThread; From 627c0145e7df9f52109a822a73c35f08a2951a43 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 6 Oct 2025 07:56:27 -0500 Subject: [PATCH 12/27] Centralize getNodeId and fix references to owner.id (#8230) --- src/mesh/NodeDB.cpp | 7 ++++ src/mesh/NodeDB.h | 4 ++ src/mesh/PhoneAPI.cpp | 2 + src/mesh/wifi/WiFiAPClient.cpp | 4 +- src/mqtt/MQTT.cpp | 38 ++++++++++++++----- src/serialization/MeshPacketSerializer.cpp | 2 +- .../MeshPacketSerializer_nRF52.cpp | 2 +- test/test_mqtt/MQTT.cpp | 4 +- 8 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index e3240462d..dec8411fe 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1874,6 +1874,13 @@ uint8_t NodeDB::getMeshNodeChannel(NodeNum n) return info->channel; } +std::string NodeDB::getNodeId() const +{ + char nodeId[16]; + snprintf(nodeId, sizeof(nodeId), "!%08x", myNodeInfo.my_node_num); + return std::string(nodeId); +} + /// Find a node in our DB, return null for missing /// NOTE: This function might be called from an ISR meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n) diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index f73f64f92..e8724f2c9 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "MeshTypes.h" @@ -203,6 +204,9 @@ class NodeDB /// @return our node number NodeNum getNodeNum() { return myNodeInfo.my_node_num; } + /// @return our node ID as a string in the format "!xxxxxxxx" + std::string getNodeId() const; + // @return last byte of a NodeNum, 0xFF if it ended at 0x00 uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); } diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 07f314415..210e0ba06 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -433,6 +433,8 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) case STATE_SEND_OTHER_NODEINFOS: { LOG_DEBUG("Send known nodes"); if (nodeInfoForPhone.num != 0) { + // Just in case we stored a different user.id in the past, but should never happen going forward + sprintf(nodeInfoForPhone.user.id, "!%08x", nodeInfoForPhone.num); LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", nodeInfoForPhone.num, nodeInfoForPhone.last_heard, nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name); fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag; diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp index 1133ad424..7d210dd33 100644 --- a/src/mesh/wifi/WiFiAPClient.cpp +++ b/src/mesh/wifi/WiFiAPClient.cpp @@ -94,11 +94,11 @@ static void onNetworkConnected() // ESPmDNS (ESP32) and SimpleMDNS (RP2040) have slightly different APIs for adding TXT records #ifdef ARCH_ESP32 MDNS.addServiceTxt("meshtastic", "tcp", "shortname", String(owner.short_name)); - MDNS.addServiceTxt("meshtastic", "tcp", "id", String(owner.id)); + MDNS.addServiceTxt("meshtastic", "tcp", "id", String(nodeDB->getNodeId().c_str())); // ESP32 prints obtained IP address in WiFiEvent #elif defined(ARCH_RP2040) MDNS.addServiceTxt("meshtastic", "shortname", owner.short_name); - MDNS.addServiceTxt("meshtastic", "id", owner.id); + MDNS.addServiceTxt("meshtastic", "id", nodeDB->getNodeId().c_str()); LOG_INFO("Obtained IP address: %s", WiFi.localIP().toString().c_str()); #endif } diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 7f7a9d511..8ce352f14 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -60,7 +60,9 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length) return; } const meshtastic_Channel &ch = channels.getByName(e.channel_id); - if (strcmp(e.gateway_id, owner.id) == 0) { + // Generate node ID from nodenum for comparison + std::string nodeId = nodeDB->getNodeId(); + if (strcmp(e.gateway_id, nodeId.c_str()) == 0) { // Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message. // We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node // receives it when we get our own packet back. Then we'll stop our retransmissions. @@ -128,8 +130,10 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length) // returns true if this is a valid JSON envelope which we accept on downlink inline bool isValidJsonEnvelope(JSONObject &json) { + // Generate node ID from nodenum for comparison + std::string nodeId = nodeDB->getNodeId(); // if "sender" is provided, avoid processing packets we uplinked - return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) && + return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(nodeId) != 0) : true) && (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number (json.find("from") != json.end()) && json["from"]->IsNumber() && (json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us @@ -297,7 +301,9 @@ bool connectPubSub(const PubSubConfig &config, PubSubClient &pubSub, Client &cli LOG_INFO("Connecting directly to MQTT server %s, port: %d, username: %s, password: %s", config.serverAddr.c_str(), config.serverPort, config.mqttUsername, config.mqttPassword); - const bool connected = pubSub.connect(owner.id, config.mqttUsername, config.mqttPassword); + // Generate node ID from nodenum for client identification + std::string nodeId = nodeDB->getNodeId(); + const bool connected = pubSub.connect(nodeId.c_str(), config.mqttUsername, config.mqttPassword); if (connected) { LOG_INFO("MQTT connected"); } else { @@ -687,11 +693,14 @@ void MQTT::publishQueuedMessages() if (jsonString.length() == 0) return; + // Generate node ID from nodenum for topic + std::string nodeId = nodeDB->getNodeId(); + std::string topicJson; if (env.packet->pki_encrypted) { - topicJson = jsonTopic + "PKI/" + owner.id; + topicJson = jsonTopic + "PKI/" + nodeId; } else { - topicJson = jsonTopic + env.channel_id + "/" + owner.id; + topicJson = jsonTopic + env.channel_id + "/" + nodeId; } LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str()); publish(topicJson.c_str(), jsonString.c_str(), false); @@ -749,10 +758,14 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me return; // Don't upload a still-encrypted PKI packet if not encryption_enabled } - const meshtastic_ServiceEnvelope env = { - .packet = const_cast(p), .channel_id = const_cast(channelId), .gateway_id = owner.id}; + // Generate node ID from nodenum for service envelope + std::string nodeId = nodeDB->getNodeId(); + + const meshtastic_ServiceEnvelope env = {.packet = const_cast(p), + .channel_id = const_cast(channelId), + .gateway_id = const_cast(nodeId.c_str())}; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &env); - std::string topic = cryptTopic + channelId + "/" + owner.id; + std::string topic = cryptTopic + channelId + "/" + nodeId; if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) { LOG_DEBUG("MQTT Publish %s, %u bytes", topic.c_str(), numBytes); @@ -766,7 +779,9 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me auto jsonString = MeshPacketSerializer::JsonSerialize(&mp_decoded); if (jsonString.length() == 0) return; - std::string topicJson = jsonTopic + channelId + "/" + owner.id; + // Generate node ID from nodenum for JSON topic + std::string nodeIdForJson = nodeDB->getNodeId(); + std::string topicJson = jsonTopic + channelId + "/" + nodeIdForJson; LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str()); publish(topicJson.c_str(), jsonString.c_str(), false); #endif // ARCH_NRF52 NRF52_USE_JSON @@ -845,11 +860,14 @@ void MQTT::perhapsReportToMap() mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), &meshtastic_MapReport_msg, &mapReport); + // Generate node ID from nodenum for service envelope + std::string nodeId = nodeDB->getNodeId(); + // Encode the MeshPacket into a binary ServiceEnvelope and publish const meshtastic_ServiceEnvelope se = { .packet = mp, .channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()), // Use primary channel as the channel_id - .gateway_id = owner.id}; + .gateway_id = const_cast(nodeId.c_str())}; size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &se); LOG_INFO("MQTT Publish map report to %s", mapTopic.c_str()); diff --git a/src/serialization/MeshPacketSerializer.cpp b/src/serialization/MeshPacketSerializer.cpp index 5a1f8ed7e..b31d2dc2e 100644 --- a/src/serialization/MeshPacketSerializer.cpp +++ b/src/serialization/MeshPacketSerializer.cpp @@ -413,7 +413,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, jsonObj["from"] = new JSONValue((unsigned int)mp->from); jsonObj["channel"] = new JSONValue((unsigned int)mp->channel); jsonObj["type"] = new JSONValue(msgType.c_str()); - jsonObj["sender"] = new JSONValue(owner.id); + jsonObj["sender"] = new JSONValue(nodeDB->getNodeId().c_str()); if (mp->rx_rssi != 0) jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi); if (mp->rx_snr != 0) diff --git a/src/serialization/MeshPacketSerializer_nRF52.cpp b/src/serialization/MeshPacketSerializer_nRF52.cpp index e0daa1a88..353c710a1 100644 --- a/src/serialization/MeshPacketSerializer_nRF52.cpp +++ b/src/serialization/MeshPacketSerializer_nRF52.cpp @@ -353,7 +353,7 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp, jsonObj["from"] = (unsigned int)mp->from; jsonObj["channel"] = (unsigned int)mp->channel; jsonObj["type"] = msgType.c_str(); - jsonObj["sender"] = owner.id; + jsonObj["sender"] = nodeDB->getNodeId().c_str(); if (mp->rx_rssi != 0) jsonObj["rssi"] = (int)mp->rx_rssi; if (mp->rx_snr != 0) diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp index ede3d22b7..8726d1ccb 100644 --- a/test/test_mqtt/MQTT.cpp +++ b/test/test_mqtt/MQTT.cpp @@ -591,7 +591,7 @@ void test_receiveEncryptedPKITopicToUs(void) // Should ignore messages published to MQTT by this gateway. void test_receiveIgnoresOwnPublishedMessages(void) { - unitTest->publish(&decoded, owner.id); + unitTest->publish(&decoded, nodeDB->getNodeId().c_str()); TEST_ASSERT_TRUE(mockRouter->packets_.empty()); TEST_ASSERT_TRUE(mockRoutingModule->ackNacks_.empty()); @@ -603,7 +603,7 @@ void test_receiveAcksOwnSentMessages(void) meshtastic_MeshPacket p = decoded; p.from = myNodeInfo.my_node_num; - unitTest->publish(&p, owner.id); + unitTest->publish(&p, nodeDB->getNodeId().c_str()); TEST_ASSERT_TRUE(mockRouter->packets_.empty()); TEST_ASSERT_EQUAL(1, mockRoutingModule->ackNacks_.size()); From 329a494ce2e35b4c13c726602f4be1fad24d54c7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:59:40 -0500 Subject: [PATCH 13/27] Update meshtastic-ArduinoThread digest to b841b04 (#8233) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index b6d6733e3..2e6f851df 100644 --- a/platformio.ini +++ b/platformio.ini @@ -70,7 +70,7 @@ lib_deps = # renovate: datasource=git-refs depName=meshtastic-TinyGPSPlus packageName=https://github.com/meshtastic/TinyGPSPlus gitBranch=master https://github.com/meshtastic/TinyGPSPlus/archive/71a82db35f3b973440044c476d4bcdc673b104f4.zip # renovate: datasource=git-refs depName=meshtastic-ArduinoThread packageName=https://github.com/meshtastic/ArduinoThread gitBranch=master - https://github.com/meshtastic/ArduinoThread/archive/7c3ee9e1951551b949763b1f5280f8db1fa4068d.zip + https://github.com/meshtastic/ArduinoThread/archive/b841b0415721f1341ea41cccfb4adccfaf951567.zip # renovate: datasource=custom.pio depName=Nanopb packageName=nanopb/library/Nanopb nanopb/Nanopb@0.4.91 # renovate: datasource=custom.pio depName=ErriezCRC32 packageName=erriez/library/ErriezCRC32 From 87e3540f48eb17cffd6cda2cbebef274ced29885 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:59:50 -0500 Subject: [PATCH 14/27] Update meshtastic/device-ui digest to f920b12 (#8234) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 2e6f851df..4ae2da54f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -120,7 +120,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/505ffadaa7a931df5dc8153229b719a07bbb028c.zip + https://github.com/meshtastic/device-ui/archive/f920b1273df750c2e3e01385d3ba30553b913afa.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From 735784e6e422ab5383dcb5afc29eb7c915117a9d Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Mon, 6 Oct 2025 13:00:44 -0500 Subject: [PATCH 15/27] Run Integration test in simulator mode (#8232) --- .github/workflows/test_native.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml index 6b788f4c7..9cf1c9e53 100644 --- a/.github/workflows/test_native.yml +++ b/.github/workflows/test_native.yml @@ -40,7 +40,7 @@ jobs: - name: Integration test run: | - .pio/build/coverage/program & + .pio/build/coverage/program -s & PID=$! timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done" echo "Simulator started, launching python test..." From fc1737c9495937ba142d9018593df4b3937e29ed Mon Sep 17 00:00:00 2001 From: Austin Date: Mon, 6 Oct 2025 20:58:00 -0400 Subject: [PATCH 16/27] Actions: Simplify matrices, cleanup build_one_* (#8218) --- .github/workflows/build_firmware.yml | 3 + .github/workflows/build_one_arch.yml | 302 +----------------- .github/workflows/build_one_target.yml | 173 +--------- .github/workflows/main_matrix.yml | 141 ++------ .github/workflows/merge_queue.yml | 134 +------- bin/generate_ci_matrix.py | 55 ++-- .../native/portduino-buildroot/platformio.ini | 1 + variants/native/portduino/platformio.ini | 3 +- 8 files changed, 92 insertions(+), 720 deletions(-) diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml index 2ef67405a..b62729332 100644 --- a/.github/workflows/build_firmware.yml +++ b/.github/workflows/build_firmware.yml @@ -19,6 +19,8 @@ jobs: pio-build: name: build-${{ inputs.platform }} runs-on: ubuntu-24.04 + outputs: + artifact-id: ${{ steps.upload.outputs.artifact-id }} steps: - uses: actions/checkout@v5 with: @@ -55,6 +57,7 @@ jobs: - name: Store binaries as an artifact uses: actions/upload-artifact@v4 + id: upload with: name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip overwrite: true diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml index f5352b3c4..6d9134941 100644 --- a/.github/workflows/build_one_arch.yml +++ b/.github/workflows/build_one_arch.yml @@ -3,6 +3,7 @@ name: Build One Arch on: workflow_dispatch: inputs: + # trunk-ignore(checkov/CKV_GHA_7) arch: type: choice options: @@ -16,10 +17,13 @@ on: - stm32 - native +permissions: read-all + +env: + INPUT_ARCH: ${{ github.event.inputs.arch }} + jobs: setup: - strategy: - fail-fast: false runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 @@ -31,23 +35,11 @@ jobs: - name: Generate matrix id: jsonStep run: | - if [[ "$GITHUB_HEAD_REF" == "" ]]; then - TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra) - else - TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr) - fi - echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" - echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra) + echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" + echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT outputs: - esp32: ${{ steps.jsonStep.outputs.esp32 }} - esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} - esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} - esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} - nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} - rp2040: ${{ steps.jsonStep.outputs.rp2040 }} - rp2350: ${{ steps.jsonStep.outputs.rp2350 }} - stm32: ${{ steps.jsonStep.outputs.stm32 }} - check: ${{ steps.jsonStep.outputs.check }} + selected_arch: ${{ steps.jsonStep.outputs.selected_arch }} version: runs-on: ubuntu-latest @@ -64,101 +56,18 @@ jobs: long: ${{ steps.version.outputs.long }} deb: ${{ steps.version.outputs.deb }} - build-esp32: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}} + build: + if: ${{ github.event_name != 'workflow_dispatch' }} needs: [setup, version] strategy: fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + matrix: + build: ${{ fromJson(needs.setup.outputs.selected_arch) }} uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32 - - build-esp32s3: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32s3 - - build-esp32c3: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c3 - - build-esp32c6: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c6 - - build-nrf52840: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: nrf52840 - - build-rp2040: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2040 - - build-rp2350: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2350 - - build-stm32: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.stm32) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: stm32 + pio_env: ${{ matrix.build.board }} + platform: ${{ matrix.build.arch }} build-debian-src: if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} @@ -252,18 +161,7 @@ jobs: - rp2350 - stm32 runs-on: ubuntu-latest - needs: - [ - version, - build-esp32, - build-esp32s3, - build-esp32c3, - build-esp32c6, - build-nrf52840, - build-rp2040, - build-rp2350, - build-stm32, - ] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 @@ -332,169 +230,3 @@ jobs: name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" github-token: ${{ secrets.GITHUB_TOKEN }} - - release-artifacts: - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' }} - outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} - needs: - - version - - gather-artifacts - - build-debian-src - - package-pio-deps-native-tft - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - name: Create release - uses: softprops/action-gh-release@v2 - id: create_release - with: - draft: true - prerelease: true - name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha - tag_name: v${{ needs.version.outputs.long }} - body: | - Autogenerated by github action, developer should edit as required before publishing... - - - name: Download source deb - uses: actions/download-artifact@v5 - with: - pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src - merge-multiple: true - path: ./output/debian-src - - - name: Download `native-tft` pio deps - uses: actions/download-artifact@v5 - with: - pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output/pio-deps-native-tft - - - name: Zip Linux sources - working-directory: output - run: | - zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src - zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add Linux sources to GtiHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip - gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - release-firmware: - strategy: - fail-fast: false - matrix: - arch: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' }} - needs: [release-artifacts, version] - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output - - - name: Display structure of downloaded files - run: ls -lR - - - name: Device scripts permissions - run: | - chmod +x ./output/device-install.sh - chmod +x ./output/device-update.sh - - - name: Zip firmware - run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output - - - uses: actions/download-artifact@v5 - with: - name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip - merge-multiple: true - path: ./elfs - - - name: Zip debug elfs - run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add bins and debug elfs to GitHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip - gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish-firmware: - runs-on: ubuntu-24.04 - if: ${{ github.event_name == 'workflow_dispatch' }} - needs: [release-firmware, version] - env: - targets: |- - esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./publish - - - name: Publish firmware to meshtastic.github.io - uses: peaceiris/actions-gh-pages@v4 - env: - # On event/* branches, use the event name as the destination prefix - DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }} - with: - deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }} - external_repository: meshtastic/meshtastic.github.io - publish_branch: master - publish_dir: ./publish - destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }} - keep_files: true - user_name: github-actions[bot] - user_email: github-actions[bot]@users.noreply.github.com - commit_message: ${{ needs.version.outputs.long }} - enable_jekyll: true diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml index 3c83ce960..ba1d5080f 100644 --- a/.github/workflows/build_one_target.yml +++ b/.github/workflows/build_one_target.yml @@ -3,6 +3,7 @@ name: Build One Target on: workflow_dispatch: inputs: + # trunk-ignore(checkov/CKV_GHA_7) arch: type: choice options: @@ -19,11 +20,13 @@ on: type: string required: false description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets. - # find-target: # type: boolean # default: true # description: 'Find the available targets' + +permissions: read-all + jobs: find-targets: if: ${{ inputs.target == '' }} @@ -51,13 +54,13 @@ jobs: - name: Generate matrix id: jsonStep run: | - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} extra) + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra) echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY echo "Targets:" >> $GITHUB_STEP_SUMMARY - echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY + echo $TARGETS >> $GITHUB_STEP_SUMMARY version: if: ${{ inputs.target != '' }} @@ -75,11 +78,9 @@ jobs: long: ${{ steps.version.outputs.long }} deb: ${{ steps.version.outputs.deb }} - build-arch: + build: if: ${{ inputs.target != '' && inputs.arch != 'native' }} needs: [version] - strategy: - fail-fast: false uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} @@ -165,10 +166,8 @@ jobs: permissions: contents: write pull-requests: write - strategy: - fail-fast: false runs-on: ubuntu-latest - needs: [version, build-arch] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 @@ -237,159 +236,3 @@ jobs: name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }} description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" github-token: ${{ secrets.GITHUB_TOKEN }} - - release-artifacts: - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}} - outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} - needs: - - version - - gather-artifacts - - build-debian-src - - package-pio-deps-native-tft - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - name: Create release - uses: softprops/action-gh-release@v2 - id: create_release - with: - draft: true - prerelease: true - name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha - tag_name: v${{ needs.version.outputs.long }} - body: | - Autogenerated by github action, developer should edit as required before publishing... - - - name: Download source deb - uses: actions/download-artifact@v5 - with: - pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src - merge-multiple: true - path: ./output/debian-src - - - name: Download `native-tft` pio deps - uses: actions/download-artifact@v5 - with: - pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output/pio-deps-native-tft - - - name: Zip Linux sources - working-directory: output - run: | - zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src - zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add Linux sources to GtiHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip - gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - release-firmware: - strategy: - fail-fast: false - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}} - needs: [release-artifacts, version] - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-*-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output - - - name: Display structure of downloaded files - run: ls -lR - - - name: Device scripts permissions - run: | - chmod +x ./output/device-install.sh - chmod +x ./output/device-update.sh - - - name: Zip firmware - run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output - - - uses: actions/download-artifact@v5 - with: - pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip - merge-multiple: true - path: ./elfs - - - name: Zip debug elfs - run: zip -j -9 -r ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./elfs - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add bins and debug elfs to GitHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip - gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish-firmware: - runs-on: ubuntu-24.04 - if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' && inputs.target != '' }} - needs: [release-firmware, version] - env: - targets: |- - esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./publish - - - name: Publish firmware to meshtastic.github.io - uses: peaceiris/actions-gh-pages@v4 - env: - # On event/* branches, use the event name as the destination prefix - DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }} - with: - deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }} - external_repository: meshtastic/meshtastic.github.io - publish_branch: master - publish_dir: ./publish - destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }} - keep_files: true - user_name: github-actions[bot] - user_email: github-actions[bot]@users.noreply.github.com - commit_message: ${{ needs.version.outputs.long }} - enable_jekyll: true diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index f61e314a7..887bf3081 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -27,19 +27,11 @@ on: jobs: setup: - if: github.repository == 'meshtastic/firmware' strategy: - fail-fast: false + fail-fast: true matrix: arch: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 + - all - check runs-on: ubuntu-24.04 steps: @@ -49,33 +41,22 @@ jobs: python-version: 3.x cache: pip - run: pip install -U platformio - - name: Uncomment build epoch - shell: bash - run: | - sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini - name: Generate matrix id: jsonStep run: | if [[ "$GITHUB_HEAD_REF" == "" ]]; then TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) else - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr) + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr) fi - echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" - echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" + echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT + echo "$TARGETS" >> $GITHUB_STEP_SUMMARY outputs: - esp32: ${{ steps.jsonStep.outputs.esp32 }} - esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} - esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} - esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} - nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} - rp2040: ${{ steps.jsonStep.outputs.rp2040 }} - rp2350: ${{ steps.jsonStep.outputs.rp2350 }} - stm32: ${{ steps.jsonStep.outputs.stm32 }} + all: ${{ steps.jsonStep.outputs.all }} check: ${{ steps.jsonStep.outputs.check }} version: - if: github.repository == 'meshtastic/firmware' runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 @@ -94,7 +75,8 @@ jobs: needs: setup strategy: fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.check) }} + matrix: + check: ${{ fromJson(needs.setup.outputs.check) }} runs-on: ubuntu-latest if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }} @@ -103,96 +85,20 @@ jobs: - name: Build base id: base uses: ./.github/actions/setup-base - - name: Check ${{ matrix.board }} - run: bin/check-all.sh ${{ matrix.board }} + - name: Check ${{ matrix.check.board }} + run: bin/check-all.sh ${{ matrix.check.board }} - build-esp32: + build: needs: [setup, version] strategy: fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + matrix: + build: ${{ fromJson(needs.setup.outputs.all) }} uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32 - - build-esp32s3: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32s3 - - build-esp32c3: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c3 - - build-esp32c6: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c6 - - build-nrf52840: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: nrf52840 - - build-rp2040: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2040 - - build-rp2350: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2350 - - build-stm32: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.stm32) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: stm32 + pio_env: ${{ matrix.build.board }} + platform: ${{ matrix.build.platform }} build-debian-src: if: github.repository == 'meshtastic/firmware' @@ -203,7 +109,7 @@ jobs: secrets: inherit package-pio-deps-native-tft: - if: ${{ github.event_name == 'workflow_dispatch' }} + if: ${{ github.repository == 'meshtastic/firmware' && github.event_name == 'workflow_dispatch' }} uses: ./.github/workflows/package_pio_deps.yml with: pio_env: native-tft @@ -288,18 +194,7 @@ jobs: - rp2350 - stm32 runs-on: ubuntu-latest - needs: - [ - version, - build-esp32, - build-esp32s3, - build-esp32c3, - build-esp32c6, - build-nrf52840, - build-rp2040, - build-rp2350, - build-stm32, - ] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml index 7f397ce18..79e8b0803 100644 --- a/.github/workflows/merge_queue.yml +++ b/.github/workflows/merge_queue.yml @@ -7,23 +7,13 @@ on: # Merge group is a special trigger that is used to trigger the workflow when a merge group is created. merge_group: -env: - FAIL_FAST_PER_ARCH: true - jobs: setup: strategy: fail-fast: true matrix: arch: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 + - all - check runs-on: ubuntu-24.04 steps: @@ -39,19 +29,12 @@ jobs: if [[ "$GITHUB_HEAD_REF" == "" ]]; then TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) else - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr) + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr) fi - echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" - echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" + echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT outputs: - esp32: ${{ steps.jsonStep.outputs.esp32 }} - esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} - esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} - esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} - nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} - rp2040: ${{ steps.jsonStep.outputs.rp2040 }} - rp2350: ${{ steps.jsonStep.outputs.rp2350 }} - stm32: ${{ steps.jsonStep.outputs.stm32 }} + all: ${{ steps.jsonStep.outputs.all }} check: ${{ steps.jsonStep.outputs.check }} version: @@ -73,7 +56,8 @@ jobs: needs: setup strategy: fail-fast: true - matrix: ${{ fromJson(needs.setup.outputs.check) }} + matrix: + check: ${{ fromJson(needs.setup.outputs.check) }} runs-on: ubuntu-latest if: ${{ github.event_name != 'workflow_dispatch' }} @@ -82,96 +66,19 @@ jobs: - name: Build base id: base uses: ./.github/actions/setup-base - - name: Check ${{ matrix.board }} - run: bin/check-all.sh ${{ matrix.board }} + - name: Check ${{ matrix.check.board }} + run: bin/check-all.sh ${{ matrix.check.board }} - build-esp32: + build: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + matrix: + build: ${{ fromJson(needs.setup.outputs.all) }} uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32 - - build-esp32s3: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32s3 - - build-esp32c3: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c3 - - build-esp32c6: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c6 - - build-nrf52840: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: nrf52840 - - build-rp2040: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2040 - - build-rp2350: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2350 - - build-stm32: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.stm32) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: stm32 + pio_env: ${{ matrix.build.board }} + platform: ${{ matrix.build.platform }} build-debian-src: if: github.repository == 'meshtastic/firmware' @@ -260,18 +167,7 @@ jobs: - rp2350 - stm32 runs-on: ubuntu-latest - needs: - [ - version, - build-esp32, - build-esp32s3, - build-esp32c3, - build-esp32c6, - build-nrf52840, - build-rp2040, - build-rp2350, - build-stm32, - ] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py index aaa76aa45..b4c18c05b 100755 --- a/bin/generate_ci_matrix.py +++ b/bin/generate_ci_matrix.py @@ -1,28 +1,32 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """Generate the CI matrix.""" +import argparse import json -import sys -import random import re from platformio.project.config import ProjectConfig -options = sys.argv[1:] +parser = argparse.ArgumentParser(description="Generate the CI matrix") +parser.add_argument("platform", help="Platform to build for") +parser.add_argument( + "--level", + choices=["extra", "pr"], + nargs="*", + default=[], + help="Board level to build for (omit for full release boards)", +) +args = parser.parse_args() outlist = [] -if len(options) < 1: - print(json.dumps(outlist)) - exit(1) - cfg = ProjectConfig.get_instance() pio_envs = cfg.envs() # Gather all PlatformIO environments for filtering later all_envs = [] for pio_env in pio_envs: - env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags') + env_build_flags = cfg.get(f"env:{pio_env}", "build_flags") env_platform = None for flag in env_build_flags: # Extract the platform from the build flags @@ -37,36 +41,35 @@ for pio_env in pio_envs: exit(1) # Store env details as a dictionary, and add to 'all_envs' list env = { - 'name': pio_env, - 'platform': env_platform, - 'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None), - 'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False)) + "ci": {"board": pio_env, "platform": env_platform}, + "board_level": cfg.get(f"env:{pio_env}", "board_level", default=None), + "board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)), } all_envs.append(env) # Filter outputs based on options # Check is mutually exclusive with other options (except 'pr') -if "check" in options: +if "check" in args.platform: for env in all_envs: - if env['board_check']: - if "pr" in options: - if env['board_level'] == 'pr': - outlist.append(env['name']) + if env["board_check"]: + if "pr" in args.level: + if env["board_level"] == "pr": + outlist.append(env["ci"]) else: - outlist.append(env['name']) + outlist.append(env["ci"]) # Filter (non-check) builds by platform else: for env in all_envs: - if options[0] == env['platform']: + if args.platform == env["ci"]["platform"] or args.platform == "all": # Always include board_level = 'pr' - if env['board_level'] == 'pr': - outlist.append(env['name']) + if env["board_level"] == "pr": + outlist.append(env["ci"]) # Include board_level = 'extra' when requested - elif "extra" in options and env['board_level'] == "extra": - outlist.append(env['name']) + elif "extra" in args.level and env["board_level"] == "extra": + outlist.append(env["ci"]) # If no board level is specified, include in release builds (not PR) - elif "pr" not in options and not env['board_level']: - outlist.append(env['name']) + elif "pr" not in args.level and not env["board_level"]: + outlist.append(env["ci"]) # Return as a JSON list print(json.dumps(outlist)) diff --git a/variants/native/portduino-buildroot/platformio.ini b/variants/native/portduino-buildroot/platformio.ini index d1bd39e10..a3d0f4639 100644 --- a/variants/native/portduino-buildroot/platformio.ini +++ b/variants/native/portduino-buildroot/platformio.ini @@ -4,5 +4,6 @@ extends = portduino_base ; environment variable in the buildroot environment. build_flags = ${portduino_base.build_flags} -O0 -I variants/native/portduino-buildroot board = buildroot +board_level = extra lib_deps = ${portduino_base.lib_deps} build_src_filter = ${portduino_base.build_src_filter} \ No newline at end of file diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini index 61eadb459..2ccdfbbfc 100644 --- a/variants/native/portduino/platformio.ini +++ b/variants/native/portduino/platformio.ini @@ -3,6 +3,7 @@ extends = portduino_base build_flags = ${portduino_base.build_flags} -I variants/native/portduino -I /usr/include board = cross_platform +board_level = extra lib_deps = ${portduino_base.lib_deps} melopero/Melopero RV3028@^1.1.0 @@ -50,7 +51,6 @@ build_type = release lib_deps = ${native_base.lib_deps} ${device-ui_base.lib_deps} -board_level = extra build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -D RAM_SIZE=8192 -D USE_FRAMEBUFFER=1 @@ -79,7 +79,6 @@ build_type = debug lib_deps = ${native_base.lib_deps} ${device-ui_base.lib_deps} -board_level = extra build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -lxkbcommon -D DEBUG_HEAP -D RAM_SIZE=16384 From 518680514f6249866ade68b7b11c5906ee1599ca Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 7 Oct 2025 13:37:13 +1100 Subject: [PATCH 17/27] Actions: Simplify matrices, cleanup build_one_* (#8218) (#8239) Co-authored-by: Austin --- .github/workflows/build_firmware.yml | 3 + .github/workflows/build_one_arch.yml | 302 +----------------- .github/workflows/build_one_target.yml | 173 +--------- .github/workflows/main_matrix.yml | 135 ++------ .github/workflows/merge_queue.yml | 134 +------- bin/generate_ci_matrix.py | 55 ++-- .../native/portduino-buildroot/platformio.ini | 1 + variants/native/portduino/platformio.ini | 3 +- 8 files changed, 92 insertions(+), 714 deletions(-) diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml index 2ef67405a..b62729332 100644 --- a/.github/workflows/build_firmware.yml +++ b/.github/workflows/build_firmware.yml @@ -19,6 +19,8 @@ jobs: pio-build: name: build-${{ inputs.platform }} runs-on: ubuntu-24.04 + outputs: + artifact-id: ${{ steps.upload.outputs.artifact-id }} steps: - uses: actions/checkout@v5 with: @@ -55,6 +57,7 @@ jobs: - name: Store binaries as an artifact uses: actions/upload-artifact@v4 + id: upload with: name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}.zip overwrite: true diff --git a/.github/workflows/build_one_arch.yml b/.github/workflows/build_one_arch.yml index f5352b3c4..6d9134941 100644 --- a/.github/workflows/build_one_arch.yml +++ b/.github/workflows/build_one_arch.yml @@ -3,6 +3,7 @@ name: Build One Arch on: workflow_dispatch: inputs: + # trunk-ignore(checkov/CKV_GHA_7) arch: type: choice options: @@ -16,10 +17,13 @@ on: - stm32 - native +permissions: read-all + +env: + INPUT_ARCH: ${{ github.event.inputs.arch }} + jobs: setup: - strategy: - fail-fast: false runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 @@ -31,23 +35,11 @@ jobs: - name: Generate matrix id: jsonStep run: | - if [[ "$GITHUB_HEAD_REF" == "" ]]; then - TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} extra) - else - TARGETS=$(./bin/generate_ci_matrix.py ${{inputs.arch}} pr) - fi - echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" - echo "${{inputs.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + TARGETS=$(./bin/generate_ci_matrix.py $INPUT_ARCH --level extra) + echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" + echo "selected_arch=$TARGETS" >> $GITHUB_OUTPUT outputs: - esp32: ${{ steps.jsonStep.outputs.esp32 }} - esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} - esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} - esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} - nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} - rp2040: ${{ steps.jsonStep.outputs.rp2040 }} - rp2350: ${{ steps.jsonStep.outputs.rp2350 }} - stm32: ${{ steps.jsonStep.outputs.stm32 }} - check: ${{ steps.jsonStep.outputs.check }} + selected_arch: ${{ steps.jsonStep.outputs.selected_arch }} version: runs-on: ubuntu-latest @@ -64,101 +56,18 @@ jobs: long: ${{ steps.version.outputs.long }} deb: ${{ steps.version.outputs.deb }} - build-esp32: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32'}} + build: + if: ${{ github.event_name != 'workflow_dispatch' }} needs: [setup, version] strategy: fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + matrix: + build: ${{ fromJson(needs.setup.outputs.selected_arch) }} uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32 - - build-esp32s3: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32s3'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32s3 - - build-esp32c3: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c3'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c3 - - build-esp32c6: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'esp32c6'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c6 - - build-nrf52840: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'nrf52840'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: nrf52840 - - build-rp2040: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2040'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2040 - - build-rp2350: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'rp2350'}} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2350 - - build-stm32: - if: ${{ github.event_name != 'workflow_dispatch' || inputs.arch == 'stm32' }} - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.stm32) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: stm32 + pio_env: ${{ matrix.build.board }} + platform: ${{ matrix.build.arch }} build-debian-src: if: ${{ github.repository == 'meshtastic/firmware' && github.event_name != 'workflow_dispatch' || inputs.arch == 'native' }} @@ -252,18 +161,7 @@ jobs: - rp2350 - stm32 runs-on: ubuntu-latest - needs: - [ - version, - build-esp32, - build-esp32s3, - build-esp32c3, - build-esp32c6, - build-nrf52840, - build-rp2040, - build-rp2350, - build-stm32, - ] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 @@ -332,169 +230,3 @@ jobs: name: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} description: "Download firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" github-token: ${{ secrets.GITHUB_TOKEN }} - - release-artifacts: - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' }} - outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} - needs: - - version - - gather-artifacts - - build-debian-src - - package-pio-deps-native-tft - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - name: Create release - uses: softprops/action-gh-release@v2 - id: create_release - with: - draft: true - prerelease: true - name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha - tag_name: v${{ needs.version.outputs.long }} - body: | - Autogenerated by github action, developer should edit as required before publishing... - - - name: Download source deb - uses: actions/download-artifact@v5 - with: - pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src - merge-multiple: true - path: ./output/debian-src - - - name: Download `native-tft` pio deps - uses: actions/download-artifact@v5 - with: - pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output/pio-deps-native-tft - - - name: Zip Linux sources - working-directory: output - run: | - zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src - zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add Linux sources to GtiHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip - gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - release-firmware: - strategy: - fail-fast: false - matrix: - arch: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' }} - needs: [release-artifacts, version] - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-${{inputs.arch}}-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output - - - name: Display structure of downloaded files - run: ls -lR - - - name: Device scripts permissions - run: | - chmod +x ./output/device-install.sh - chmod +x ./output/device-update.sh - - - name: Zip firmware - run: zip -j -9 -r ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./output - - - uses: actions/download-artifact@v5 - with: - name: debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip - merge-multiple: true - path: ./elfs - - - name: Zip debug elfs - run: zip -j -9 -r ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip ./elfs - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add bins and debug elfs to GitHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip - gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.arch}}-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish-firmware: - runs-on: ubuntu-24.04 - if: ${{ github.event_name == 'workflow_dispatch' }} - needs: [release-firmware, version] - env: - targets: |- - esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./publish - - - name: Publish firmware to meshtastic.github.io - uses: peaceiris/actions-gh-pages@v4 - env: - # On event/* branches, use the event name as the destination prefix - DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }} - with: - deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }} - external_repository: meshtastic/meshtastic.github.io - publish_branch: master - publish_dir: ./publish - destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }} - keep_files: true - user_name: github-actions[bot] - user_email: github-actions[bot]@users.noreply.github.com - commit_message: ${{ needs.version.outputs.long }} - enable_jekyll: true diff --git a/.github/workflows/build_one_target.yml b/.github/workflows/build_one_target.yml index 3c83ce960..ba1d5080f 100644 --- a/.github/workflows/build_one_target.yml +++ b/.github/workflows/build_one_target.yml @@ -3,6 +3,7 @@ name: Build One Target on: workflow_dispatch: inputs: + # trunk-ignore(checkov/CKV_GHA_7) arch: type: choice options: @@ -19,11 +20,13 @@ on: type: string required: false description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets. - # find-target: # type: boolean # default: true # description: 'Find the available targets' + +permissions: read-all + jobs: find-targets: if: ${{ inputs.target == '' }} @@ -51,13 +54,13 @@ jobs: - name: Generate matrix id: jsonStep run: | - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} extra) + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra) echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY echo "Targets:" >> $GITHUB_STEP_SUMMARY - echo $TARGETS | sed 's/[][]//g; s/", "/\n- /g; s/"//g; s/^/- /' >> $GITHUB_STEP_SUMMARY + echo $TARGETS >> $GITHUB_STEP_SUMMARY version: if: ${{ inputs.target != '' }} @@ -75,11 +78,9 @@ jobs: long: ${{ steps.version.outputs.long }} deb: ${{ steps.version.outputs.deb }} - build-arch: + build: if: ${{ inputs.target != '' && inputs.arch != 'native' }} needs: [version] - strategy: - fail-fast: false uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} @@ -165,10 +166,8 @@ jobs: permissions: contents: write pull-requests: write - strategy: - fail-fast: false runs-on: ubuntu-latest - needs: [version, build-arch] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 @@ -237,159 +236,3 @@ jobs: name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }} description: "Download firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation" github-token: ${{ secrets.GITHUB_TOKEN }} - - release-artifacts: - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}} - outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} - needs: - - version - - gather-artifacts - - build-debian-src - - package-pio-deps-native-tft - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - name: Create release - uses: softprops/action-gh-release@v2 - id: create_release - with: - draft: true - prerelease: true - name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha - tag_name: v${{ needs.version.outputs.long }} - body: | - Autogenerated by github action, developer should edit as required before publishing... - - - name: Download source deb - uses: actions/download-artifact@v5 - with: - pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src - merge-multiple: true - path: ./output/debian-src - - - name: Download `native-tft` pio deps - uses: actions/download-artifact@v5 - with: - pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output/pio-deps-native-tft - - - name: Zip Linux sources - working-directory: output - run: | - zip -j -9 -r ./meshtasticd-${{ needs.version.outputs.deb }}-src.zip ./debian-src - zip -9 -r ./platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip ./pio-deps-native-tft - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add Linux sources to GtiHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./output/meshtasticd-${{ needs.version.outputs.deb }}-src.zip - gh release upload v${{ needs.version.outputs.long }} ./output/platformio-deps-native-tft-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - release-firmware: - strategy: - fail-fast: false - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' && inputs.target != ''}} - needs: [release-artifacts, version] - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-*-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./output - - - name: Display structure of downloaded files - run: ls -lR - - - name: Device scripts permissions - run: | - chmod +x ./output/device-install.sh - chmod +x ./output/device-update.sh - - - name: Zip firmware - run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output - - - uses: actions/download-artifact@v5 - with: - pattern: debug-elfs-*-${{ needs.version.outputs.long }}.zip - merge-multiple: true - path: ./elfs - - - name: Zip debug elfs - run: zip -j -9 -r ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./elfs - - # For diagnostics - - name: Display structure of downloaded files - run: ls -lR - - - name: Add bins and debug elfs to GitHub Release - # Only run when targeting master branch with workflow_dispatch - if: ${{ github.ref_name == 'master' }} - run: | - gh release upload v${{ needs.version.outputs.long }} ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip - gh release upload v${{ needs.version.outputs.long }} ./debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - publish-firmware: - runs-on: ubuntu-24.04 - if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' && inputs.target != '' }} - needs: [release-firmware, version] - env: - targets: |- - esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32 - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: 3.x - - - uses: actions/download-artifact@v5 - with: - pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }} - merge-multiple: true - path: ./publish - - - name: Publish firmware to meshtastic.github.io - uses: peaceiris/actions-gh-pages@v4 - env: - # On event/* branches, use the event name as the destination prefix - DEST_PREFIX: ${{ contains(github.ref_name, 'event/') && format('{0}/', github.ref_name) || '' }} - with: - deploy_key: ${{ secrets.DIST_PAGES_DEPLOY_KEY }} - external_repository: meshtastic/meshtastic.github.io - publish_branch: master - publish_dir: ./publish - destination_dir: ${{ env.DEST_PREFIX }}firmware-${{ needs.version.outputs.long }} - keep_files: true - user_name: github-actions[bot] - user_email: github-actions[bot]@users.noreply.github.com - commit_message: ${{ needs.version.outputs.long }} - enable_jekyll: true diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index f61e314a7..812990eca 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -29,17 +29,10 @@ jobs: setup: if: github.repository == 'meshtastic/firmware' strategy: - fail-fast: false + fail-fast: true matrix: arch: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 + - all - check runs-on: ubuntu-24.04 steps: @@ -59,19 +52,13 @@ jobs: if [[ "$GITHUB_HEAD_REF" == "" ]]; then TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) else - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr) + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr) fi - echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" - echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" + echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT + echo "$TARGETS" >> $GITHUB_STEP_SUMMARY outputs: - esp32: ${{ steps.jsonStep.outputs.esp32 }} - esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} - esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} - esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} - nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} - rp2040: ${{ steps.jsonStep.outputs.rp2040 }} - rp2350: ${{ steps.jsonStep.outputs.rp2350 }} - stm32: ${{ steps.jsonStep.outputs.stm32 }} + all: ${{ steps.jsonStep.outputs.all }} check: ${{ steps.jsonStep.outputs.check }} version: @@ -94,7 +81,8 @@ jobs: needs: setup strategy: fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.check) }} + matrix: + check: ${{ fromJson(needs.setup.outputs.check) }} runs-on: ubuntu-latest if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }} @@ -103,96 +91,20 @@ jobs: - name: Build base id: base uses: ./.github/actions/setup-base - - name: Check ${{ matrix.board }} - run: bin/check-all.sh ${{ matrix.board }} + - name: Check ${{ matrix.check.board }} + run: bin/check-all.sh ${{ matrix.check.board }} - build-esp32: + build: needs: [setup, version] strategy: fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + matrix: + build: ${{ fromJson(needs.setup.outputs.all) }} uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32 - - build-esp32s3: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32s3 - - build-esp32c3: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c3 - - build-esp32c6: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c6 - - build-nrf52840: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: nrf52840 - - build-rp2040: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2040 - - build-rp2350: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2350 - - build-stm32: - needs: [setup, version] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.setup.outputs.stm32) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: stm32 + pio_env: ${{ matrix.build.board }} + platform: ${{ matrix.build.platform }} build-debian-src: if: github.repository == 'meshtastic/firmware' @@ -203,7 +115,7 @@ jobs: secrets: inherit package-pio-deps-native-tft: - if: ${{ github.event_name == 'workflow_dispatch' }} + if: ${{ github.repository == 'meshtastic/firmware' && github.event_name == 'workflow_dispatch' }} uses: ./.github/workflows/package_pio_deps.yml with: pio_env: native-tft @@ -288,18 +200,7 @@ jobs: - rp2350 - stm32 runs-on: ubuntu-latest - needs: - [ - version, - build-esp32, - build-esp32s3, - build-esp32c3, - build-esp32c6, - build-nrf52840, - build-rp2040, - build-rp2350, - build-stm32, - ] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 diff --git a/.github/workflows/merge_queue.yml b/.github/workflows/merge_queue.yml index 7f397ce18..79e8b0803 100644 --- a/.github/workflows/merge_queue.yml +++ b/.github/workflows/merge_queue.yml @@ -7,23 +7,13 @@ on: # Merge group is a special trigger that is used to trigger the workflow when a merge group is created. merge_group: -env: - FAIL_FAST_PER_ARCH: true - jobs: setup: strategy: fail-fast: true matrix: arch: - - esp32 - - esp32s3 - - esp32c3 - - esp32c6 - - nrf52840 - - rp2040 - - rp2350 - - stm32 + - all - check runs-on: ubuntu-24.04 steps: @@ -39,19 +29,12 @@ jobs: if [[ "$GITHUB_HEAD_REF" == "" ]]; then TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}}) else - TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} pr) + TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level pr) fi - echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF Targets: $TARGETS" - echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT + echo "Name: $GITHUB_REF_NAME Base: $GITHUB_BASE_REF Ref: $GITHUB_REF" + echo "${{matrix.arch}}=$TARGETS" >> $GITHUB_OUTPUT outputs: - esp32: ${{ steps.jsonStep.outputs.esp32 }} - esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }} - esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }} - esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }} - nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }} - rp2040: ${{ steps.jsonStep.outputs.rp2040 }} - rp2350: ${{ steps.jsonStep.outputs.rp2350 }} - stm32: ${{ steps.jsonStep.outputs.stm32 }} + all: ${{ steps.jsonStep.outputs.all }} check: ${{ steps.jsonStep.outputs.check }} version: @@ -73,7 +56,8 @@ jobs: needs: setup strategy: fail-fast: true - matrix: ${{ fromJson(needs.setup.outputs.check) }} + matrix: + check: ${{ fromJson(needs.setup.outputs.check) }} runs-on: ubuntu-latest if: ${{ github.event_name != 'workflow_dispatch' }} @@ -82,96 +66,19 @@ jobs: - name: Build base id: base uses: ./.github/actions/setup-base - - name: Check ${{ matrix.board }} - run: bin/check-all.sh ${{ matrix.board }} + - name: Check ${{ matrix.check.board }} + run: bin/check-all.sh ${{ matrix.check.board }} - build-esp32: + build: needs: [setup, version] strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32) }} + matrix: + build: ${{ fromJson(needs.setup.outputs.all) }} uses: ./.github/workflows/build_firmware.yml with: version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32 - - build-esp32s3: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32s3 - - build-esp32c3: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c3 - - build-esp32c6: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: esp32c6 - - build-nrf52840: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: nrf52840 - - build-rp2040: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.rp2040) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2040 - - build-rp2350: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.rp2350) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: rp2350 - - build-stm32: - needs: [setup, version] - strategy: - fail-fast: ${{ vars.FAIL_FAST_PER_ARCH == true }} - matrix: ${{ fromJson(needs.setup.outputs.stm32) }} - uses: ./.github/workflows/build_firmware.yml - with: - version: ${{ needs.version.outputs.long }} - pio_env: ${{ matrix.board }} - platform: stm32 + pio_env: ${{ matrix.build.board }} + platform: ${{ matrix.build.platform }} build-debian-src: if: github.repository == 'meshtastic/firmware' @@ -260,18 +167,7 @@ jobs: - rp2350 - stm32 runs-on: ubuntu-latest - needs: - [ - version, - build-esp32, - build-esp32s3, - build-esp32c3, - build-esp32c6, - build-nrf52840, - build-rp2040, - build-rp2350, - build-stm32, - ] + needs: [version, build] steps: - name: Checkout code uses: actions/checkout@v5 diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py index aaa76aa45..b4c18c05b 100755 --- a/bin/generate_ci_matrix.py +++ b/bin/generate_ci_matrix.py @@ -1,28 +1,32 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """Generate the CI matrix.""" +import argparse import json -import sys -import random import re from platformio.project.config import ProjectConfig -options = sys.argv[1:] +parser = argparse.ArgumentParser(description="Generate the CI matrix") +parser.add_argument("platform", help="Platform to build for") +parser.add_argument( + "--level", + choices=["extra", "pr"], + nargs="*", + default=[], + help="Board level to build for (omit for full release boards)", +) +args = parser.parse_args() outlist = [] -if len(options) < 1: - print(json.dumps(outlist)) - exit(1) - cfg = ProjectConfig.get_instance() pio_envs = cfg.envs() # Gather all PlatformIO environments for filtering later all_envs = [] for pio_env in pio_envs: - env_build_flags = cfg.get(f"env:{pio_env}", 'build_flags') + env_build_flags = cfg.get(f"env:{pio_env}", "build_flags") env_platform = None for flag in env_build_flags: # Extract the platform from the build flags @@ -37,36 +41,35 @@ for pio_env in pio_envs: exit(1) # Store env details as a dictionary, and add to 'all_envs' list env = { - 'name': pio_env, - 'platform': env_platform, - 'board_level': cfg.get(f"env:{pio_env}", 'board_level', default=None), - 'board_check': bool(cfg.get(f"env:{pio_env}", 'board_check', default=False)) + "ci": {"board": pio_env, "platform": env_platform}, + "board_level": cfg.get(f"env:{pio_env}", "board_level", default=None), + "board_check": bool(cfg.get(f"env:{pio_env}", "board_check", default=False)), } all_envs.append(env) # Filter outputs based on options # Check is mutually exclusive with other options (except 'pr') -if "check" in options: +if "check" in args.platform: for env in all_envs: - if env['board_check']: - if "pr" in options: - if env['board_level'] == 'pr': - outlist.append(env['name']) + if env["board_check"]: + if "pr" in args.level: + if env["board_level"] == "pr": + outlist.append(env["ci"]) else: - outlist.append(env['name']) + outlist.append(env["ci"]) # Filter (non-check) builds by platform else: for env in all_envs: - if options[0] == env['platform']: + if args.platform == env["ci"]["platform"] or args.platform == "all": # Always include board_level = 'pr' - if env['board_level'] == 'pr': - outlist.append(env['name']) + if env["board_level"] == "pr": + outlist.append(env["ci"]) # Include board_level = 'extra' when requested - elif "extra" in options and env['board_level'] == "extra": - outlist.append(env['name']) + elif "extra" in args.level and env["board_level"] == "extra": + outlist.append(env["ci"]) # If no board level is specified, include in release builds (not PR) - elif "pr" not in options and not env['board_level']: - outlist.append(env['name']) + elif "pr" not in args.level and not env["board_level"]: + outlist.append(env["ci"]) # Return as a JSON list print(json.dumps(outlist)) diff --git a/variants/native/portduino-buildroot/platformio.ini b/variants/native/portduino-buildroot/platformio.ini index d1bd39e10..a3d0f4639 100644 --- a/variants/native/portduino-buildroot/platformio.ini +++ b/variants/native/portduino-buildroot/platformio.ini @@ -4,5 +4,6 @@ extends = portduino_base ; environment variable in the buildroot environment. build_flags = ${portduino_base.build_flags} -O0 -I variants/native/portduino-buildroot board = buildroot +board_level = extra lib_deps = ${portduino_base.lib_deps} build_src_filter = ${portduino_base.build_src_filter} \ No newline at end of file diff --git a/variants/native/portduino/platformio.ini b/variants/native/portduino/platformio.ini index 61eadb459..2ccdfbbfc 100644 --- a/variants/native/portduino/platformio.ini +++ b/variants/native/portduino/platformio.ini @@ -3,6 +3,7 @@ extends = portduino_base build_flags = ${portduino_base.build_flags} -I variants/native/portduino -I /usr/include board = cross_platform +board_level = extra lib_deps = ${portduino_base.lib_deps} melopero/Melopero RV3028@^1.1.0 @@ -50,7 +51,6 @@ build_type = release lib_deps = ${native_base.lib_deps} ${device-ui_base.lib_deps} -board_level = extra build_flags = ${native_base.build_flags} -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -D RAM_SIZE=8192 -D USE_FRAMEBUFFER=1 @@ -79,7 +79,6 @@ build_type = debug lib_deps = ${native_base.lib_deps} ${device-ui_base.lib_deps} -board_level = extra build_flags = ${native_base.build_flags} -O0 -fsanitize=address -lX11 -linput -lxkbcommon -D DEBUG_HEAP -D RAM_SIZE=16384 From 68a2c4adda4236bcc3e260751617b5ca2106200d Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 7 Oct 2025 16:11:36 +1100 Subject: [PATCH 18/27] Run Integration test in simulator mode (#8232) (#8242) Co-authored-by: Jonathan Bennett --- .github/workflows/test_native.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml index 6b788f4c7..9cf1c9e53 100644 --- a/.github/workflows/test_native.yml +++ b/.github/workflows/test_native.yml @@ -40,7 +40,7 @@ jobs: - name: Integration test run: | - .pio/build/coverage/program & + .pio/build/coverage/program -s & PID=$! timeout 20 bash -c "until ls -al /proc/$PID/fd | grep socket; do sleep 1; done" echo "Simulator started, launching python test..." From b214f09ca1b8b63df62ee8733d7d4138ff6b3979 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:34:00 +1100 Subject: [PATCH 19/27] Update meshtastic/web to v2.6.6 (#7583) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- bin/web.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/web.version b/bin/web.version index e46a05b19..952f449f1 100644 --- a/bin/web.version +++ b/bin/web.version @@ -1 +1 @@ -2.6.4 \ No newline at end of file +2.6.6 \ No newline at end of file From b696e083f39dc76b3dd364838671ae6881d74c83 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 7 Oct 2025 00:37:04 -0500 Subject: [PATCH 20/27] Log antispam (#8241) * less power spam * Don't warn about the first 4 GPS checksum failures --- src/Power.cpp | 7 +++++-- src/gps/GPS.cpp | 8 ++++++-- src/power.h | 1 + 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Power.cpp b/src/Power.cpp index 7de82b8d6..bb5d16d10 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -828,8 +828,11 @@ void Power::readPowerStatus() // Notify any status instances that are observing us const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent); - LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(), - powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); + if (millis() > lastLogTime + 50 * 1000) { + LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), + powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); + lastLogTime = millis(); + } newStatus.notifyObservers(&powerStatus2); #ifdef DEBUG_HEAP if (lastheap != memGet.getFreeHeap()) { diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 0487d1fea..36ec7c580 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1589,8 +1589,12 @@ bool GPS::lookForLocation() #ifndef TINYGPS_OPTION_NO_STATISTICS if (reader.failedChecksum() > lastChecksumFailCount) { - LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount, - reader.failedChecksum()); +// In a GPS_DEBUG build we want to log all of these. In production, we only care if there are many of them. +#ifndef GPS_DEBUG + if (reader.failedChecksum() > 4) +#endif + LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount, + reader.failedChecksum()); lastChecksumFailCount = reader.failedChecksum(); } #endif diff --git a/src/power.h b/src/power.h index 23eb95064..cdbdd3ea0 100644 --- a/src/power.h +++ b/src/power.h @@ -138,6 +138,7 @@ class Power : private concurrency::OSThread void reboot(); // open circuit voltage lookup table uint8_t low_voltage_counter; + int32_t lastLogTime = 0; #ifdef DEBUG_HEAP uint32_t lastheap; #endif From 1d5b3438363190226e5bb294f404766ab12299ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:56:13 +1100 Subject: [PATCH 21/27] Update meshtastic/device-ui digest to e564d78 (#8235) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 4ae2da54f..2e101a8a2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -120,7 +120,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/f920b1273df750c2e3e01385d3ba30553b913afa.zip + https://github.com/meshtastic/device-ui/archive/e564d78ae1a7e9a225aaf4a73b1cb84c549f510f.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From 668cc9fd64513fc41b94627bd6fc7c7ec3b69e63 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 7 Oct 2025 05:58:39 -0500 Subject: [PATCH 22/27] Do slightly better at threading the search for GPS hardware (#8240) * Do slightly better at threading the search for GPS hardware * Formatting and minor logic fix * Remove now-spam GPS Log messages --------- Co-authored-by: Ben Meadors --- src/gps/GPS.cpp | 316 ++++++++++++++++++++++++++---------------------- src/gps/GPS.h | 2 + 2 files changed, 176 insertions(+), 142 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 36ec7c580..e70eed362 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -506,10 +506,9 @@ bool GPS::setup() delay(1000); #endif if (probeTries < GPS_PROBETRIES) { - LOG_DEBUG("Probe for GPS at %d", serialSpeeds[speedSelect]); gnssModel = probe(serialSpeeds[speedSelect]); if (gnssModel == GNSS_MODEL_UNKNOWN) { - if (++speedSelect == array_count(serialSpeeds)) { + if (currentStep == 0 && ++speedSelect == array_count(serialSpeeds)) { speedSelect = 0; ++probeTries; } @@ -518,10 +517,9 @@ bool GPS::setup() // Rare Serial Speeds #ifndef CONFIG_IDF_TARGET_ESP32C6 if (probeTries == GPS_PROBETRIES) { - LOG_DEBUG("Probe for GPS at %d", rareSerialSpeeds[speedSelect]); gnssModel = probe(rareSerialSpeeds[speedSelect]); if (gnssModel == GNSS_MODEL_UNKNOWN) { - if (++speedSelect == array_count(rareSerialSpeeds)) { + if (currentStep == 0 && ++speedSelect == array_count(rareSerialSpeeds)) { LOG_WARN("Give up on GPS probe and set to %d", GPS_BAUDRATE); return true; } @@ -1094,7 +1092,7 @@ int32_t GPS::runOnce() return disable(); } if (!setup()) - return 2000; // Setup failed, re-run in two seconds + return currentDelay; // Setup failed, re-run in two seconds // We have now loaded our saved preferences from flash if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { @@ -1218,163 +1216,197 @@ static const char *DETECTED_MESSAGE = "%s detected"; GnssModel_t GPS::probe(int serialSpeed) { + uint8_t buffer[768] = {0}; + + switch (currentStep) { + case 0: { #if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL) - _serial_gps->end(); - _serial_gps->begin(serialSpeed); + _serial_gps->end(); + _serial_gps->begin(serialSpeed); #elif defined(ARCH_RP2040) - _serial_gps->end(); - _serial_gps->setFIFOSize(256); - _serial_gps->begin(serialSpeed); + _serial_gps->end(); + _serial_gps->setFIFOSize(256); + _serial_gps->begin(serialSpeed); #else - if (_serial_gps->baudRate() != serialSpeed) { - LOG_DEBUG("Set Baud to %i", serialSpeed); - _serial_gps->updateBaudRate(serialSpeed); - } + if (_serial_gps->baudRate() != serialSpeed) { + LOG_DEBUG("Set GPS Baud to %i", serialSpeed); + _serial_gps->updateBaudRate(serialSpeed); + } #endif - memset(&ublox_info, 0, sizeof(ublox_info)); - uint8_t buffer[768] = {0}; - delay(100); + memset(&ublox_info, 0, sizeof(ublox_info)); + delay(100); - // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices) - _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"); - delay(20); - // Close NMEA sequences on Ublox - _serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n"); - _serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n"); - _serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n"); - delay(20); - // Close NMEA sequences on CM121 - _serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n"); - _serial_gps->write("$CFGMSG,0,2,0,1*18\r\n"); - _serial_gps->write("$CFGMSG,0,3,0,1*19\r\n"); - delay(20); - - // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121 - std::vector unicore = { - {"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}}; - PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500); - - std::vector atgm = { - {"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H}, - /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */ - {"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}}; - PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500); - - /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */ - _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume - _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume - _serial_gps->write("$PAIR513*3D\r\n"); // save configuration - std::vector airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335}, - {"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352}, - {"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}}; - PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000); - - PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500); - PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500); - - // Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms - _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n"); - delay(20); - std::vector mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D}, - {"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B}, - {"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B}, - {"L80", "_3339_", GNSS_MODEL_MTK_L76B}}; - - PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500); - - uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00}; - UBXChecksum(cfg_rate, sizeof(cfg_rate)); - clearBuffer(); - _serial_gps->write(cfg_rate, sizeof(cfg_rate)); - // Check that the returned response class and message ID are correct - GPS_RESPONSE response = getACK(0x06, 0x08, 750); - if (response == GNSS_RESPONSE_NONE) { - LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed); + // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices) + _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"); + delay(20); + // Close NMEA sequences on Ublox + _serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n"); + _serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n"); + _serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n"); + delay(20); + // Close NMEA sequences on CM121 + _serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n"); + _serial_gps->write("$CFGMSG,0,2,0,1*18\r\n"); + _serial_gps->write("$CFGMSG,0,3,0,1*19\r\n"); + currentDelay = 20; + currentStep = 1; return GNSS_MODEL_UNKNOWN; - } else if (response == GNSS_RESPONSE_FRAME_ERRORS) { - LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed); } + case 1: { - memset(buffer, 0, sizeof(buffer)); - uint8_t _message_MONVER[8] = { - 0xB5, 0x62, // Sync message for UBX protocol - 0x0A, 0x04, // Message class and ID (UBX-MON-VER) - 0x00, 0x00, // Length of payload (we're asking for an answer, so no payload) - 0x00, 0x00 // Checksum - }; - // Get Ublox gnss module hardware and software info - UBXChecksum(_message_MONVER, sizeof(_message_MONVER)); - clearBuffer(); - _serial_gps->write(_message_MONVER, sizeof(_message_MONVER)); + // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121 + std::vector unicore = { + {"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}}; + PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500); + currentDelay = 20; + currentStep = 2; + return GNSS_MODEL_UNKNOWN; + } + case 2: { + std::vector atgm = { + {"ATGM336H", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H}, + /* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS)) based on AT6558 */ + {"ATGM332D", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H}}; + PROBE_FAMILY("ATGM33xx Family", "$PCAS06,1*1A", atgm, 500); + currentDelay = 20; + currentStep = 3; + return GNSS_MODEL_UNKNOWN; + } + case 3: { + /* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */ + _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume + _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume + _serial_gps->write("$PAIR513*3D\r\n"); // save configuration + std::vector airoha = {{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335}, + {"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352}, + {"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352}}; + PROBE_FAMILY("Airoha Family", "$PAIR021*39", airoha, 1000); + currentDelay = 20; + currentStep = 4; + return GNSS_MODEL_UNKNOWN; + } + case 4: { + PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500); + PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500); + currentDelay = 20; + currentStep = 5; + return GNSS_MODEL_UNKNOWN; + } + case 5: { - uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200); - if (len) { - uint16_t position = 0; - for (int i = 0; i < 30; i++) { - ublox_info.swVersion[i] = buffer[position]; - position++; - } - for (int i = 0; i < 10; i++) { - ublox_info.hwVersion[i] = buffer[position]; - position++; - } + // Close all NMEA sentences, valid for MTK3333 and MTK3339 platforms + _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n"); + delay(20); + std::vector mtk = {{"L76B", "Quectel-L76B", GNSS_MODEL_MTK_L76B}, {"PA1010D", "1010D", GNSS_MODEL_MTK_PA1010D}, + {"PA1616S", "1616S", GNSS_MODEL_MTK_PA1616S}, {"LS20031", "MC-1513", GNSS_MODEL_MTK_L76B}, + {"L96", "Quectel-L96", GNSS_MODEL_MTK_L76B}, {"L80-R", "_3337_", GNSS_MODEL_MTK_L76B}, + {"L80", "_3339_", GNSS_MODEL_MTK_L76B}}; - while (len >= position + 30) { - for (int i = 0; i < 30; i++) { - ublox_info.extension[ublox_info.extensionNo][i] = buffer[position]; - position++; - } - ublox_info.extensionNo++; - if (ublox_info.extensionNo > 9) - break; - } - - LOG_DEBUG("Module Info : "); - LOG_DEBUG("Soft version: %s", ublox_info.swVersion); - LOG_DEBUG("Hard version: %s", ublox_info.hwVersion); - LOG_DEBUG("Extensions:%d", ublox_info.extensionNo); - for (int i = 0; i < ublox_info.extensionNo; i++) { - LOG_DEBUG(" %s", ublox_info.extension[i]); + PROBE_FAMILY("MTK Family", "$PMTK605*31", mtk, 500); + currentDelay = 20; + currentStep = 6; + return GNSS_MODEL_UNKNOWN; + } + case 6: { + uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00}; + UBXChecksum(cfg_rate, sizeof(cfg_rate)); + clearBuffer(); + _serial_gps->write(cfg_rate, sizeof(cfg_rate)); + // Check that the returned response class and message ID are correct + GPS_RESPONSE response = getACK(0x06, 0x08, 750); + if (response == GNSS_RESPONSE_NONE) { + LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed); + currentDelay = 2000; + currentStep = 0; + return GNSS_MODEL_UNKNOWN; + } else if (response == GNSS_RESPONSE_FRAME_ERRORS) { + LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed); } memset(buffer, 0, sizeof(buffer)); + uint8_t _message_MONVER[8] = { + 0xB5, 0x62, // Sync message for UBX protocol + 0x0A, 0x04, // Message class and ID (UBX-MON-VER) + 0x00, 0x00, // Length of payload (we're asking for an answer, so no payload) + 0x00, 0x00 // Checksum + }; + // Get Ublox gnss module hardware and software info + UBXChecksum(_message_MONVER, sizeof(_message_MONVER)); + clearBuffer(); + _serial_gps->write(_message_MONVER, sizeof(_message_MONVER)); - // tips: extensionNo field is 0 on some 6M GNSS modules - for (int i = 0; i < ublox_info.extensionNo; ++i) { - if (!strncmp(ublox_info.extension[i], "MOD=", 4)) { - strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer)); - } else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) { - char *ptr = nullptr; - memset(buffer, 0, sizeof(buffer)); - strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer)); - LOG_DEBUG("Protocol Version:%s", (char *)buffer); - if (strlen((char *)buffer)) { - ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10); - LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version); - } else { - ublox_info.protocol_version = 0; + uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200); + if (len) { + uint16_t position = 0; + for (int i = 0; i < 30; i++) { + ublox_info.swVersion[i] = buffer[position]; + position++; + } + for (int i = 0; i < 10; i++) { + ublox_info.hwVersion[i] = buffer[position]; + position++; + } + + while (len >= position + 30) { + for (int i = 0; i < 30; i++) { + ublox_info.extension[ublox_info.extensionNo][i] = buffer[position]; + position++; + } + ublox_info.extensionNo++; + if (ublox_info.extensionNo > 9) + break; + } + + LOG_DEBUG("Module Info : "); + LOG_DEBUG("Soft version: %s", ublox_info.swVersion); + LOG_DEBUG("Hard version: %s", ublox_info.hwVersion); + LOG_DEBUG("Extensions:%d", ublox_info.extensionNo); + for (int i = 0; i < ublox_info.extensionNo; i++) { + LOG_DEBUG(" %s", ublox_info.extension[i]); + } + + memset(buffer, 0, sizeof(buffer)); + + // tips: extensionNo field is 0 on some 6M GNSS modules + for (int i = 0; i < ublox_info.extensionNo; ++i) { + if (!strncmp(ublox_info.extension[i], "MOD=", 4)) { + strncpy((char *)buffer, &(ublox_info.extension[i][4]), sizeof(buffer)); + } else if (!strncmp(ublox_info.extension[i], "PROTVER", 7)) { + char *ptr = nullptr; + memset(buffer, 0, sizeof(buffer)); + strncpy((char *)buffer, &(ublox_info.extension[i][8]), sizeof(buffer)); + LOG_DEBUG("Protocol Version:%s", (char *)buffer); + if (strlen((char *)buffer)) { + ublox_info.protocol_version = strtoul((char *)buffer, &ptr, 10); + LOG_DEBUG("ProtVer=%d", ublox_info.protocol_version); + } else { + ublox_info.protocol_version = 0; + } } } - } - if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) { - LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6"); - return GNSS_MODEL_UBLOX6; - } else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) { - LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7"); - return GNSS_MODEL_UBLOX7; - } else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) { - LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8"); - return GNSS_MODEL_UBLOX8; - } else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) { - LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9"); - return GNSS_MODEL_UBLOX9; - } else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) { - LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10"); - return GNSS_MODEL_UBLOX10; + if (strncmp(ublox_info.hwVersion, "00040007", 8) == 0) { + LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6"); + return GNSS_MODEL_UBLOX6; + } else if (strncmp(ublox_info.hwVersion, "00070000", 8) == 0) { + LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7"); + return GNSS_MODEL_UBLOX7; + } else if (strncmp(ublox_info.hwVersion, "00080000", 8) == 0) { + LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8"); + return GNSS_MODEL_UBLOX8; + } else if (strncmp(ublox_info.hwVersion, "00190000", 8) == 0) { + LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9"); + return GNSS_MODEL_UBLOX9; + } else if (strncmp(ublox_info.hwVersion, "000A0000", 8) == 0) { + LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10"); + return GNSS_MODEL_UBLOX10; + } } } + } LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed); + currentDelay = 2000; + currentStep = 0; return GNSS_MODEL_UNKNOWN; } diff --git a/src/gps/GPS.h b/src/gps/GPS.h index cba767460..f3bcf37f5 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -151,6 +151,8 @@ class GPS : private concurrency::OSThread TinyGPSPlus reader; uint8_t fixQual = 0; // fix quality from GPGGA uint32_t lastChecksumFailCount = 0; + uint8_t currentStep = 0; + int32_t currentDelay = 2000; #ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS // (20210908) TinyGps++ can only read the GPGSA "FIX TYPE" field From 5bcc47dddb6aa339e51651a05ff7f2bdc6e27bd2 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 7 Oct 2025 22:00:09 +1100 Subject: [PATCH 23/27] Revert "develop --> Master" (#8244) --- .github/workflows/main_matrix.yml | 6 ++ src/Power.cpp | 7 +- src/gps/GPS.cpp | 8 +-- src/mesh/FloodingRouter.cpp | 102 ++++++++++++++++++----------- src/mesh/FloodingRouter.h | 15 ++--- src/mesh/NextHopRouter.cpp | 105 +++++++++++++++--------------- src/mesh/NextHopRouter.h | 6 +- src/mesh/PacketHistory.cpp | 1 + src/mesh/http/ContentHandler.cpp | 5 -- src/mesh/http/WebServer.cpp | 35 +--------- src/mesh/http/WebServer.h | 4 -- src/modules/TraceRouteModule.cpp | 64 ------------------ src/modules/TraceRouteModule.h | 6 -- src/power.h | 1 - 14 files changed, 136 insertions(+), 229 deletions(-) diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index 887bf3081..812990eca 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -27,6 +27,7 @@ on: jobs: setup: + if: github.repository == 'meshtastic/firmware' strategy: fail-fast: true matrix: @@ -41,6 +42,10 @@ jobs: python-version: 3.x cache: pip - run: pip install -U platformio + - name: Uncomment build epoch + shell: bash + run: | + sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini - name: Generate matrix id: jsonStep run: | @@ -57,6 +62,7 @@ jobs: check: ${{ steps.jsonStep.outputs.check }} version: + if: github.repository == 'meshtastic/firmware' runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 diff --git a/src/Power.cpp b/src/Power.cpp index bb5d16d10..7de82b8d6 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -828,11 +828,8 @@ void Power::readPowerStatus() // Notify any status instances that are observing us const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent); - if (millis() > lastLogTime + 50 * 1000) { - LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), - powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); - lastLogTime = millis(); - } + LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(), + powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); newStatus.notifyObservers(&powerStatus2); #ifdef DEBUG_HEAP if (lastheap != memGet.getFreeHeap()) { diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 36ec7c580..0487d1fea 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1589,12 +1589,8 @@ bool GPS::lookForLocation() #ifndef TINYGPS_OPTION_NO_STATISTICS if (reader.failedChecksum() > lastChecksumFailCount) { -// In a GPS_DEBUG build we want to log all of these. In production, we only care if there are many of them. -#ifndef GPS_DEBUG - if (reader.failedChecksum() > 4) -#endif - LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount, - reader.failedChecksum()); + LOG_WARN("%u new GPS checksum failures, for a total of %u", reader.failedChecksum() - lastChecksumFailCount, + reader.failedChecksum()); lastChecksumFailCount = reader.failedChecksum(); } #endif diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 032be241b..1d8ac247f 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -31,8 +31,33 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) wasSeenRecently(p, true, nullptr, nullptr, &wasUpgraded); // Updates history; returns false when an upgrade is detected // Handle hop_limit upgrade scenario for rebroadcasters - if (wasUpgraded && perhapsHandleUpgradedPacket(p)) { - return true; // we handled it, so stop processing + // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages + if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) { + // wasSeenRecently() reports false in upgrade cases so we handle replacement before the duplicate short-circuit + // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to + // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead. + uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining + if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) { + LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id, + p->hop_limit, dropThreshold); + + if (nodeDB) + nodeDB->updateFrom(*p); +#if !MESHTASTIC_EXCLUDE_TRACEROUTE + if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && + p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP) + traceRouteModule->processUpgradedPacket(*p); +#endif + + perhapsRebroadcast(p); + + // We already enqueued the improved copy, so make sure the incoming packet stops here. + return true; + } + + // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid + // delivering the same packet to applications/phone twice with different hop limits. + seenRecently = true; } if (seenRecently) { @@ -45,10 +70,8 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) if (isRepeated) { LOG_DEBUG("Repeated reliable tx"); // Check if it's still in the Tx queue, if not, we have to relay it again - if (!findInTxQueue(p->from, p->id)) { - reprocessPacket(p); + if (!findInTxQueue(p->from, p->id)) perhapsRebroadcast(p); - } } else { perhapsCancelDupe(p); } @@ -59,40 +82,6 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) return Router::shouldFilterReceived(p); } -bool FloodingRouter::perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p) -{ - // isRebroadcaster() is duplicated in perhapsRebroadcast(), but this avoids confusing log messages - if (isRebroadcaster() && iface && p->hop_limit > 0) { - // If we overhear a duplicate copy of the packet with more hops left than the one we are waiting to - // rebroadcast, then remove the packet currently sitting in the TX queue and use this one instead. - uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining - if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) { - LOG_DEBUG("Processing upgraded packet 0x%08x for rebroadcast with hop limit %d (dropping queued < %d)", p->id, - p->hop_limit, dropThreshold); - - reprocessPacket(p); - perhapsRebroadcast(p); - - rxDupe++; - // We already enqueued the improved copy, so make sure the incoming packet stops here. - return true; - } - } - - return false; -} - -void FloodingRouter::reprocessPacket(const meshtastic_MeshPacket *p) -{ - if (nodeDB) - nodeDB->updateFrom(*p); -#if !MESHTASTIC_EXCLUDE_TRACEROUTE - if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && - p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP) - traceRouteModule->processUpgradedPacket(*p); -#endif -} - bool FloodingRouter::roleAllowsCancelingDupe(const meshtastic_MeshPacket *p) { if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || @@ -132,6 +121,41 @@ bool FloodingRouter::isRebroadcaster() config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE; } +void FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p) +{ + if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) { + if (p->id != 0) { + if (isRebroadcaster()) { + meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it + + // Use shared logic to determine if hop_limit should be decremented + if (shouldDecrementHopLimit(p)) { + tosend->hop_limit--; // bump down the hop count + } else { + LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE flood: preserving hop_limit"); + } +#if USERPREFS_EVENT_MODE + if (tosend->hop_limit > 2) { + // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away. + tosend->hop_start -= (tosend->hop_limit - 2); + tosend->hop_limit = 2; + } +#endif + + tosend->next_hop = NO_NEXT_HOP_PREFERENCE; // this should already be the case, but just in case + + LOG_INFO("Rebroadcast received floodmsg"); + // Note: we are careful to resend using the original senders node id + send(tosend); + } else { + LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE"); + } + } else { + LOG_DEBUG("Ignore 0 id broadcast"); + } + } +} + void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) { bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index e8a2e9685..eaf71d294 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -27,6 +27,10 @@ */ class FloodingRouter : public Router { + private: + /* Check if we should rebroadcast this packet, and do so if needed */ + void perhapsRebroadcast(const meshtastic_MeshPacket *p); + public: /** * Constructor @@ -55,17 +59,6 @@ class FloodingRouter : public Router */ virtual void sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c) override; - /* Check if we should rebroadcast this packet, and do so if needed */ - virtual bool perhapsRebroadcast(const meshtastic_MeshPacket *p) = 0; - - /* Check if we should handle an upgraded packet (with higher hop_limit) - * @return true if we handled it (so stop processing) - */ - bool perhapsHandleUpgradedPacket(const meshtastic_MeshPacket *p); - - /* Call when we receive a packet that needs some reprocessing, but afterwards should be filtered */ - void reprocessPacket(const meshtastic_MeshPacket *p); - // Return false for roles like ROUTER which should always rebroadcast even when we've heard another rebroadcast of // the same packet bool roleAllowsCancelingDupe(const meshtastic_MeshPacket *p); diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp index afdb4d096..0461d7eb6 100644 --- a/src/mesh/NextHopRouter.cpp +++ b/src/mesh/NextHopRouter.cpp @@ -43,8 +43,31 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) &wasUpgraded); // Updates history; returns false when an upgrade is detected // Handle hop_limit upgrade scenario for rebroadcasters - if (wasUpgraded && perhapsHandleUpgradedPacket(p)) { - return true; // we handled it, so stop processing + // isRebroadcaster() is duplicated in perhapsRelay(), but this avoids confusing log messages + if (wasUpgraded && isRebroadcaster() && iface && p->hop_limit > 0) { + // Upgrade detection bypasses the duplicate short-circuit so we replace the queued packet before exiting + uint8_t dropThreshold = p->hop_limit; // remove queued packets that have fewer hops remaining + if (iface->removePendingTXPacket(getFrom(p), p->id, dropThreshold)) { + LOG_DEBUG("Processing upgraded packet 0x%08x for relay with hop limit %d (dropping queued < %d)", p->id, p->hop_limit, + dropThreshold); + + if (nodeDB) + nodeDB->updateFrom(*p); +#if !MESHTASTIC_EXCLUDE_TRACEROUTE + if (traceRouteModule && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && + p->decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP) + traceRouteModule->processUpgradedPacket(*p); +#endif + + perhapsRelay(p); + + // We already enqueued the improved copy, so make sure the incoming packet stops here. + return true; + } + + // No queue entry was replaced by this upgraded copy, so treat it as a duplicate to avoid + // delivering the same packet to applications/phone twice with different hop limits. + seenRecently = true; } if (seenRecently) { @@ -59,20 +82,14 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p) if (wasFallback) { LOG_INFO("Fallback to flooding from relay_node=0x%x", p->relay_node); // Check if it's still in the Tx queue, if not, we have to relay it again - if (!findInTxQueue(p->from, p->id)) { - reprocessPacket(p); - perhapsRebroadcast(p); - } + if (!findInTxQueue(p->from, p->id)) + perhapsRelay(p); } else { bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit; // If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again if (isRepeated) { - if (!findInTxQueue(p->from, p->id)) { - reprocessPacket(p); - if (!perhapsRebroadcast(p) && isToUs(p) && p->want_ack) { - sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0); - } - } + if (!findInTxQueue(p->from, p->id) && !perhapsRelay(p) && isToUs(p) && p->want_ack) + sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0); } else if (!weWereNextHop) { perhapsCancelDupe(p); // If it's a dupe, cancel relay if we were not explicitly asked to relay } @@ -90,14 +107,13 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0 || p->decoded.reply_id != 0); if (isAckorReply) { - // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" - // is not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the - // destination + // Update next-hop for the original transmitter of this successful transmission to the relay node, but ONLY if "from" is + // not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the destination if (p->from != 0) { meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from); if (origTx) { - // Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came - // directly from the destination + // Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came directly + // from the destination bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to); bool weWereSoleRelayer = false; bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer); @@ -118,49 +134,34 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast } } - perhapsRebroadcast(p); + perhapsRelay(p); // handle the packet as normal Router::sniffReceived(p, c); } -/* Check if we should be rebroadcasting this packet if so, do so. */ -bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p) +/* Check if we should be relaying this packet if so, do so. */ +bool NextHopRouter::perhapsRelay(const meshtastic_MeshPacket *p) { if (!isToUs(p) && !isFromUs(p) && p->hop_limit > 0) { - if (p->id != 0) { + if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) { if (isRebroadcaster()) { - if (p->next_hop == NO_NEXT_HOP_PREFERENCE || p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum())) { - meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it - LOG_INFO("Rebroadcast received message coming from %x", p->relay_node); + meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it + LOG_INFO("Relaying received message coming from %x", p->relay_node); - // Use shared logic to determine if hop_limit should be decremented - if (shouldDecrementHopLimit(p)) { - tosend->hop_limit--; // bump down the hop count - } else { - LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit"); - } -#if USERPREFS_EVENT_MODE - if (tosend->hop_limit > 2) { - // if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away. - tosend->hop_start -= (tosend->hop_limit - 2); - tosend->hop_limit = 2; - } -#endif - - if (p->next_hop == NO_NEXT_HOP_PREFERENCE) { - FloodingRouter::send(tosend); - } else { - NextHopRouter::send(tosend); - } - - return true; + // Use shared logic to determine if hop_limit should be decremented + if (shouldDecrementHopLimit(p)) { + tosend->hop_limit--; // bump down the hop count + } else { + LOG_INFO("Router/CLIENT_BASE-to-favorite-router/CLIENT_BASE relay: preserving hop_limit"); } + + NextHopRouter::send(tosend); + + return true; } else { - LOG_DEBUG("No rebroadcast: Role = CLIENT_MUTE or Rebroadcast Mode = NONE"); + LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE"); } - } else { - LOG_DEBUG("Ignore 0 id broadcast"); } } @@ -230,13 +231,13 @@ bool NextHopRouter::stopRetransmission(GlobalPacketId key) } } - // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it - // doesn't get scheduled again. (This is the core of stopRetransmission.) + // Regardless of whether or not we canceled this packet from the txQueue, remove it from our pending list so it doesn't + // get scheduled again. (This is the core of stopRetransmission.) auto numErased = pending.erase(key); assert(numErased == 1); - // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the - // call to startRetransmission. + // When we remove an entry from pending, always be sure to release the copy of the packet that was allocated in the call + // to startRetransmission. packetPool.release(p); return true; diff --git a/src/mesh/NextHopRouter.h b/src/mesh/NextHopRouter.h index c1df3596b..0022644e9 100644 --- a/src/mesh/NextHopRouter.h +++ b/src/mesh/NextHopRouter.h @@ -148,7 +148,7 @@ class NextHopRouter : public FloodingRouter */ uint8_t getNextHop(NodeNum to, uint8_t relay_node); - /** Check if we should be rebroadcasting this packet if so, do so. - * @return true if we did rebroadcast */ - bool perhapsRebroadcast(const meshtastic_MeshPacket *p) override; + /** Check if we should be relaying this packet if so, do so. + * @return true if we did relay */ + bool perhapsRelay(const meshtastic_MeshPacket *p); }; \ No newline at end of file diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index b4af707ae..49d581d9a 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -94,6 +94,7 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd LOG_DEBUG("Packet History - Hop limit upgrade: packet 0x%08x from hop_limit=%d to hop_limit=%d", p->id, found->hop_limit, p->hop_limit); *wasUpgraded = true; + seenRecently = false; // Allow router processing but prevent duplicate app delivery } else if (wasUpgraded) { *wasUpgraded = false; // Initialize to false if not an upgrade } diff --git a/src/mesh/http/ContentHandler.cpp b/src/mesh/http/ContentHandler.cpp index 7b7ebb595..f87c6e3b0 100644 --- a/src/mesh/http/ContentHandler.cpp +++ b/src/mesh/http/ContentHandler.cpp @@ -148,8 +148,6 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer) void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res) { - if (webServerThread) - webServerThread->markActivity(); LOG_DEBUG("webAPI handleAPIv1FromRadio"); @@ -393,9 +391,6 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res) void handleStatic(HTTPRequest *req, HTTPResponse *res) { - if (webServerThread) - webServerThread->markActivity(); - // Get access to the parameters ResourceParameters *params = req->getParams(); diff --git a/src/mesh/http/WebServer.cpp b/src/mesh/http/WebServer.cpp index 3a264fa5a..bf170de59 100644 --- a/src/mesh/http/WebServer.cpp +++ b/src/mesh/http/WebServer.cpp @@ -49,12 +49,6 @@ Preferences prefs; using namespace httpsserver; #include "mesh/http/ContentHandler.h" -static const uint32_t ACTIVE_THRESHOLD_MS = 5000; -static const uint32_t MEDIUM_THRESHOLD_MS = 30000; -static const int32_t ACTIVE_INTERVAL_MS = 50; -static const int32_t MEDIUM_INTERVAL_MS = 200; -static const int32_t IDLE_INTERVAL_MS = 1000; - static SSLCert *cert; static HTTPSServer *secureServer; static HTTPServer *insecureServer; @@ -181,32 +175,6 @@ WebServerThread::WebServerThread() : concurrency::OSThread("WebServer") if (!config.network.wifi_enabled && !config.network.eth_enabled) { disable(); } - lastActivityTime = millis(); -} - -void WebServerThread::markActivity() -{ - lastActivityTime = millis(); -} - -int32_t WebServerThread::getAdaptiveInterval() -{ - uint32_t currentTime = millis(); - uint32_t timeSinceActivity; - - if (currentTime >= lastActivityTime) { - timeSinceActivity = currentTime - lastActivityTime; - } else { - timeSinceActivity = (UINT32_MAX - lastActivityTime) + currentTime + 1; - } - - if (timeSinceActivity < ACTIVE_THRESHOLD_MS) { - return ACTIVE_INTERVAL_MS; - } else if (timeSinceActivity < MEDIUM_THRESHOLD_MS) { - return MEDIUM_INTERVAL_MS; - } else { - return IDLE_INTERVAL_MS; - } } int32_t WebServerThread::runOnce() @@ -221,7 +189,8 @@ int32_t WebServerThread::runOnce() ESP.restart(); } - return getAdaptiveInterval(); + // Loop every 5ms. + return (5); } void initWebServer() diff --git a/src/mesh/http/WebServer.h b/src/mesh/http/WebServer.h index e7a29a5a7..815d87432 100644 --- a/src/mesh/http/WebServer.h +++ b/src/mesh/http/WebServer.h @@ -10,17 +10,13 @@ void createSSLCert(); class WebServerThread : private concurrency::OSThread { - private: - uint32_t lastActivityTime = 0; public: WebServerThread(); uint32_t requestRestart = 0; - void markActivity(); protected: virtual int32_t runOnce() override; - int32_t getAdaptiveInterval(); }; extern WebServerThread *webServerThread; diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp index 5bdde1919..fc2cc232b 100644 --- a/src/modules/TraceRouteModule.cpp +++ b/src/modules/TraceRouteModule.cpp @@ -21,11 +21,6 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti { const meshtastic_Data &incoming = p.decoded; - // Update next-hops using returned route - if (incoming.request_id) { - updateNextHops(p, r); - } - // Insert unknown hops if necessary insertUnknownHops(p, r, !incoming.request_id); @@ -158,65 +153,6 @@ void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtasti } } -void TraceRouteModule::updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) -{ - // E.g. if the route is A->B->C->D and we are B, we can set C as next-hop for C and D - // Similarly, if we are C, we can set D as next-hop for D - // If we are A, we can set B as next-hop for B, C and D - - // First check if we were the original sender or in the original route - int8_t nextHopIndex = -1; - if (isToUs(&p)) { - nextHopIndex = 0; // We are the original sender, next hop is first in route - } else { - // Check if we are in the original route - for (uint8_t i = 0; i < r->route_count; i++) { - if (r->route[i] == nodeDB->getNodeNum()) { - nextHopIndex = i + 1; // Next hop is the one after us - break; - } - } - } - - // If we are in the original route, update the next hops - if (nextHopIndex != -1) { - // For every node after us, we can set the next-hop to the first node after us - NodeNum nextHop; - if (nextHopIndex == r->route_count) { - nextHop = p.from; // We are the last in the route, next hop is destination - } else { - nextHop = r->route[nextHopIndex]; - } - - if (nextHop == NODENUM_BROADCAST) { - return; - } - uint8_t nextHopByte = nodeDB->getLastByteOfNodeNum(nextHop); - - // For the rest of the nodes in the route, set their next-hop - // Note: if we are the last in the route, this loop will not run - for (int8_t i = nextHopIndex; i < r->route_count; i++) { - NodeNum targetNode = r->route[i]; - maybeSetNextHop(targetNode, nextHopByte); - } - - // Also set next-hop for the destination node - maybeSetNextHop(p.from, nextHopByte); - } -} - -void TraceRouteModule::maybeSetNextHop(NodeNum target, uint8_t nextHopByte) -{ - if (target == NODENUM_BROADCAST) - return; - - meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(target); - if (node && node->next_hop != nextHopByte) { - LOG_INFO("Updating next-hop for 0x%08x to 0x%02x based on traceroute", target, nextHopByte); - node->next_hop = nextHopByte; - } -} - void TraceRouteModule::processUpgradedPacket(const meshtastic_MeshPacket &mp) { if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag || mp.decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP) diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h index dac422388..a069f7157 100644 --- a/src/modules/TraceRouteModule.h +++ b/src/modules/TraceRouteModule.h @@ -55,12 +55,6 @@ class TraceRouteModule : public ProtobufModule, // Call to add your ID to the route array of a RouteDiscovery message void appendMyIDandSNR(meshtastic_RouteDiscovery *r, float snr, bool isTowardsDestination, bool SNRonly); - // Update next-hops in the routing table based on the returned route - void updateNextHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r); - - // Helper to update next-hop for a single node - void maybeSetNextHop(NodeNum target, uint8_t nextHopByte); - /* Call to print the route array of a RouteDiscovery message. Set origin to where the request came from. Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */ diff --git a/src/power.h b/src/power.h index cdbdd3ea0..23eb95064 100644 --- a/src/power.h +++ b/src/power.h @@ -138,7 +138,6 @@ class Power : private concurrency::OSThread void reboot(); // open circuit voltage lookup table uint8_t low_voltage_counter; - int32_t lastLogTime = 0; #ifdef DEBUG_HEAP uint32_t lastheap; #endif From 81a5aeff7480021dce73d0a75ab0f5e77d8fe8c5 Mon Sep 17 00:00:00 2001 From: Chloe Bethel Date: Tue, 7 Oct 2025 12:11:26 +0100 Subject: [PATCH 24/27] Fix serial pins for Ebyte E77 MBL board (#8246) Also move RAK3172 and new EBYTE_E77_MBL define to variant.h, as this makes VSCode know about the defines properly... Co-authored-by: Ben Meadors --- src/modules/SerialModule.cpp | 2 +- variants/stm32/CDEBYTE_E77-MBL/platformio.ini | 5 ++++- variants/stm32/CDEBYTE_E77-MBL/variant.h | 2 ++ variants/stm32/rak3172/platformio.ini | 1 - variants/stm32/rak3172/variant.h | 2 ++ 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 7485f1c2d..a9ec8f6a8 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -67,7 +67,7 @@ SerialModuleRadio *serialModuleRadio; defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {} static Print *serialPrint = &Serial; -#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) +#elif defined(CONFIG_IDF_TARGET_ESP32C6) || defined(RAK3172) || defined(EBYTE_E77_MBL) SerialModule::SerialModule() : StreamAPI(&Serial1), concurrency::OSThread("Serial") {} static Print *serialPrint = &Serial1; #else diff --git a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini index 290982405..c5af9a4a4 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/platformio.ini +++ b/variants/stm32/CDEBYTE_E77-MBL/platformio.ini @@ -6,9 +6,12 @@ board_level = extra build_flags = ${stm32_base.build_flags} -Ivariants/stm32/CDEBYTE_E77-MBL - -DSERIAL_UART_INSTANCE=1 + -DSERIAL_UART_INSTANCE=2 -DPIN_SERIAL_RX=PA3 -DPIN_SERIAL_TX=PA2 + -DENABLE_HWSERIAL1 + -DPIN_SERIAL1_RX=PB7 + -DPIN_SERIAL1_TX=PB6 -DMESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 -DMESHTASTIC_EXCLUDE_I2C=1 -DMESHTASTIC_EXCLUDE_GPS=1 diff --git a/variants/stm32/CDEBYTE_E77-MBL/variant.h b/variants/stm32/CDEBYTE_E77-MBL/variant.h index 317f44489..e3d111a33 100644 --- a/variants/stm32/CDEBYTE_E77-MBL/variant.h +++ b/variants/stm32/CDEBYTE_E77-MBL/variant.h @@ -18,4 +18,6 @@ Do not expect a working Meshtastic device with this target. #define LED_PIN PB4 // LED1 // #define LED_PIN PB3 // LED2 #define LED_STATE_ON 1 + +#define EBYTE_E77_MBL #endif diff --git a/variants/stm32/rak3172/platformio.ini b/variants/stm32/rak3172/platformio.ini index 7fc6c7cba..b9a4b8a04 100644 --- a/variants/stm32/rak3172/platformio.ini +++ b/variants/stm32/rak3172/platformio.ini @@ -6,7 +6,6 @@ board_upload.maximum_size = 233472 ; reserve the last 28KB for filesystem build_flags = ${stm32_base.build_flags} -Ivariants/stm32/rak3172 - -DRAK3172 -DENABLE_HWSERIAL1 -DPIN_SERIAL1_RX=PB7 -DPIN_SERIAL1_TX=PB6 diff --git a/variants/stm32/rak3172/variant.h b/variants/stm32/rak3172/variant.h index 45752b481..30d2b57b4 100644 --- a/variants/stm32/rak3172/variant.h +++ b/variants/stm32/rak3172/variant.h @@ -16,4 +16,6 @@ Do not expect a working Meshtastic device with this target. #define LED_PIN PA0 // Green LED #define LED_STATE_ON 1 +#define RAK3172 + #endif From bd9076b7401aed73b1810e746fbb1aed30ba8649 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 7 Oct 2025 06:14:35 -0500 Subject: [PATCH 25/27] Remove risky change --- src/modules/NodeInfoModule.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp index 9b94b3933..aaab019d6 100644 --- a/src/modules/NodeInfoModule.cpp +++ b/src/modules/NodeInfoModule.cpp @@ -113,8 +113,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply() u.public_key.size = 0; } - // Clear the user.id field since it should be derived from node number on the receiving end - u.id[0] = '\0'; + // FIXME: Clear the user.id field since it should be derived from node number on the receiving end + // u.id[0] = '\0'; + + // Ensure our user.id is derived correctly + strcpy(u.id, nodeDB->getNodeId().c_str()); + LOG_INFO("Send owner %s/%s/%s", u.id, u.long_name, u.short_name); lastSentToMesh = millis(); return allocDataProtobuf(u); From 468b40e8dbf2bf899ec0020e57fa2035b4f03b54 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Tue, 7 Oct 2025 22:24:09 +1100 Subject: [PATCH 26/27] Wait until after GPS lock hold before updating position, if we can. (#8064) * Wait until after GPS lock hold before updating position, if we can. After the recent patch, we hold lock for a bit before updating the position. The positions that come in after the hold are genuinely better positions than what we've been doing before. However, they only come 20 seconds after we've got lock. Previously, we would update the local position as soon as we got a lock as well as at the end of the hold, since a hold was not always possible. With this patch, if the settings allow, we should skip that first local position update. Fixes https://github.com/meshtastic/firmware/issues/8029 * Fix falling edge handling. * spelling Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Congeal lock handling * Add named constants * define unit to avoid confusion Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * ifdef, not if. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add handling for when we first turn on. * Don't run if not active * Reset fixhold * Logic fixes * Add path for ACTIVE--> IDLE --> ACTIVE Previously we only covered HARDSLEEP --> ACTIVE. * Change hold time to gps_update_interval - 10s * Update comment * Add extra buffer to avoid re-starting hold --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Ben Meadors --- src/gps/GPS.cpp | 125 +++++++++++++++++++++++++++++++++--------------- src/gps/GPS.h | 5 +- 2 files changed, 90 insertions(+), 40 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 1125cf998..2cfa558ed 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1031,7 +1031,7 @@ void GPS::down() LOG_DEBUG("%us until next search", sleepTime / 1000); // If update interval less than 10 seconds, no attempt to sleep - if (updateInterval <= 10 * 1000UL || sleepTime == 0) + if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS || sleepTime == 0) setPowerState(GPS_IDLE); else { @@ -1102,6 +1102,29 @@ int32_t GPS::runOnce() publishUpdate(); } + // ======================== GPS_ACTIVE state ======================== + // In GPS_ACTIVE state, GPS is powered on and we're receiving NMEA messages. + // We use the following logic to determine when to update the local position + // or time by running GPS::publishUpdate. + // Note: Local position update is asynchronous to position broadcast. We + // generally run this state every gps_update_interval seconds, and in most cases + // gps_update_interval is faster than the position broadcast interval so there's a + // fresh position ready when the device wants to broadcast one on the mesh. + // + // 1. Got a time for the first time --> set the time, don't publish. + // 2. Got a lock for the first time + // --> If gps_update_interval is <= 10s --> publishUpdate + // --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s) + // 3. Got a lock after turning back on + // --> If gps_update_interval is <= 10s --> publishUpdate + // --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s) + // 4. Hold has expired + // --> If we have a time and a location --> publishUpdate + // --> down() + // 5. Search time has expired + // --> If we have a time and a location --> publishUpdate + // --> If we had a location before but don't now --> publishUpdate + // --> down() if (whileActive()) { // if we have received valid NMEA claim we are connected setConnected(); @@ -1111,55 +1134,81 @@ int32_t GPS::runOnce() if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue()) up(); - // If we've already set time from the GPS, no need to ask the GPS - bool gotTime = (getRTCQuality() >= RTCQualityGPS); - if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time - gotTime = true; - shouldPublish = true; - } - + // quality of the previous fix. We set it to 0 when we go down, so it's a way + // to check if we're getting a lock after being GPS_OFF. uint8_t prev_fixQual = fixQual; - bool gotLoc = lookForLocation(); - if (gotLoc && !hasValidLocation) { // declare that we have location ASAP - LOG_DEBUG("hasValidLocation RISING EDGE"); - hasValidLocation = true; - shouldPublish = true; - // Hold for 20secs after getting a lock to download ephemeris etc - fixHoldEnds = millis() + 20000; - } - if (gotLoc && prev_fixQual == 0) { // just got a lock after turning back on. - fixHoldEnds = millis() + 20000; - shouldPublish = true; // Publish immediately, since next publish is at end of hold - } + if (powerState == GPS_ACTIVE) { + // if gps_update_interval is <=10s, GPS never goes off, so we treat that differently + uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval); - bool tooLong = scheduling.searchedTooLong(); - if (tooLong) - LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time"); + // 1. Got a time for the first time + bool gotTime = (getRTCQuality() >= RTCQualityGPS); + if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time + gotTime = true; + } - // Once we get a location we no longer desperately want an update - if ((gotLoc && gotTime) || tooLong) { + // 2. Got a lock for the first time, or 3. Got a lock after turning back on + bool gotLoc = lookForLocation(); + if (gotLoc) { +#ifdef GPS_DEBUG + if (!hasValidLocation) { // declare that we have location ASAP + LOG_DEBUG("hasValidLocation RISING EDGE"); + } +#endif + if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS) { + hasValidLocation = true; + shouldPublish = true; + } else if (!hasValidLocation || prev_fixQual == 0 || (fixHoldEnds + GPS_THREAD_INTERVAL) < millis()) { + hasValidLocation = true; + // Hold for up to 20secs after getting a lock to download ephemeris etc + uint32_t holdTime = updateInterval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS; + if (holdTime > GPS_FIX_HOLD_MAX_MS) + holdTime = GPS_FIX_HOLD_MAX_MS; + fixHoldEnds = millis() + holdTime; +#ifdef GPS_DEBUG + LOG_DEBUG("Holding for %ums after lock", holdTime); +#endif + } + } + + bool tooLong = scheduling.searchedTooLong(); if (tooLong && !gotLoc) { + LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time"); // we didn't get a location during this ack window, therefore declare loss of lock if (hasValidLocation) { - LOG_DEBUG("hasValidLocation FALLING EDGE"); - } - p = meshtastic_Position_init_default; - hasValidLocation = false; - } - if (millis() > fixHoldEnds) { - shouldPublish = true; // publish our update at the end of the lock hold - publishUpdate(); - down(); + p = meshtastic_Position_init_default; + hasValidLocation = false; + shouldPublish = true; #ifdef GPS_DEBUG - } else { + LOG_DEBUG("hasValidLocation FALLING EDGE"); +#endif + } + } + + // Hold has expired , Search time has expired, we got a time only, or we never needed to hold. + bool holdExpired = (fixHoldEnds != 0 && millis() > fixHoldEnds); + if (shouldPublish || tooLong || holdExpired) { + if (gotTime && hasValidLocation) { + shouldPublish = true; + } + if (shouldPublish) { + fixHoldEnds = 0; + publishUpdate(); + } + + // There's a chance we just got a time, so keep going to see if we can get a location too + if (tooLong || holdExpired) { + down(); + } + +#ifdef GPS_DEBUG + } else if (fixHoldEnds != 0) { LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view); #endif } } - - // If state has changed do a publish - publishUpdate(); + // ===================== end GPS_ACTIVE state ======================== if (config.position.fixed_position == true && hasValidLocation) return disable(); // This should trigger when we have a fixed position, and get that first position diff --git a/src/gps/GPS.h b/src/gps/GPS.h index f3bcf37f5..8ba1ce0a6 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -16,6 +16,9 @@ #define GPS_EN_ACTIVE 1 #endif +static constexpr uint32_t GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS = 10 * 1000UL; +static constexpr uint32_t GPS_FIX_HOLD_MAX_MS = 20000; + typedef enum { GNSS_MODEL_ATGM336H, GNSS_MODEL_MTK, @@ -175,8 +178,6 @@ class GPS : private concurrency::OSThread */ bool hasValidLocation = false; // default to false, until we complete our first read - bool isInPowersave = false; - bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop() bool hasGPS = false; // Do we have a GPS we are talking to From f0e4ea76649a49f614e391bbb7c16519e163a219 Mon Sep 17 00:00:00 2001 From: szlifier Date: Tue, 7 Oct 2025 13:25:38 +0200 Subject: [PATCH 27/27] Add SHT4x serial number for detection (#8222) SHT4X chip recognized as SHT31, registerValue that stores first bytes of chip's serial number did not mach the chip. Added a serial number match for SHT40 found in a SONOFF SNZB-02P. --- src/detect/ScanI2CTwoWire.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 6df3f8be1..89a0610b4 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -378,7 +378,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2); - if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c || registerValue == 0xc8d) { + if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0x11f3 || registerValue == 0xe9c || registerValue == 0xc8d) { type = SHT4X; logFoundDevice("SHT4X", (uint8_t)addr.address); } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {