mirror of
https://github.com/meshtastic/firmware.git
synced 2026-10-08 22:29:03 -04:00
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>
This commit is contained in:
1 parent
e0c76fd41e
commit
8c0abbd522
29 files changed
+1486
-12
No files matched your search
@@ -13,7 +13,7 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get -y update --fix-missing
|
||||
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev lsb-release
|
||||
sudo apt-get install -y cppcheck libbluetooth-dev libsdbus-c++-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev lsb-release
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v7
|
||||
|
||||
@@ -24,7 +24,7 @@ runs:
|
||||
set -euo pipefail
|
||||
sudo apt-get -y update --fix-missing
|
||||
sudo apt-get install -y ccache lcov \
|
||||
libbluetooth-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev openssl libssl-dev \
|
||||
libbluetooth-dev libsdbus-c++-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev openssl libssl-dev \
|
||||
libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev libcurl4-gnutls-dev
|
||||
|
||||
- name: Install PlatformIO
|
||||
|
||||
@@ -11,4 +11,4 @@ runs:
|
||||
- name: Install libs needed for native build
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev libcurl4-gnutls-dev libnotify-dev
|
||||
sudo apt-get install -y libbluetooth-dev libsdbus-c++-dev libgpiod-dev libyaml-cpp-dev libjsoncpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev libi2c-dev libuv1-dev libcurl4-gnutls-dev libnotify-dev
|
||||
+2
-2
@@ -15,7 +15,7 @@ ENV PIP_BREAK_SYSTEM_PACKAGES=1
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
curl wget g++ zip git ca-certificates pkg-config \
|
||||
python3-pip python3-grpc-tools \
|
||||
libgpiod-dev libyaml-cpp-dev libjsoncpp-dev libbluetooth-dev libi2c-dev libuv1-dev \
|
||||
libgpiod-dev libyaml-cpp-dev libjsoncpp-dev libbluetooth-dev libsdbus-c++-dev libi2c-dev libuv1-dev \
|
||||
libcurl4-gnutls-dev libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev \
|
||||
libx11-dev libinput-dev libxkbcommon-x11-dev libsqlite3-dev libsdl2-dev libnotify-dev \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
@@ -54,7 +54,7 @@ ENV TZ=Etc/UTC
|
||||
USER root
|
||||
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install \
|
||||
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libjsoncpp26 libi2c0 libuv1t64 libusb-1.0-0-dev \
|
||||
libc-bin libc6 libgpiod3 libyaml-cpp0.8 libjsoncpp26 libsdbus-c++2 libi2c0 libuv1t64 libusb-1.0-0-dev \
|
||||
libcurl4t64 liborcania2.3 libulfius2.7t64 libssl3t64 \
|
||||
libx11-6 libinput10 libxkbcommon-x11-0 libsdl2-2.0-0 libnotify4 \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/* \
|
||||
|
||||
@@ -250,6 +250,10 @@ Webserver:
|
||||
# SSLKey: /etc/meshtasticd/ssl/private_key.pem # Path to SSL Key, generated if not present
|
||||
# SSLCert: /etc/meshtasticd/ssl/certificate.pem # Path to SSL Certificate, generated if not present
|
||||
|
||||
Bluetooth:
|
||||
# Enabled: true # Advertise a BLE peripheral via BlueZ, like other Meshtastic devices (Linux only)
|
||||
# AdapterId: hci0 # BlueZ adapter to use
|
||||
|
||||
HostMetrics:
|
||||
# ReportInterval: 30 # Interval in minutes between HostMetrics report packets, or 0 for disabled
|
||||
# Channel: 0 # channel to send Host Metrics over. Defaults to the primary channel.
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<!--
|
||||
Allow the unprivileged meshtasticd service user to talk to bluetoothd, so
|
||||
meshtasticd can register its BLE GATT service, advertisement and pairing agent.
|
||||
bluetoothd's own policy (bluetooth.conf) covers the reverse direction: it runs as
|
||||
root, which may call back into any client's GATT objects.
|
||||
|
||||
The rule is deliberately destination-wide rather than per-interface: meshtasticd
|
||||
legitimately uses GattManager1, LEAdvertisingManager1, AgentManager1, Adapter1,
|
||||
Device1, ObjectManager, Properties and Introspectable, and a stale interface
|
||||
allowlist fails as a hard-to-diagnose runtime denial after a BlueZ upgrade. The
|
||||
scope is still bounded: only the meshtasticd user, and only toward org.bluez.
|
||||
-->
|
||||
<busconfig>
|
||||
<policy user="meshtasticd">
|
||||
<allow send_destination="org.bluez"/>
|
||||
</policy>
|
||||
</busconfig>
|
||||
@@ -1,6 +1,7 @@
|
||||
[Unit]
|
||||
Description=Meshtastic %i Daemon
|
||||
After=network-online.target
|
||||
After=network-online.target bluetooth.service
|
||||
Wants=bluetooth.service
|
||||
StartLimitInterval=200
|
||||
StartLimitBurst=5
|
||||
|
||||
|
||||
@@ -147,6 +147,16 @@ assert "empty sections are not a fault" 0 empty-sections.yaml check \
|
||||
assert "warnings alone do not fail the run" 0 unknown-key.yaml check \
|
||||
"unknown key 'Lora.Frequency'" \
|
||||
"Result: 0 errors, 1 warning"
|
||||
# On a build without BLE support the same file additionally reports the build-time
|
||||
# gap as a warning, so only the error count is asserted here.
|
||||
assert "bluetooth section parses" 0 bluetooth.yaml check \
|
||||
"Result: 0 errors,"
|
||||
assert "bluetooth bad Enabled is silently defaulted" 0 bluetooth-bad-enabled.yaml check \
|
||||
"Bluetooth.Enabled is not a true/false value" \
|
||||
"Result: 0 errors, 1 warning"
|
||||
assert "bluetooth AdapterId must name a real adapter" 0 bluetooth-bad-adapter.yaml check \
|
||||
"is not a BlueZ adapter name" \
|
||||
"Result: 0 errors, 1 warning"
|
||||
|
||||
echo
|
||||
echo "module names are matched exactly:"
|
||||
|
||||
Vendored
+2
@@ -17,6 +17,7 @@ Build-Depends: debhelper-compat (= 13),
|
||||
libjsoncpp-dev,
|
||||
libgpiod-dev,
|
||||
libbluetooth-dev,
|
||||
libsdbus-c++-dev,
|
||||
libusb-1.0-0-dev,
|
||||
libi2c-dev,
|
||||
libuv1-dev,
|
||||
@@ -40,6 +41,7 @@ Architecture: any
|
||||
Depends: adduser,
|
||||
${misc:Depends},
|
||||
${shlibs:Depends}
|
||||
Recommends: bluez
|
||||
Description: Meshtastic daemon for communicating with Meshtastic devices
|
||||
Meshtastic is an off-grid text communication platform that uses inexpensive
|
||||
LoRa radios.
|
||||
Vendored
+1
@@ -5,6 +5,7 @@ bin/config.d/* etc/meshtasticd/available.d
|
||||
|
||||
bin/meshtasticd.service lib/systemd/system
|
||||
bin/meshtasticd-start.sh usr/bin
|
||||
bin/dbus-1/meshtasticd.conf usr/share/dbus-1/system.d
|
||||
|
||||
web/* usr/share/meshtasticd/web
|
||||
|
||||
|
||||
Vendored
+1
@@ -37,6 +37,7 @@ case "$1" in
|
||||
getent group video >/dev/null 2>/dev/null && adduser meshtasticd video >/dev/null 2>/dev/null
|
||||
getent group audio >/dev/null 2>/dev/null && adduser meshtasticd audio >/dev/null 2>/dev/null
|
||||
getent group input >/dev/null 2>/dev/null && adduser meshtasticd input >/dev/null 2>/dev/null
|
||||
getent group bluetooth >/dev/null 2>/dev/null && adduser meshtasticd bluetooth >/dev/null 2>/dev/null
|
||||
|
||||
|
||||
# migrate /root/.portduino to /var/lib/meshtasticd/.portduino
|
||||
|
||||
@@ -37,6 +37,7 @@ BuildRequires: pkgconfig(yaml-cpp)
|
||||
BuildRequires: pkgconfig(jsoncpp)
|
||||
BuildRequires: pkgconfig(libgpiod)
|
||||
BuildRequires: pkgconfig(bluez)
|
||||
BuildRequires: pkgconfig(sdbus-c++)
|
||||
BuildRequires: pkgconfig(libusb-1.0)
|
||||
BuildRequires: libi2c-devel
|
||||
BuildRequires: pkgconfig(libuv)
|
||||
@@ -62,6 +63,8 @@ BuildRequires: pkgconfig(libbsd-overlay)
|
||||
%endif
|
||||
|
||||
Requires: systemd-udev
|
||||
# bluetoothd and the 'bluetooth' group, needed for BLE peripheral support
|
||||
Recommends: bluez
|
||||
|
||||
# Declare that this package provides the user/group it creates in %pre
|
||||
# Required for Fedora 43+ which tracks users/groups as RPM dependencies
|
||||
@@ -109,6 +112,9 @@ install -D -m 0644 bin/meshtasticd.service %{buildroot}%{_unitdir}/meshtasticd.s
|
||||
# Install meshtasticd start wrapper
|
||||
install -D -m 0755 bin/meshtasticd-start.sh %{buildroot}%{_bindir}/meshtasticd-start.sh
|
||||
|
||||
# Install D-Bus policy (lets the meshtasticd user talk to bluetoothd for BLE)
|
||||
install -D -m 0644 bin/dbus-1/meshtasticd.conf %{buildroot}%{_datadir}/dbus-1/system.d/meshtasticd.conf
|
||||
|
||||
# Install the web files under /usr/share/meshtasticd/web
|
||||
mkdir -p %{buildroot}%{_datadir}/meshtasticd/web
|
||||
cp -r web/* %{buildroot}%{_datadir}/meshtasticd/web
|
||||
@@ -135,6 +141,7 @@ getent group i2c > /dev/null && usermod -a -G i2c %{meshtasticd_user} > /dev/nul
|
||||
getent group video > /dev/null && usermod -a -G video %{meshtasticd_user} > /dev/null
|
||||
getent group audio > /dev/null && usermod -a -G audio %{meshtasticd_user} > /dev/null
|
||||
getent group input > /dev/null && usermod -a -G input %{meshtasticd_user} > /dev/null
|
||||
getent group bluetooth > /dev/null && usermod -a -G bluetooth %{meshtasticd_user} > /dev/null
|
||||
exit 0
|
||||
|
||||
%triggerin -- meshtasticd < 2.6.9
|
||||
@@ -174,6 +181,7 @@ fi
|
||||
%config(noreplace) %{_sysconfdir}/meshtasticd/config.yaml
|
||||
%config %{_sysconfdir}/meshtasticd/available.d/*
|
||||
%{_unitdir}/meshtasticd.service
|
||||
%attr(0644,root,root) %{_datadir}/dbus-1/system.d/meshtasticd.conf
|
||||
%dir %{_datadir}/meshtasticd
|
||||
%dir %{_datadir}/meshtasticd/web
|
||||
%{_datadir}/meshtasticd/web/*
|
||||
|
||||
@@ -235,6 +235,8 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_
|
||||
isBleConnected = nimbleBluetooth && nimbleBluetooth->isActive() && nimbleBluetooth->isConnected();
|
||||
#elif defined(ARCH_NRF52)
|
||||
isBleConnected = nrf52Bluetooth != nullptr && nrf52Bluetooth->isConnected();
|
||||
#elif defined(MESHTASTIC_LINUX_BLE)
|
||||
isBleConnected = linuxBluetooth != nullptr && linuxBluetooth->isEnabled() && linuxBluetooth->isConnected();
|
||||
#endif
|
||||
if (isBleConnected) {
|
||||
auto thread = concurrency::OSThread::currentThread;
|
||||
@@ -251,6 +253,8 @@ void RedirectablePrint::log_to_ble(const char *logLevel, const char *format, va_
|
||||
nimbleBluetooth->sendLog(buffer.get(), size);
|
||||
#elif defined(ARCH_NRF52)
|
||||
nrf52Bluetooth->sendLog(buffer.get(), size);
|
||||
#elif defined(MESHTASTIC_LINUX_BLE)
|
||||
linuxBluetooth->sendLog(buffer.get(), size);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -76,6 +76,10 @@ NimbleBluetooth *nimbleBluetooth = nullptr;
|
||||
NRF52Bluetooth *nrf52Bluetooth = nullptr;
|
||||
#endif
|
||||
|
||||
#ifdef MESHTASTIC_LINUX_BLE
|
||||
LinuxBluetooth *linuxBluetooth = nullptr;
|
||||
#endif
|
||||
|
||||
#ifdef MESHTASTIC_ENABLE_APPROTECT
|
||||
#include "security/APProtect.h"
|
||||
#endif
|
||||
@@ -279,7 +283,7 @@ const char *firmware_version = optstr(APP_VERSION_SHORT);
|
||||
|
||||
const char *getDeviceName()
|
||||
{
|
||||
uint8_t dmac[6];
|
||||
uint8_t dmac[6] = {0};
|
||||
|
||||
getMacAddr(dmac);
|
||||
|
||||
@@ -1361,8 +1365,9 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
|
||||
|
||||
// No bluetooth on these targets (yet):
|
||||
// Pico W / 2W may get it at some point
|
||||
// Portduino and ESP32-C6 are excluded because we don't have a working bluetooth stacks integrated yet.
|
||||
#if defined(ARCH_RP2040) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32) || defined(CONFIG_IDF_TARGET_ESP32C6) || !HAS_BLUETOOTH
|
||||
// ESP32-C6 is excluded because we don't have a working bluetooth stack integrated yet.
|
||||
// Portduino only has BLE when built against BlueZ/sdbus-c++, so it falls out via !HAS_BLUETOOTH.
|
||||
#if defined(ARCH_RP2040) || defined(ARCH_STM32) || defined(CONFIG_IDF_TARGET_ESP32C6) || !HAS_BLUETOOTH
|
||||
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_BLUETOOTH_CONFIG;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@ extern NimbleBluetooth *nimbleBluetooth;
|
||||
#include "NRF52Bluetooth.h"
|
||||
extern NRF52Bluetooth *nrf52Bluetooth;
|
||||
#endif
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "platform/portduino/LinuxBluetooth.h" // self-guards; defines MESHTASTIC_LINUX_BLE when BLE is compiled in
|
||||
#ifdef MESHTASTIC_LINUX_BLE
|
||||
extern LinuxBluetooth *linuxBluetooth;
|
||||
#endif
|
||||
#endif
|
||||
#if !MESHTASTIC_EXCLUDE_I2C
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
#endif
|
||||
|
||||
@@ -952,6 +952,18 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
|
||||
bond_print_list(BLE_GAP_ROLE_CENTRAL);
|
||||
Bluefruit.Periph.clearBonds();
|
||||
Bluefruit.Central.clearBonds();
|
||||
#endif
|
||||
#ifdef MESHTASTIC_LINUX_BLE
|
||||
// isEnabled(), not just the pointer: a setup() that threw leaves the object
|
||||
// allocated with its bus torn down, and clearBonds() needs a live connection.
|
||||
if (linuxBluetooth && linuxBluetooth->isEnabled()) {
|
||||
LOG_INFO("Clear bluetooth bonds");
|
||||
linuxBluetooth->clearBonds();
|
||||
} else {
|
||||
// BlueZ bonds live in the host adapter's store, not ours, so there is no
|
||||
// removing them from here without that connection.
|
||||
LOG_WARN("BLE off, host bluetooth bonds left in place");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -1819,6 +1819,11 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
|
||||
if (config.bluetooth.enabled && nrf52Bluetooth) {
|
||||
conn.bluetooth.is_connected = nrf52Bluetooth->isConnected();
|
||||
}
|
||||
#elif defined(MESHTASTIC_LINUX_BLE)
|
||||
if (config.bluetooth.enabled && linuxBluetooth) {
|
||||
conn.bluetooth.is_connected = linuxBluetooth->isConnected();
|
||||
conn.bluetooth.rssi = linuxBluetooth->getRssi();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
conn.has_serial = true; // No serial-less devices
|
||||
@@ -2508,6 +2513,9 @@ void disableBluetooth()
|
||||
#elif defined(ARCH_NRF52)
|
||||
if (nrf52Bluetooth)
|
||||
nrf52Bluetooth->shutdown();
|
||||
#elif defined(MESHTASTIC_LINUX_BLE)
|
||||
if (linuxBluetooth)
|
||||
linuxBluetooth->deinit();
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
@@ -89,6 +89,7 @@ const std::map<std::string, std::set<std::string>> &schema()
|
||||
{"I2C", {"I2CDevice"}},
|
||||
{"Logging", {"LogLevel", "TraceFile", "JSONFile", "JSONFileRotate", "JSONFilter", "AsciiLogs"}},
|
||||
{"Webserver", {"Port", "RootPath", "SSLCert", "SSLKey"}},
|
||||
{"Bluetooth", {"Enabled", "AdapterId"}},
|
||||
{"HostMetrics", {"ReportInterval", "Channel", "UserStringCommand"}},
|
||||
// Read by packaging/menu tooling rather than by meshtasticd itself.
|
||||
{"Meta", {}},
|
||||
@@ -592,6 +593,8 @@ const std::map<std::string, ValueSpec> &valueSpecs()
|
||||
{"Webserver.RootPath", {kString, false}},
|
||||
{"Webserver.SSLCert", {kString, false}},
|
||||
{"Webserver.SSLKey", {kString, false}},
|
||||
{"Bluetooth.Enabled", {kBool, false}},
|
||||
{"Bluetooth.AdapterId", {kString, false}},
|
||||
{"HostMetrics.ReportInterval", {kInt, false}},
|
||||
{"HostMetrics.Channel", {kInt, false}},
|
||||
{"HostMetrics.UserStringCommand", {kString, false}},
|
||||
@@ -894,6 +897,19 @@ void checkSection(const std::string &file, const std::string §ion, const YAM
|
||||
if (value.IsSequence())
|
||||
for (const auto &pin : value)
|
||||
checkPinNode(file, section + "." + key, pin, findings);
|
||||
} else if (section == "Bluetooth" && key == "AdapterId") {
|
||||
// LinuxBluetooth uses this verbatim as the BlueZ object path (/org/bluez/<id>), while the
|
||||
// MAC fallback only reads the leading hciN. A value like "hci1junk" therefore looks
|
||||
// plausible, yields a MAC, and then finds no adapter -- BLE just never comes up. Only
|
||||
// hci<digits> is a real adapter name.
|
||||
const std::string adapter = value.as<std::string>("");
|
||||
const bool wellFormed = adapter.rfind("hci", 0) == 0 && adapter.size() > 3 &&
|
||||
adapter.find_first_not_of("0123456789", 3) == std::string::npos;
|
||||
if (!wellFormed)
|
||||
findings.push_back({kWarn, file, lineOf(value),
|
||||
"Bluetooth.AdapterId '" + adapter +
|
||||
"' is not a BlueZ adapter name. It must be hci followed by digits (hci0, hci1); "
|
||||
"anything else leaves no /org/bluez entry to attach to and Bluetooth stays off"});
|
||||
} else if (key == "JoystickButtons") {
|
||||
checkJoystickButtons(file, value, findings);
|
||||
} else if ((section == "Lora" && kLoraPinKeys.count(key)) ||
|
||||
@@ -1217,6 +1233,17 @@ void checkMergedConfig(const PathIndex &paths, std::vector<Finding> &findings)
|
||||
"startup. Rebuild with hzeller/rpi-rgb-led-matrix installed (it provides rgbmatrix.pc)"});
|
||||
#endif
|
||||
|
||||
#if !HAS_BLUETOOTH
|
||||
// Same class of build-time gap as HUB75 above, but only a warning: BLE
|
||||
// quietly stays off rather than aborting startup.
|
||||
if (portduino_config.bluetooth_enabled)
|
||||
findings.push_back({kWarn, merged, 0,
|
||||
"Bluetooth.Enabled is true but this meshtasticd "
|
||||
"was built without BLE support, so Bluetooth "
|
||||
"stays off. Rebuild with libsdbus-c++-dev "
|
||||
"installed (it provides sdbus-c++.pc)"});
|
||||
#endif
|
||||
|
||||
if (portduino_config.lora_cs_pin.enabled && !portduino_config.lora_spi_dev.empty() &&
|
||||
portduino_config.lora_spi_dev != "ch341")
|
||||
findings.push_back({kInfo, merged, 0,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
// BLE peripheral support for meshtasticd, implemented against BlueZ's D-Bus
|
||||
// GATT, advertising and agent APIs via sdbus-c++. Compiled only on Linux hosts
|
||||
// with the sdbus-c++ dev package present (same optional-dependency idiom as the
|
||||
// ulfius webserver); everywhere else this header is empty and HAS_BLUETOOTH
|
||||
// stays 0.
|
||||
#if defined(ARCH_PORTDUINO) && defined(PORTDUINO_LINUX_HARDWARE) && __has_include(<sdbus-c++/sdbus-c++.h>)
|
||||
|
||||
#define MESHTASTIC_LINUX_BLE 1
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* Runs the standard Meshtastic BLE service (toRadio/fromRadio/fromNum/logRadio)
|
||||
* through bluetoothd. Method names deliberately match
|
||||
* NimbleBluetooth/NRF52Bluetooth so the cross-platform call sites
|
||||
* (setBluetoothEnable, AdminModule, RedirectablePrint, ...) read the same on
|
||||
* every architecture.
|
||||
*
|
||||
* Note this intentionally does not derive from BluetoothApi: no call site uses
|
||||
* that base polymorphically (each platform is reached through its own concrete
|
||||
* global pointer), and the base's declared-but-undefined virtuals make its
|
||||
* vtable/typeinfo unlinkable in unoptimized RTTI builds such as the native
|
||||
* coverage env.
|
||||
*/
|
||||
class LinuxBluetooth
|
||||
{
|
||||
public:
|
||||
LinuxBluetooth();
|
||||
~LinuxBluetooth();
|
||||
|
||||
/// Connect to BlueZ, export the GATT application, register agent +
|
||||
/// advertisement. Failure (no adapter, D-Bus policy denial, no bluetoothd)
|
||||
/// logs and leaves BLE off.
|
||||
void setup();
|
||||
/// Stop advertising only; an established connection stays up (PowerFSM dark
|
||||
/// states).
|
||||
void shutdown();
|
||||
/// Re-register the advertisement after shutdown().
|
||||
void resumeAdvertising();
|
||||
/// Full teardown: unregister everything and drop the bus connection.
|
||||
void deinit();
|
||||
|
||||
void clearBonds();
|
||||
bool isConnected();
|
||||
int getRssi();
|
||||
/// setup() succeeded and deinit() has not run.
|
||||
bool isEnabled();
|
||||
void sendLog(const uint8_t *logMessage, size_t length);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -37,6 +37,12 @@
|
||||
#include <bluetooth/hci.h>
|
||||
#endif
|
||||
|
||||
#include "LinuxBluetooth.h"
|
||||
#ifdef MESHTASTIC_LINUX_BLE
|
||||
#include "mesh/NodeDB.h" // config.bluetooth.enabled
|
||||
extern LinuxBluetooth *linuxBluetooth; // defined in main.cpp
|
||||
#endif
|
||||
|
||||
#ifdef PORTDUINO_LINUX_HARDWARE
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
@@ -152,7 +158,36 @@ char stdoutBuffer[512];
|
||||
// FIXME - move setBluetoothEnable into a HALPlatform class
|
||||
void setBluetoothEnable(bool enable)
|
||||
{
|
||||
// not needed
|
||||
#ifdef MESHTASTIC_LINUX_BLE
|
||||
// Disable is not gated on the config flags: if BLE is running it must always be
|
||||
// stoppable, even after the device config was switched off underneath it.
|
||||
if (!enable) {
|
||||
if (linuxBluetooth) {
|
||||
// Stop advertising only; a live phone connection survives PowerFSM state
|
||||
// dips.
|
||||
linuxBluetooth->shutdown();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Opt-in twice: the config.yaml Bluetooth section must enable BLE on this
|
||||
// host, and the regular device config (like every other platform) must have
|
||||
// Bluetooth on.
|
||||
if (!portduino_config.bluetooth_enabled || !config.bluetooth.enabled)
|
||||
return;
|
||||
if (!linuxBluetooth) {
|
||||
LOG_INFO("Init LinuxBluetooth (adapter %s)", portduino_config.bluetooth_adapter.c_str());
|
||||
linuxBluetooth = new LinuxBluetooth();
|
||||
linuxBluetooth->setup();
|
||||
} else if (!linuxBluetooth->isEnabled()) {
|
||||
// The backend exists but never came up -- bluetoothd was not ready, the adapter was
|
||||
// missing, or policy refused us. resumeAdvertising() returns immediately while disabled, so
|
||||
// without this a transient failure at boot would keep BLE off until the process restarted.
|
||||
LOG_INFO("Retry LinuxBluetooth setup (adapter %s)", portduino_config.bluetooth_adapter.c_str());
|
||||
linuxBluetooth->setup();
|
||||
} else {
|
||||
linuxBluetooth->resumeAdvertising();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void cpuDeepSleep(uint32_t msecs)
|
||||
@@ -295,9 +330,23 @@ void getMacAddr(uint8_t *dmac)
|
||||
return;
|
||||
} else {
|
||||
#ifdef PORTDUINO_LINUX_HARDWARE
|
||||
// Cache after the first successful read. The adapter address can't change at
|
||||
// runtime, this now gets called from BLE property getters on every bluetoothd
|
||||
// read (not just at startup), and the socket used to leak one fd per call.
|
||||
static uint8_t cachedMac[6];
|
||||
static bool macCached = false;
|
||||
if (macCached) {
|
||||
memcpy(dmac, cachedMac, 6);
|
||||
return;
|
||||
}
|
||||
struct hci_dev_info di = {0};
|
||||
di.dev_id = 0;
|
||||
bdaddr_t bdaddr;
|
||||
// Read the adapter configured for BLE (Bluetooth.AdapterId) so the node
|
||||
// identity matches the advertised adapter; a name that doesn't parse as
|
||||
// hci<N> falls back to hci0, preserving the pre-BLE behavior.
|
||||
unsigned adapterIndex = 0;
|
||||
if (sscanf(portduino_config.bluetooth_adapter.c_str(), "hci%u", &adapterIndex) != 1)
|
||||
adapterIndex = 0;
|
||||
di.dev_id = adapterIndex;
|
||||
int btsock;
|
||||
btsock = socket(AF_BLUETOOTH, SOCK_RAW, 1);
|
||||
if (btsock < 0) { // If anything fails, just return with the default value
|
||||
@@ -305,8 +354,10 @@ void getMacAddr(uint8_t *dmac)
|
||||
}
|
||||
|
||||
if (ioctl(btsock, HCIGETDEVINFO, (void *)&di)) {
|
||||
close(btsock);
|
||||
return;
|
||||
}
|
||||
close(btsock);
|
||||
|
||||
dmac[0] = di.bdaddr.b[5];
|
||||
dmac[1] = di.bdaddr.b[4];
|
||||
@@ -314,6 +365,8 @@ void getMacAddr(uint8_t *dmac)
|
||||
dmac[3] = di.bdaddr.b[2];
|
||||
dmac[4] = di.bdaddr.b[1];
|
||||
dmac[5] = di.bdaddr.b[0];
|
||||
memcpy(cachedMac, dmac, 6);
|
||||
macCached = true;
|
||||
#elif defined(__APPLE__)
|
||||
// No BlueZ on macOS, but we can fall back to the host's primary
|
||||
// network interface MAC. `en0` is Wi-Fi on every shipping Mac
|
||||
@@ -1351,6 +1404,18 @@ bool loadConfig(const char *configPath)
|
||||
(yamlConfig["Webserver"]["SSLCert"]).as<std::string>("/etc/meshtasticd/ssl/certificate.pem");
|
||||
}
|
||||
|
||||
if (yamlConfig["Bluetooth"]) {
|
||||
// Assign per key, not per section. loadConfig() runs once for every file in config.d, so
|
||||
// reading an absent key as its default would let a later file that names only one of
|
||||
// these silently reset the other -- `AdapterId: hci1` alone would turn Bluetooth off,
|
||||
// and `Enabled: true` alone would drag the adapter back to hci0. Only what a file
|
||||
// actually says should override what an earlier one set.
|
||||
if (yamlConfig["Bluetooth"]["Enabled"])
|
||||
portduino_config.bluetooth_enabled = (yamlConfig["Bluetooth"]["Enabled"]).as<bool>(false);
|
||||
if (yamlConfig["Bluetooth"]["AdapterId"])
|
||||
portduino_config.bluetooth_adapter = (yamlConfig["Bluetooth"]["AdapterId"]).as<std::string>("hci0");
|
||||
}
|
||||
|
||||
if (yamlConfig["HostMetrics"]) {
|
||||
portduino_config.hostMetrics_channel = (yamlConfig["HostMetrics"]["Channel"]).as<int>(0);
|
||||
portduino_config.hostMetrics_interval = (yamlConfig["HostMetrics"]["ReportInterval"]).as<int>(0);
|
||||
|
||||
@@ -265,6 +265,11 @@ extern struct portduino_config_struct {
|
||||
std::string webserver_ssl_cert_path = "/etc/meshtasticd/ssl/certificate.pem";
|
||||
int webserverport = -1;
|
||||
|
||||
// Bluetooth (BLE peripheral via BlueZ; Linux only, and only when built with
|
||||
// sdbus-c++)
|
||||
bool bluetooth_enabled = false;
|
||||
std::string bluetooth_adapter = "hci0";
|
||||
|
||||
// HostMetrics
|
||||
std::string hostMetrics_user_command = "";
|
||||
int hostMetrics_interval = 0;
|
||||
@@ -641,6 +646,14 @@ extern struct portduino_config_struct {
|
||||
out << YAML::EndMap; // Webserver
|
||||
}
|
||||
|
||||
// Bluetooth
|
||||
if (bluetooth_enabled) {
|
||||
out << YAML::Key << "Bluetooth" << YAML::Value << YAML::BeginMap;
|
||||
out << YAML::Key << "Enabled" << YAML::Value << bluetooth_enabled;
|
||||
out << YAML::Key << "AdapterId" << YAML::Value << bluetooth_adapter;
|
||||
out << YAML::EndMap; // Bluetooth
|
||||
}
|
||||
|
||||
// HostMetrics
|
||||
if (hostMetrics_user_command != "") {
|
||||
out << YAML::Key << "HostMetrics" << YAML::Value << YAML::BeginMap;
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Thin compatibility layer over the sdbus-c++ 1.x and 2.x APIs, so
|
||||
* LinuxBluetooth.cpp can be written once against a single registration/proxy
|
||||
* syntax.
|
||||
*
|
||||
* The 2.x major release (Debian trixie, Fedora, current Raspberry Pi OS)
|
||||
* replaced the 1.x fluent object-registration API
|
||||
* (registerMethod().onInterface().implementedAs() + finishRegistration()) with
|
||||
* vtable items (addVTable(items...).forInterface()), and made
|
||||
* bus/interface/member names strong types. 1.x (Debian bookworm ships 1.2,
|
||||
* Ubuntu 24.04 ships 1.4) predates both. MESHTASTIC_SDBUS_CPP_V2 is set from
|
||||
* pkg-config in variants/native/portduino.ini.
|
||||
*
|
||||
* Only what LinuxBluetooth.cpp actually uses is shimmed.
|
||||
*/
|
||||
|
||||
#include <sdbus-c++/sdbus-c++.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace sdbuscompat
|
||||
{
|
||||
|
||||
#ifdef MESHTASTIC_SDBUS_CPP_V2
|
||||
|
||||
// Error argument of an async method-reply callback (2.x passes an optional, 1.x
|
||||
// a pointer).
|
||||
using AsyncError = std::optional<sdbus::Error>;
|
||||
inline bool asyncFailed(const AsyncError &error)
|
||||
{
|
||||
return error.has_value();
|
||||
}
|
||||
inline std::string asyncErrorMessage(const AsyncError &error)
|
||||
{
|
||||
return error ? error->getMessage() : std::string();
|
||||
}
|
||||
|
||||
template <typename F> auto method(const char *name, F &&callback)
|
||||
{
|
||||
return sdbus::registerMethod(std::string(name)).implementedAs(std::forward<F>(callback));
|
||||
}
|
||||
|
||||
template <typename F> auto property(const char *name, F &&getter)
|
||||
{
|
||||
return sdbus::registerProperty(std::string(name)).withGetter(std::forward<F>(getter));
|
||||
}
|
||||
|
||||
template <typename... Items> void addVTable(sdbus::IObject &object, const char *interfaceName, Items &&...items)
|
||||
{
|
||||
object.addVTable(std::forward<Items>(items)...).forInterface(sdbus::InterfaceName{interfaceName});
|
||||
}
|
||||
|
||||
inline std::unique_ptr<sdbus::IProxy> makeProxy(sdbus::IConnection &connection, const char *destination, const std::string &path)
|
||||
{
|
||||
return sdbus::createProxy(connection, sdbus::ServiceName{destination}, sdbus::ObjectPath{path});
|
||||
}
|
||||
|
||||
inline sdbus::Error dbusError(const char *name, const char *message)
|
||||
{
|
||||
return sdbus::Error(sdbus::Error::Name{name}, message);
|
||||
}
|
||||
|
||||
inline void emitPropertiesChanged(sdbus::IObject &object, const char *interfaceName, const char *propertyName)
|
||||
{
|
||||
object.emitPropertiesChangedSignal(interfaceName, {sdbus::PropertyName{propertyName}});
|
||||
}
|
||||
|
||||
#else // sdbus-c++ 1.x
|
||||
|
||||
using AsyncError = const sdbus::Error *;
|
||||
inline bool asyncFailed(AsyncError error)
|
||||
{
|
||||
return error != nullptr;
|
||||
}
|
||||
inline std::string asyncErrorMessage(AsyncError error)
|
||||
{
|
||||
return error ? error->getMessage() : std::string();
|
||||
}
|
||||
|
||||
// 1.x has no vtable-item concept; emulate one with type-erased "apply this
|
||||
// registration to that object" closures so call sites read the same as the 2.x
|
||||
// path.
|
||||
struct VTableItem {
|
||||
std::function<void(sdbus::IObject &, const std::string &)> apply;
|
||||
};
|
||||
|
||||
template <typename F> VTableItem method(const char *name, F &&callback)
|
||||
{
|
||||
return {[name = std::string(name), cb = std::forward<F>(callback)](sdbus::IObject &object, const std::string &iface) mutable {
|
||||
object.registerMethod(name).onInterface(iface).implementedAs(std::move(cb));
|
||||
}};
|
||||
}
|
||||
|
||||
template <typename F> VTableItem property(const char *name, F &&getter)
|
||||
{
|
||||
return {[name = std::string(name), g = std::forward<F>(getter)](sdbus::IObject &object, const std::string &iface) mutable {
|
||||
object.registerProperty(name).onInterface(iface).withGetter(std::move(g));
|
||||
}};
|
||||
}
|
||||
|
||||
template <typename... Items> void addVTable(sdbus::IObject &object, const char *interfaceName, Items &&...items)
|
||||
{
|
||||
(items.apply(object, interfaceName), ...);
|
||||
object.finishRegistration();
|
||||
}
|
||||
|
||||
inline std::unique_ptr<sdbus::IProxy> makeProxy(sdbus::IConnection &connection, const char *destination, const std::string &path)
|
||||
{
|
||||
return sdbus::createProxy(connection, destination, path);
|
||||
}
|
||||
|
||||
inline sdbus::Error dbusError(const char *name, const char *message)
|
||||
{
|
||||
return sdbus::Error(name, message);
|
||||
}
|
||||
|
||||
inline void emitPropertiesChanged(sdbus::IObject &object, const char *interfaceName, const char *propertyName)
|
||||
{
|
||||
object.emitPropertiesChangedSignal(interfaceName, {std::string(propertyName)});
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
inline std::unique_ptr<sdbus::IObject> makeObject(sdbus::IConnection &connection, const std::string &path)
|
||||
{
|
||||
// sdbus::ObjectPath derives from std::string in both major versions, so this
|
||||
// one needs no #if: 1.x takes std::string, 2.x takes ObjectPath.
|
||||
return sdbus::createObject(connection, sdbus::ObjectPath{path});
|
||||
}
|
||||
|
||||
inline void finishProxy(sdbus::IProxy &proxy)
|
||||
{
|
||||
#ifndef MESHTASTIC_SDBUS_CPP_V2
|
||||
// 1.x defers uponSignal() subscriptions until finishRegistration(); 2.x
|
||||
// subscribes immediately and has no such method.
|
||||
proxy.finishRegistration();
|
||||
#else
|
||||
(void)proxy;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace sdbuscompat
|
||||
@@ -38,4 +38,14 @@
|
||||
// HAS_HUB75_NATIVE stays undefined and the backend compiles out. See src/graphics/HUB75Display.cpp.
|
||||
#if defined(ARCH_PORTDUINO) && __has_include(<led-matrix.h>)
|
||||
#define HAS_HUB75_NATIVE 1
|
||||
#endif
|
||||
|
||||
// BLE peripheral support (BlueZ over D-Bus) on Linux turns on automatically
|
||||
// when the sdbus-c++ dev package is installed (wired up in
|
||||
// variants/native/portduino.ini). When absent, HAS_BLUETOOTH stays at
|
||||
// configuration.h's default of 0 and the whole backend compiles out. Runtime
|
||||
// activation additionally requires `Bluetooth: Enabled: true` in config.yaml.
|
||||
// See src/platform/portduino/LinuxBluetooth.cpp.
|
||||
#if defined(PORTDUINO_LINUX_HARDWARE) && __has_include(<sdbus-c++/sdbus-c++.h>)
|
||||
#define HAS_BLUETOOTH 1
|
||||
#endif
|
||||
+12
@@ -149,6 +149,18 @@ is `uint16_t[22]`, so extra points are dropped and out-of-range values wrap.
|
||||
| `statusmessage-long.yaml` | Copied into a `char[80]`, so it is safe but silently shortened to 79 characters. |
|
||||
| `configdir-missing.yaml` | **Crash regression guard** - an unreadable `ConfigDirectory` used to abort meshtasticd (and `--check`) with SIGABRT via an uncaught `filesystem_error`. |
|
||||
|
||||
## Bluetooth
|
||||
|
||||
BLE support is compiled in only when sdbus-c++ is present, so a valid `Bluetooth:`
|
||||
section is clean on a BLE build and reports the build-time gap as a warning on a
|
||||
non-BLE build. The assertions only test what holds either way.
|
||||
|
||||
| File | Expected |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------------ |
|
||||
| `bluetooth.yaml` | The section parses, both keys are known, and no error is invented. |
|
||||
| `bluetooth-bad-enabled.yaml` | `Enabled: maybe` is read with a fallback: silently replaced by `false`, so BLE never turns on. |
|
||||
| `bluetooth-bad-adapter.yaml` | `hci1junk` passes the MAC fallback but is not a BlueZ object path, so BLE silently never starts. |
|
||||
|
||||
## MAC address
|
||||
|
||||
The MAC no longer determines NodeNum - that comes from the public key - but a MAC
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# FAULT: AdapterId is not a BlueZ adapter name. The MAC fallback reads only the leading
|
||||
# hci1 and is happy, but LinuxBluetooth uses the whole string as the object path and finds
|
||||
# no /org/bluez/hci1junk, so BLE silently never comes up. Expect a WARNING and exit 0.
|
||||
Lora:
|
||||
Module: sx1262
|
||||
CS: 21
|
||||
IRQ: 16
|
||||
Busy: 20
|
||||
Reset: 18
|
||||
Bluetooth:
|
||||
Enabled: true
|
||||
AdapterId: hci1junk
|
||||
@@ -0,0 +1,11 @@
|
||||
# FAULT: Bluetooth.Enabled is not a true/false value. It is read with a fallback, so
|
||||
# the value is silently replaced by the default (false) and BLE never turns on.
|
||||
# Expect a WARNING and exit 0.
|
||||
Lora:
|
||||
Module: sx1262
|
||||
CS: 21
|
||||
IRQ: 16
|
||||
Busy: 20
|
||||
Reset: 18
|
||||
Bluetooth:
|
||||
Enabled: maybe
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# CLEAN on a build with BLE support (sdbus-c++ present); on a build without it the
|
||||
# checker reports the build-time gap as a warning instead. The suite asserts only what
|
||||
# holds either way: the section parses, the keys are known, and no error is invented.
|
||||
Lora:
|
||||
Module: sx1262
|
||||
CS: 21
|
||||
IRQ: 16
|
||||
Busy: 20
|
||||
Reset: 18
|
||||
Bluetooth:
|
||||
Enabled: true
|
||||
AdapterId: hci0
|
||||
@@ -68,6 +68,14 @@ build_flags =
|
||||
-D_FORTIFY_SOURCE=2
|
||||
-fstack-protector-all -Wstack-protector --param ssp-buffer-size=4
|
||||
-DPORTDUINO_LINUX_HARDWARE
|
||||
; BLE peripheral support via BlueZ D-Bus needs sdbus-c++; when the dev package is
|
||||
; absent these lines emit nothing and BLE compiles out (HAS_BLUETOOTH stays 0,
|
||||
; see src/platform/portduino/architecture.h). The second line flags the sdbus-c++
|
||||
; 2.x API for src/platform/portduino/SdbusCompat.h. Scriptlet lines must not sit
|
||||
; last in this list: native_base appends "-I ..." to the final line inline, and a
|
||||
; trailing scriptlet would swallow it into the shell command.
|
||||
!pkg-config --cflags --libs sdbus-c++ --silence-errors || :
|
||||
!pkg-config --exists 'sdbus-c++ >= 2' && echo '-DMESHTASTIC_SDBUS_CPP_V2' || :
|
||||
-lstdc++fs
|
||||
-lbluetooth
|
||||
-lgpiod
|
||||
|
||||
Reference in new issue
Block a user