Commit Graph
65 Commits
Author SHA1 Message Date
1e3a38fc8a Add native Portduino mDNS discovery with Avahi and Bonjour (#12032)
* Add native Portduino mDNS discovery via Avahi and Bonjour

* Guard native discovery in Portduino WASM builds

* Add linux dependencies in packaging, support "buildroot"

Adds libavahi dependencies for Debian, RPM, Docker and CI.
Moves the libavahi definition to the shared `build_flags` (which is only consumed on Linux targets)

* Fix platformio formatting

* fix(portduino): disable Avahi when daemon unavailable

---------

Co-authored-by: Jake-B <jake-b@users.noreply.github.com>
Co-authored-by: vidplace7 <vidplace7@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:56:28 +00:00
Jonathan BennettandClaude Opus 5 8c0abbd522 feat(portduino): BLE peripheral support via BlueZ for meshtasticd (Raspberry Pi) (#11396)
* feat(portduino): BLE peripheral support via BlueZ for meshtasticd on Linux

Adds the standard Meshtastic BLE service (toRadio/fromRadio/fromNum/logRadio)
to the Linux native target, so a Raspberry Pi running meshtasticd can be
paired and used over BLE like any other Meshtastic device.

Implementation: a new LinuxBluetooth backend registers a GATT application,
LE advertisement and pairing agent with bluetoothd over the org.bluez D-Bus
APIs, using sdbus-c++ (both the 1.x and 2.x major versions, via a small
compat shim - Debian bookworm/Ubuntu 24.04 ship 1.x, trixie/Fedora ship 2.x).
When the sdbus-c++ dev package is absent the whole backend compiles out via
__has_include, the same optional-dependency idiom as the ulfius webserver.

Threading follows the NimbleBluetooth model, simplified: the sdbus event
loop runs its own thread, and all PhoneAPI calls happen on the main thread.
Writes queue to the main loop; reads park the D-Bus reply and are completed
from the main thread after queued writes, so write-then-read clients see
their answer without any busy-waiting.

Enablement is a double opt-in: a new `Bluetooth:` config.yaml section
(Enabled, default false; AdapterId, default hci0) must turn BLE on for the
host, and the regular device config bluetooth.enabled must be on. The
config-check schema and fixtures cover the new section.

Pairing honors config.bluetooth.mode: NO_PIN maps to a NoInputNoOutput
just-works agent; RANDOM_PIN to DisplayOnly with the kernel-generated
passkey shown on screen/log via the existing BluetoothStatus plumbing.
FIXED_PIN falls back to random-passkey semantics with a warning - BlueZ
does not support forcing a passkey. PIN modes enforce
encrypt-authenticated-read/write on all mesh characteristics.

Packaging: install a D-Bus system policy so the meshtasticd user may talk
to org.bluez, add it to the bluetooth group, order the unit after
bluetooth.service, and add libsdbus-c++-dev to debian/rpm/docker/CI deps.

Verified in-container against a mock bluetoothd: registration flow, GATT
tree enumeration, advertisement properties, and a full config download
(ToRadio wantConfig -> 47 FromRadio packets) through the D-Bus bridge.
Real-hardware pairing/notify testing on a Pi still pending.

Known limitations (v1): meshtasticd must be restarted if bluetoothd
restarts; FIXED_PIN degrades to a random passkey; getRssi() returns 0
(same as nRF52).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ

* fix(portduino): BLE fixes from first real-hardware pass (Pi CM5 + RAK6421)

Findings from testing PR #11396 on a Raspberry Pi CM5 (Pi OS trixie,
BlueZ/sdbus-c++ 2.1 - the v2 compat path) with a RAK6421 HAT and an
Android phone:

- getMacAddr() leaked its HCI socket on every call and never closed it,
  and on failure returned without touching the caller's buffer - which
  getDeviceName() passed in uninitialized. Close the socket on all paths
  and cache the MAC after the first successful read; it cannot change at
  runtime and this now runs on every bluetoothd property read.
- getDeviceName() zero-initializes its MAC buffer, and LinuxBluetooth
  snapshots the name once at setup() on the main thread: the
  advertisement's LocalName getter runs on the D-Bus event-loop thread
  and getDeviceName()'s static buffer is not thread-safe.
- Restore NimBLE-style config-phase packet prefetch (depth 3). The
  initial port answered every FromRadio read with a D-Bus -> main-loop
  round trip, which made the config download noticeably slow; ReadValue
  now answers straight from the prefetch queue on the event-loop thread,
  with NimBLE's safety rules (never in STATE_SEND_PACKETS, writes always
  observed before reads, queue cleared on disconnect).
- Set advertising MinInterval/MaxInterval to 20-100ms (BlueZ >= 5.71;
  older versions ignore the properties). btmon showed the kernel default
  of 1.28s otherwise, and Android's background-connect scan windows are
  sparse enough that tap-to-connect took 8-14s; 20ms is the same floor
  NimBLE uses on ESP32.

Verified on hardware: scan, passkey pairing, connect, config download,
reconnect after bond wipe. Also diagnosed (no code change): the node
identity MAC comes from the RAK HAT EEPROM by design, so the BLE name
suffix follows the HAT rather than the BT adapter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ

* fix(portduino): address CodeRabbit review on BLE support

- setBluetoothEnable: handle disable before the config gate, so a running
  BLE stack is always stoppable even after the device config turns
  Bluetooth off underneath it
- getMacAddr: read the adapter configured as Bluetooth.AdapterId instead
  of hardcoding hci0, falling back to hci0 for unparseable names
- systemd unit: Wants=bluetooth.service so bluetoothd is pulled up when
  present (After= only orders, it does not start it)
- debian/rpm: Recommends: bluez as the runtime contract for BLE
- dbus policy: document why the org.bluez rule is destination-wide
  rather than a per-interface allowlist

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ

* fix(portduino): show the BLE pairing code on BaseUI screens

onDisplayPasskey published the passkey to bluetoothStatus and triggered
PowerFSM, but never called screen->startAlert(), so on BaseUI the code only
ever reached the log. BluetoothStatus has no BaseUI consumer -- only InkHUD's
PairingApplet and StatusLEDModule read it -- so a Pi driving a HUB75/OLED
panel showed nothing while BlueZ sat waiting for the user to type a code they
could not see. NimBLE and nRF52 draw it via startAlert(); this adds the
missing half for Linux.

The agent callbacks run on the sdbus event-loop thread while the screen is
owned by the main thread, so the passkey is handed over as a pending flag and
drawn from runOnce(), matching the existing disconnectCleanupPending pattern
rather than reaching into the screen from the event loop.

Dismissed on all four exits, so a stale code cannot stick on an always-on
panel: Paired -> true (newly watched in PropertiesChanged, which previously
only looked at Connected), agent Cancel, peer disconnect (moved out of the
lastGone branch so a peer leaving mid-pairing clears the code even when
another device is still connected), and doDeinit() -- applied inline there
because runOnce() may never be scheduled again after teardown.

Verified on a Pi 5 + BlueZ 5.66 in RANDOM_PIN mode: the code renders on a
HUB75 panel and clears once the phone completes pairing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* ci: install libsdbus-c++-dev for the native test build

setup-native-test landed on develop while this branch was adding
libsdbus-c++-dev to setup-native, so the new action's "full setup-native
list" of C libraries is missing it. Without the package the test job
builds with HAS_BLUETOOTH 0 and never compiles LinuxBluetooth.cpp.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ

* fix(portduino): warn when a factory reset cannot clear BLE bonds

factoryReset(eraseBleBonds) silently did nothing on Linux when the BLE
backend was not running, so the reset reported success while the host's
pairings stayed. Removing them needs a live connection to bluetoothd that
a disabled backend never opened, so say so rather than imply they went.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ

* fix(portduino): gate the factory-reset bond clear on an enabled backend

setup() leaves linuxBluetooth allocated with its bus torn down when it
throws, so a pointer check alone let factoryReset log "Clear bluetooth
bonds" for a clear that clearBonds() then declined to perform. isEnabled()
is only true after setup() completes, which routes that case to the
warning that says the bonds were left alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ

* style: reformat under clang-format 20

#11909 moved trunk from clang-format 16 to 20, which spaces C-style casts
differently and reindents the comment above the HAS_WIFI block. Both files
are ones this branch already touches, and trunk's fmt linter grades whole
files, so its check fails until they are reformatted. No behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0184v7MyCLuJHW2ebZ9r8NmQ

* fix(portduino): three BLE config and lifecycle fixes from review

Bluetooth config keys are now assigned individually rather than per section.
loadConfig() runs once for every file in config.d, so reading an absent key as
its default let a later file that named only one of them silently reset the
other: `AdapterId: hci1` alone turned Bluetooth off, and `Enabled: true` alone
dragged the adapter back to hci0. Only what a file actually states should
override what an earlier one set.

A backend that failed to come up is now retried. setup() can leave
linuxBluetooth non-null but disabled - bluetoothd not ready, adapter missing,
policy refusing - and every later enable then called resumeAdvertising(), which
returns immediately while disabled. A transient failure at boot kept BLE off
until the process restarted. doSetup() already opens with `if (enabled) return`
and tears the bus down on every failure path, so calling it again is safe.

Bluetooth.AdapterId is now checked for the hci<digits> form. LinuxBluetooth uses
the value verbatim as the BlueZ object path while the MAC fallback reads only
the leading hciN, so "hci1junk" looks plausible, yields a MAC, then finds no
adapter and BLE never comes up. Covered by a new fixture and suite case, which
is the kind of silent no-op that directory exists to catalogue.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-01 21:20:09 +00:00
f5940ab5dd Meshtasticd notifications (#10216)
* First attempt at sending message notifications on Linux

* Add files via upload

* Notifications: Use libnotify

Use libnotify for meshtasticd desktop notifications.

Add "HAS_LIBNOTIFY" macro to guard for builds where libnotify is expected.
This is not included in buildroot/openwrt builds. (no libnotify except when extra repos are added).

Install desktop icon in the correct location on Debian and Fedora packages.
Update dependencies in packaging and dockerfiles.

* Add libnotify to setup-native (GitHub Actions)

* Address review feedback on the meshtasticd notification path

- platformio.ini: only define HAS_LIBNOTIFY when pkg-config actually finds
  libnotify. The probe previously ran unguarded and the macro was defined
  unconditionally, so a native build without libnotify-dev both hard-failed at
  config time and claimed the feature was available.
- Fix the sender lookup for develop's flattened NodeInfoLite: has_user/user.*
  are gone, replaced by nodeInfoLiteHasUser() and direct long_name/short_name.
  This is a silent semantic conflict - it merges cleanly but does not compile.
- Move the desktop notification off the packet path. notify_notification_show()
  is a synchronous DBus round trip and meshtasticd's packet handling is
  single-threaded, so a slow or wedged notification daemon could stall the
  radio. handleReceived() now resolves the strings and queues them (bounded at
  16); a dedicated worker owns every libnotify call.
- Stop retrying forever: a failed notify_init(), or three consecutive failed
  shows, latches desktop notifications off instead of re-logging per message.
- NodeDB: the ARCH_PORTDUINO default-enable block was nested inside
  #ifdef HAS_I2S, which Portduino never defines, so it never ran. Hoist it out.

* fix(portduino): close input-broker guard before the libnotify block

The libnotify implementation was inserted ahead of the #endif that closed
#if !MESHTASTIC_EXCLUDE_INPUTBROKER, so the file's trailing #endif closed
#if HAS_LIBNOTIFY instead and the input-broker conditional was never closed.
Every build target failed to preprocess:

  src/modules/ExternalNotificationModule.cpp:630: error: unterminated #if

Close the guard immediately after handleInputEvent(), as develop does, so the
two conditionals stay independent and HAS_LIBNOTIFY still resolves when the
input broker is excluded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs

* fix(portduino): back off desktop notifications instead of latching them off

notifyDisabled was doing two jobs: "libnotify looks unusable" and "the
destructor wants the worker to exit". Because the worker's only exit path was
also its only failure path, three failed shows turned notifications off for the
process lifetime with no way back. The packaged daemon is exactly that case:
bin/meshtasticd.service runs as User=meshtasticd and the rpm spec creates that
account with /sbin/nologin, so there is no session bus and every show() fails.
A stock install logged three warnings and then went silent forever, while
NodeDB now enables the module by default on Portduino.

Split the flag. notifyShutdown is destructor-only and remains the worker's one
exit; a retry window replaces the latch. After maxNotifyFailures the worker
arms a backoff (30s, doubling to a 15min cap), drops the queue rather than
holding stale popups, and keeps looping. The producer refuses to queue while
the window is open, so the retry is driven by the next message after it expires
rather than by a timer - no idle wakeups, no probe notifications. Any success
resets the backoff. notify_init() moved inside the loop so a retry can pick up
a session bus that was absent at startup.

The worker also no longer calls the LOG_ macros. RedirectablePrint formats into
a shared static buffer that nothing guards and every other writer to it is on
the main thread, so the worker now records the state change under the mutex it
already holds and reportNotifyStatus() emits it from portduinoNotify() and
runOnce(). Per-message failure spam becomes one line per transition: the reason
and retry interval when it goes down, and a line when it comes back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs

* fix(portduino): sanitize mesh text before it reaches libnotify

The notification body was the raw decoded payload and the summary was a node
name, both attacker-controlled, and neither was checked before being handed to
libnotify. g_variant_new_string() rejects invalid UTF-8: a GLib CRITICAL and an
"[Invalid UTF-8]" body by default, and a hard abort under G_DEBUG=fatal-criticals,
so an unauthenticated mesh packet could terminate meshtasticd on any install
running with that flag. An embedded NUL separately truncated the body at the
first one, hiding the rest of the message.

Route both strings through sanitizedMeshText(), which replaces embedded NULs and
then applies the existing sanitizeUtf8() helper. TypeConversions already
sanitizes names on the way into NodeDB, but an abort is too sharp an edge to
leave resting on an invariant owned by another file.

Escape the body for Pango markup as well. Servers advertising "body-markup"
parse a markup subset there, so a message can inject formatting to dress itself
up as trusted UI, and where the server also advertises body-images or
body-hyperlinks it can inject tags that make the notification daemon fetch a
remote URL. The escaping is unconditional rather than gated on
notify_get_server_caps(): a caps query that fails, or goes stale across a daemon
restart, fails open, while on a server without body-markup the only cost is
entities rendering literally, and mesh text carries no markup worth preserving.
The summary is not escaped - the spec gives it no markup, so escaping it would
only ever show entities.

Also call notify_uninit() in the destructor, after the join: the worker owns
every libnotify call, so tearing down while it is still running would be a
use-after-uninit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs

* fix(portduino): gate the notification default-on behind HAS_LIBNOTIFY

The default-enable block was gated on ARCH_PORTDUINO, so a native build where
pkg-config could not find libnotify still shipped external_notification enabled.
portduinoNotify() is not compiled into that build, and native defines
EXT_NOTIFICATION_MODULE_OUTPUT as 0 with setup() guarding the pin writes behind
output > 0, so nothing was driven - it only exposed a config surface that can
do nothing. Gate it on the same macro that governs the code it exists to feed.

HAS_LIBNOTIFY is a build flag for the whole env and is simply absent when the
probe fails, so this reads as 0 on every other target, matching how
ExternalNotificationModule.cpp already tests it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs

* fix(portduino): fail closed when markup escaping returns NULL

escapedNotificationBody() returned its input unescaped if g_markup_escape_text()
gave back NULL, which would hand libnotify the exact attacker-controlled string
the function exists to neutralize. The branch is unreachable for the input we
pass - already sanitized to valid UTF-8, and GLib documents no NULL return for
it - but an error path whose fallback is the unsafe action is the wrong shape
for a helper on this boundary. Drop the body instead.

Also note in the doc comment why running this on the caller's thread does not
weaken the rule that the worker owns every libnotify call: it is a pure GLib
string function that touches no libnotify or DBus state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs

* style: format ExternalNotificationModule.h per the pinned clang-format

Trunk Check Runner failed on c0d5ebff with "1 unformatted file" against this
header. The deviation is a brace-spacing fix that clang-format wants on the
rtttl stub's begin(): the formatter emitted it while reformatting this file for
an earlier commit, and I reverted it then as unrelated churn. It was not -
trunk's fmt check reports a modified file as a whole, so touching this header at
all surfaces that line, and dropping the formatter's output is what turned the
check red.

Verified with the version trunk.yaml pins, clang-format 20.1.0, rather than the
older one available locally: it is the only remaining deviation across the three
files this branch touches.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs

* fix(portduino): stop the libnotify probe swallowing native-tft's link flags

The docker arm64 builds failed to link native-tft:

  libmeshtastic-device-ui.a(CURLService.cpp.o): undefined reference to symbol
  'curl_easy_reset@@CURL_GNUTLS_3'
  /lib/aarch64-linux-gnu/libcurl-gnutls.so.4: DSO missing from command line

-lcurl was never on the link line. The libnotify probe was the last line of
[native_base].build_flags, and an env extending it writes

  build_flags = ${native_base.build_flags} -Os -lcurl -lX11 ...

so the interpolation appended those flags to the probe's own line. The result is
a single shell command ending in `... && echo -D HAS_LIBNOTIFY=1 || : -Os -lcurl
-lX11 -linput -lxkbcommon -ffunction-sections -fdata-sections -Wl,--gc-sections`,
where the trailing flags are arguments to echo when the probe succeeds and to `:`
when it fails. They never reach the compiler, and nothing reports it.

Three envs lost flags this way: native-tft and native-tft-debug (-lcurl -lX11
-linput -lxkbcommon), and native-fb (-lcurl, --gc-sections). env:native appends
nothing on that line, which is why the native test suite stayed green and this
stayed hidden until the module compiled and something actually tried to link.

Move the probe above `-I /usr/include` so a plain flag line ends the value, and
record the constraint so the next flag added here does not re-break it.

Verified with `pio project config --json-output`: before, the three envs carried
the flags inside the probe's command string; after, all three carry them as
build flags and no env still swallows any.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EZstXBJtzRyBmUD1h2FLs

---------

Co-authored-by: Austin Lane <vidplace7@gmail.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-01 17:38:14 +00:00
Austin 683b3533fd setup-base: Move python dependencies into a requirements.txt, pin versions for caching (#11876)
Moves python dependencies declared in `setup-base` to a separate requirements.txt file (which renovate can keep updated) and aligns it with gh-action-firmware.

Also removes `pio upgrade`, this is pointless (we just installed the latest platformio in the previous step)
2026-09-17 21:12:59 +00:00
Austin 7b8a02bb4c Revert "fix(ci): build with pioarduino core instead of upstream platformio (#…" (#11760)
This reverts commit f631428309.
2026-09-07 09:23:15 -04:00
Thomas Göttgens f631428309 fix(ci): build with pioarduino core instead of upstream platformio (#11759)
* fix(ci): build with pioarduino core instead of upstream platformio

The espressif32 platform is the pioarduino fork, but setup-base installed
upstream platformio and then ran pio upgrade, so every firmware build ran
a core the platform is not built against.

Upstream 6.2.0, released 2026-09-05, moved its tool-scons core dependency
to ~4.41101.0 (SCons 4.11.1). The lazy "import SCons.Tool.FortranCommon"
that smart_link() uses to choose a linker fails there, so every ESP target
died at link-action resolution before compiling a file. Core resolves
tool-scons as a core dependency, so a platform_packages pin cannot help:
core installs its own version and removes the pinned one.

pioarduino core pins SCons 4.8.1 by URL rather than by range, so upstream
releases cannot reach it. Dropping pio upgrade as well, since it re-pulled
the latest upstream core regardless of what pip installed.

Only setup-base changes. The matrix-generation jobs install platformio to
parse the ini files and never build firmware, and the native test suites
pass on upstream core because that platform does not reach smart_link.

* Revert "fix(ci): pin tool-scons to 4.8.1 for ESP targets (#11756)"

This reverts commit a8912b1eb.

The pin never took effect. PlatformIO Core resolves tool-scons as a core
dependency, so it installs its own version and removes the pinned one:

  Installing platformio/tool-scons @ 4.40801.0
  Installing platformio/tool-scons @ ~4.41101.0
  Removing tool-scons @ 4.40801.0

Switching to pioarduino core in the preceding commit fixes this properly,
and leaving a platform_packages entry that fights a core dependency would
only be misleading.
2026-09-07 12:24:12 +02:00
renovate[bot] 5920d05f5b chore(deps): update actions/setup-python action to v7 (#11727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-06 19:00:21 +00:00
renovate[bot]andThomas Göttgens 76e45c5b44 Update actions/setup-python action to v7 (#11081)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2026-09-03 09:28:01 +00:00
Thomas Göttgens 9850b76351 ci(test): shard the native test suite across a matrix (#11706)
* ci(test): shard the native test suite across a matrix

Replace the single sequential runner with a matrix populated by
bin/test-shards.py from the test/ tree: areas over --max-suites are
split, smaller ones packed, and a --max-shards budget bounds the
fan-out. A collector job merges the per-shard JUnit reports, checks the
union against the canonical suite set, and states the verdict. Native
PlatformIO Tests remains as the single required check over the matrix.

Drop the --without-testing warm build. PlatformIO links every native
test program to the same $BUILD_DIR/$PROGNAME, so the area run relinked
each suite regardless. ccache carries the shared src objects between
shards instead; one shard is flagged cache_writer so a single entry is
saved.

The coverage-event-policy and coverage-channel-table envs and the
attribution canary move into their own matrix rows and job.

Harden the new paths: bound the matrix row count so a branch cannot size
the fan-out, reject multi-line or empty $GITHUB_OUTPUT values, fail the
whole-run attribution gate on an empty expected set, upload exact report
and tracefile names instead of globs, and pass the repo path to
bin/lib/shuffle.sh as an argument rather than into bash -c source text.

12 shards, largest 9 suites.

* ci(test): minimal test toolchain, cap shard runtime, fix pack overflow

Add .github/actions/setup-native-test, used by the shard and canary jobs
in place of setup-native. It drops the redundant second checkout, both
submodules (src/mesh/generated is tracked, meshtestic is the hardware
harness), cppcheck, and the adafruit-nrfutil, poetry and meshtastic pip
installs, and folds in ccache and lcov. setup-base and setup-native are
unchanged, so the firmware matrix and every other consumer keep theirs.

Cap the shard job at 30 minutes. A lost runner held one for 48 of the
360 GitHub allows by default, and there are twelve of them.

pack() could exceed --max-suites: ceil(total / cap) is a lower bound and
whole areas do not divide, so three areas of 6 at cap 10 put 12 in one
of two bins. Grow the bin count until every bin fits.

Validate the fixed-env test_filter tokens against SUITE_RE. PlatformIO
accepts globs there, and those tokens reach the same word-split and the
same attribution gate as discovered names. Split with read -ra so a
token cannot glob against the workspace either.

Report the suite count rather than the length of the -f argument array,
which counted every name twice.

Trim comments to the one or two lines AGENTS.md asks for.

* ci(test): quote the $GITHUB_OUTPUT redirects

Applied to all five, including the three that predate this branch, so the
file is consistent rather than half-converted.
2026-09-02 21:05:03 +00:00
Manuel 5f51bb1c1e MUI: Portduino curl maps (#11214)
* add libcurl, fix missing SDL2

* device-ui commit reference PR353

* add curl dependencies to CI build

* switch to gnutls curl version

* Update device-ui library dependency URL

* Update device-ui dependency to new version

* Fix formatting in Dockerfile package installation

* Update device-ui library dependency URL
2026-07-28 11:09:40 +00:00
Ben Meadors d8884125b6 chore(deps): forward-port renovate updates from master to develop (#10824)
Reconciles develop to master's latest renovate values for deps bumped on the
2.7 line but never back-merged, so the develop->master 2.8 promotion (#10777)
doesn't regress them. Done as a value reconcile, not a cherry-pick: several
master commits are superseded (5 device-ui bumps, 2 ststm32 bumps), and
develop's stale archive/refs/tags/ URL form actually blocked renovate from
bumping these (the regex expects archive/<version>.zip).

GitHub Actions:
- actions/checkout v6 -> v7 (35 refs)
- actions/cache v5 -> v6
- actions/github-script v8 -> v9 (3 refs)
- actions/stale v10.2.0 -> v10.3.0

Build / platform:
- alpine 3.23 -> 3.24 (alpine.Dockerfile)
- platformio/ststm32 19.5.0 -> 19.7.0
- platformio/nordicnrf52 10.11.0 -> 10.12.0

Libraries:
- Adafruit SSD1306 2.5.16 -> 2.5.17
- SparkFun MMC5983MA v1.1.4 -> v1.1.5
- Sensirion I2C SCD30 1.0.0 -> 1.1.1
- meshtastic esp8266-oled-ssd1306 6bfd1f1 -> 2e26010

Deliberately excluded:
- meshtastic/device-ui digest: coupled to firmware protobuf/API and develop has
  diverged hard (NodeDB v25); left for a separate maintainer bump + visual check.
- libpax: develop uses the mverch67 fork (pinned by Arduino-3.x migration #9122),
  master uses dbinfrago -- a fork divergence, not a version bump; not reconciled.
- platform-native digest: develop already at 61067ac (equal to master).
2026-06-30 12:18:01 -05:00
Thomas Göttgens f86cb7781e Remove fragile JSON libraries from the firmware while retaining Meshtasticd JSON support (#10152) 2026-06-03 16:47:30 +02:00
Austin Lane abfa346630 Cleanup GH Actions 2026-03-23 11:17:30 -04:00
renovate[bot] 65eee22396 chore(deps): update github artifact actions (#9767)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-03 08:09:01 +11:00
renovate[bot] de2b9632bb Update GitHub Artifact Actions (#8954)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 06:52:40 -06:00
renovate[bot] 2ac74d6677 Update actions/cache action to v5 (#8944)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-11 19:03:14 -06:00
Austin 66ff1536f3 Meshtastic build manifest (#8248) 2025-12-08 17:21:23 -06:00
renovate[bot] 376dc7ef3a Update actions/checkout action to v6 (#8695)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-22 08:27:00 -06:00
renovate[bot] dd51de85f3 Update GitHub Artifact Actions (#8443)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 19:42:32 +11:00
Ben Meadors 701028b749 Unify build epoch to add flag in platformio-custom.py (#7917)
* Unify build_epoch replacement logic in platformio-custom

* Missed one
2025-09-11 06:29:50 +10:00
renovate[bot] f994eb185f chore(deps): update actions/setup-python action to v6 (#7849)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 06:17:11 -05:00
renovate[bot] a2df80e833 chore(deps): update actions/checkout action to v5 (#7605)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-11 20:58:54 -05:00
Ben Meadors 2ab717cebb Remove bundling of web-ui from ESP32 devices (#7143) 2025-06-26 10:57:33 -05:00
Austin cfc2a96a45 Update web, use centrally defined version (#6500) 2025-04-08 09:09:23 -05:00
d1068fd1e4 Add UDP multicast support on linux. (#6342)
* Add UDP multicast support on linux.

Closes #6326

We tested it an it works.

This is really hacky to say the least.

* Add libuv to Linux packaging

* Trunkadunk

* Correct ref

* Add libuv1-dev to setup-native

---------

Co-authored-by: vidplace7 <vidplace7@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-03-20 08:47:39 -05:00
Austin 4e575872da junk in the Trunk (#6149) 2025-02-25 16:41:45 +08:00
Eric Severance 253ab458ef Add lsb-release on the github runner (#5825) 2025-01-11 22:29:24 -06:00
Eric Severance b4a4d2db4e Cache Python & PlatformIO dependencies (#5822) 2025-01-11 19:40:39 -06:00
Austin 8aac9f2e8e GH Actions: Update Release action, clarify versioning (#5794) 2025-01-08 20:43:24 -06:00
Eric Severance 183f68ba00 Run tests as part of the main CI (#5712)
* Create an shared action to install native dependecies

* Create a workflow for running native tests

* Artifact names contain version

* Add test-native to main_matrix.yml

* No permission are required for test_native.yml

* Add permissions for dorny/test-reporter

* No permissions when running tests

* s/Generate Reports/Generate Test Reports/
2025-01-01 19:26:12 -06:00
Ben Meadors ca3d8da128 version tags 2024-11-16 06:22:08 -06:00
Ben Meadors add70b5229 Rework some things 2024-11-16 05:58:07 -06:00
Ben Meadors c3b9d493b6 Leave the build epoch commented and uncomment when CI runs (#4943) 2024-10-04 15:07:10 -05:00
Thomas Göttgens b769d9f854 change workflow to build one zip per processor arch 2024-10-01 13:14:51 +02:00
Jason Murray c72612d826 sed doesn't like newlines (#4847)
* sed doesn't like newlines

* fold remove-debug-flags block

* PATH is a system env var

* Runners don't like rm -f ${workspace path}
2024-09-24 15:41:40 +08:00
Tom Fifield 682133501a Syntax fix for github action (#4846)
https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-actions
2024-09-24 14:49:01 +08:00
Jason Murray b4c09ace23 Consolidate variant build steps (#4820)
* poc: consolidate variant build steps

* use build-variant action

* only checkout once and clean up after run

* checkout before local action
2024-09-24 13:47:31 +08:00
Tom Fifield 7db98ca1da Revert "Consolidate variant build steps (#4806)" (#4816)
This reverts commit 9f8d86cb25.
2024-09-22 19:39:35 +08:00
Jason Murray 9f8d86cb25 Consolidate variant build steps (#4806)
* poc: consolidate variant build steps

* use build-variant action

* only checkout once and clean up after run
2024-09-22 19:22:00 +08:00
Ben Meadors 6ddee795d6 Poetry 2024-08-21 17:24:56 -05:00
Ben Meadors 6813b8e4e9 Just literally trying stuff at this point 2024-07-29 06:52:14 -05:00
Ben Meadors 4aa6f60e95 Maybe remove pip cache 2024-07-29 06:41:26 -05:00
Ben Meadors 2ffc93324d After 2024-07-29 06:38:34 -05:00
Ben Meadors c501cc501d Pip pip cheerios plz 2024-07-29 06:37:19 -05:00
Ben Meadors 8c0ff89972 Trunk 2024-07-29 06:14:32 -05:00
Ben Meadors cf22b7ff04 Latest pip version of setuptools is broken. Install specific version 2024-07-29 06:11:08 -05:00
Ben Meadors c887675bb4 Fix missing 2024-07-10 09:35:18 -05:00
Thomas Göttgens a52db85ebe fix base setup 2024-06-07 15:36:31 +02:00
Thomas Göttgens 4d8c98c23d Update CI runner versions from Node 16 to 20. (#3872) 2024-05-13 10:47:40 +02:00
Jonathan Bennett 79cfb1e876 Revert "Bump actions to node 20 (#3461)" (#3462)
This reverts commit defeb8e52b.

As per https://github.com/actions/upload-artifact/issues/478 the new version of upload-artifact includes a breaking change.
2024-03-21 16:50:44 -05:00