Audience: a developer working inside the OpenView 3 repo. This document is self-contained — it captures every decision, the source code to port, the OpenView 3 architecture, the platform gotchas discovered during a prior investigation, and a phased plan.
Goal: Add a generic SMP / MCUmgr device-management tool to OpenView 3,
woven in as a top-level "Device Manager" destination. It must work with any
SMP-enabled BLE device (a Nordic dev kit running smp_svr, a HealthyPi Move,
etc.), not just ProtoCentral hardware. The standard OS/Image/FS management groups
are the generic tool; the custom HPI_HS Health Store group is ProtoCentral-
specific and only surfaces when detected.
The functionality was first prototyped as a standalone Flutter web app
(healthypi-move-webtool, path below) that spoke SMP/MCUmgr over Web
Bluetooth. That prototype hit a hard wall: Web Bluetooth on macOS/Windows/
Linux does not support on-demand BLE pairing/bonding (only ChromeOS + Android
do — per the Web Bluetooth implementation-status matrix).
The HealthyPi Move firmware forces bonding on connect (bt_conn_set_security(conn, BT_SECURITY_L2)
with passkey display), which desktop Web Bluetooth cannot complete → connect
fails with NetworkError: Unsupported device.
OpenView 3 is a native Flutter app (macOS/iOS/Android/Windows/Linux) using
universal_ble. Native CoreBluetooth/Android BLE do support
bonding/pairing normally (the OS shows the passkey dialog). So moving this
feature into OpenView eliminates the entire Web Bluetooth limitation and is
the correct long-term home. The prototype's protocol code is pure, portable Dart.
| Decision | Choice |
|---|---|
| Target app | OpenView 3 (protocental_openview3) — the new release |
Reuse OpenView's streaming ConnectionController? |
No. SMP is request/response over a separate GATT service; it must be a decoupled subsystem with its own connection. |
| Entry point / UX | Top-level "Device Manager" destination in the nav rail, with its own scan → connect → manage flow. |
| First capability to build | Device Info (OS group) + an echo smoke test (lowest-risk end-to-end proof), then Firmware DFU / Files / Health Store. |
| Generic vs ProtoCentral | OS/Image/FS + raw SMP console = generic (any device). HPI_HS Health Store shows only when that custom group is detected. |
| Capability gate | A device is "SMP-enabled" iff its GATT exposes the SMP service 8d53dc1d-1db7-4cd3-868b-8a527460aa84. |
| BLE plugin | universal_ble (BSD-3, maintained, all platforms incl. web) — adopted; spike passed, BLE streaming hardware-verified. See §1B. |
| Protocol code base | Reuse healthypi_studio's hand-rolled SMP client core + add a BLE transport (§2). Do not adopt the mcumgr_flutter plugin (native mobile-only; no macOS/Windows/Linux — see §2A). |
Status: DONE. The BLE layer is universal_ble (Navideck, BSD-3) — active,
maintained, and covering every target (Android, iOS, macOS, Windows, Linux, Web).
It has a complete API (startScan, connect, discoverServices,
subscribeNotifications + characteristicValueStream, write w/ & w/o response,
requestMtu, getSystemDevices) and an explicit pair()/unpair() API (relevant
to the bonded Move). BSD-3 keeps OpenView shippable as genuine open source with no
commercial-license or telemetry constraints.
The Phase-0 spike passed: BleService (lib/transport/ble_service.dart) runs on
universal_ble behind the unchanged TransportService interface, and BLE
streaming is hardware-verified. Build notes captured during adoption:
- iOS deployment target must be ≥ 13.1 (
universal_blefloor) — set inios/Podfile+ the Runner target. macOS ≥ 10.15, AndroidminSdk 21. universal_bleis a static/singleton API keyed bydeviceId(not device objects) — hold device id + resolved service/characteristic UUID strings.
Invariant: write the SMP BLE transport (§5) directly on universal_ble, and keep
the SMP + transport layers plugin-agnostic (TransportService / SmpTransport) so
the BLE plugin stays swappable. Do not leak plugin types above BleService /
SmpBleTransport. The Developer tab
(lib/controllers/developer_ble_controller.dart +
lib/ui/screens/developer_screen.dart) is a decoupled, unfiltered BLE playground
(scan / connect / GATT read-write-notify) — reuse it as the reference for the SMP
transport's scan/connect/discover/notify calls.
Do not reimplement SMP from scratch, and do not add the mcumgr_flutter
plugin. There are three candidate bases; the recommendation is #1.
1. ✅ RECOMMENDED — healthypi_studio's hand-rolled SMP client.
/Users/akw/Documents/GitHub/healthypi_studio/lib/services/smp_serial_client.dart
is a proven, in-production ProtoCentral MCUmgr/SMP client in pure Dart. It:
-
implements the SMP body (8-byte header + CBOR), seq-matched request/response, and RX fragment reassembly;
-
has a complete, working firmware-OTA
imageUpload()— SHA-256, device-driven offset chunking, progress callback,os reset,rchandling (image group 1 + os group 0). This is the hard part, already done. -
is already transport-agnostic: the identical SMP logic runs over serial (
flutter_libserialport) and TCP (ESP32 WiFi passthrough) — "only the byte sink/source differs." Adding BLE GATT as a third transport is exactly the seam it was built for. -
is native and all-platform (pure Dart; no BLE-plugin coupling).
Caveat: its outer framing is serial
uart_mcumgr(0x06 0x09start /0x04 0x14continuation, base64, crc16-xmodem). BLE does NOT use that. BLE is raw GATT: write the SMP frame to the char, and reassemble notifications by the header's big-endianlen(no base64, no crc16, no line framing). So: extract the SMP-transaction core + mcumgr command builders fromsmp_serial_client.dart, and give it a BLE transport that does length-prefixed notification reassembly (the web prototype'sSmpClientalready shows that reassembly cleanly — use it as the BLE-side reference).
2. Web prototype (healthypi-move-webtool). Cleaner separation
(SmpTransport abstraction, per-group files, HPI_HS models) and its SmpClient
already does BLE-style length-prefixed reassembly — but its transport targets
Web Bluetooth and its groups are less complete than Studio's OTA.
Use it as the structural/BLE-reassembly reference and for the HPI_HS models;
take the battle-tested OTA/image logic from Studio.
3. ❌ mcumgr_flutter plugin — do not adopt. See §2A-eval below.
-
It is a native iOS + Android only wrapper (over Apple's iOS-nRF-Connect-Device-Manager and the Android mcumgr Java lib). It has no macOS / Windows / Linux / web implementation → throws / unavailable on desktop.
-
OpenView 3 targets desktop (macOS/Windows/Linux) as first-class — and desktop macOS is the entire reason this work moved out of the web prototype.
mcumgr_fluttercannot cover the desktop targets, so it would force a second, mobile-only code path alongside a desktop one. -
No app in this org actually uses it. (
move_ultralight_flutteruses a custom DFU service0x FF30, not stock MCUmgr;healthypi_studiouses the hand-rolled Dart client above.) So there's no existingmcumgr_flutterintegration to inherit. -
A single portable Dart SMP client (option 1) covers every OpenView target with one code path and reuses proven org code. That wins decisively.
It remains a useful reference for stock OS/Image/FS group ids, command ids, and CBOR key shapes — read its Dart source if a wire detail is unclear — but don't depend on it.
The web prototype has the cleanest per-group protocol layer and the HPI_HS models; Studio has the proven OTA. Pull from both per §2A. Read these before writing anything.
Prototype repo: /Users/akw/Documents/GitHub/healthypi-move-webtool
Studio SMP client: /Users/akw/Documents/GitHub/healthypi_studio/lib/services/smp_serial_client.dart
Port these files (pure Dart, no UI/plugin coupling — copy nearly verbatim):
| Prototype file | Ports to (suggested) | Notes |
|---|---|---|
lib/smp/smp_message.dart |
lib/smp/smp_message.dart |
SMP header (8-byte) + CBOR codec. Depends only on package:cbor. |
lib/smp/smp_client.dart |
lib/smp/smp_client.dart |
seq allocation, request/response matching, fragment reassembly, timeout. |
lib/smp/smp_transport.dart |
lib/smp/smp_transport.dart |
Abstract transport interface + SmpConnectionState + SmpException/SmpTransportException. |
lib/mcumgr/os_mgmt.dart |
lib/mcumgr/os_mgmt.dart |
Phase 1. echo/params/taskstat/datetime/reset. |
lib/mcumgr/img_mgmt.dart |
lib/mcumgr/img_mgmt.dart |
Phase 3 (DFU). Partial in prototype. |
lib/mcumgr/fs_mgmt.dart |
lib/mcumgr/fs_mgmt.dart |
Phase 4 (files). |
lib/mcumgr/hpi_hs.dart |
lib/mcumgr/hpi_hs.dart |
Phase 5 (Health Store, ProtoCentral-only). |
lib/models/hs_sample.dart, lib/models/hs_type.dart |
lib/models/… |
HPI_HS decoders (18-byte LE sample stride; type registry). |
docs/HPI_HS_API.md |
docs/HPI_HS_API.md |
The custom-group contract. Copy for reference. |
ARCHITECTURE.md |
— | Read for the SMP wire-format details. |
Do NOT port: lib/smp/smp_web_bluetooth_transport.dart (Web-Bluetooth
specific), the prototype's lib/ui/*, lib/services/device_service.dart,
web/, or macos/. You will rewrite the transport (§5) and controller (§6) to
fit OpenView, and rebuild the screens in OpenView's theme.
cbor dependency: the prototype uses cbor: ^6.x. Add cbor to OpenView's
pubspec.yaml if not already present (grep cbor pubspec.yaml).
Transport (Nordic SMP GATT service):
- Service UUID:
8D53DC1D-1DB7-4CD3-868B-8A527460AA84 - Characteristic UUID:
DA2E7828-FBCE-4E01-AE9E-261174997C48(write-without-response + notify; used both directions)
SMP frame = 8-byte header + CBOR map payload:
offset field size notes
0 op 1 0=read-req 1=read-rsp 2=write-req 3=write-rsp
1 flags 1 0
2 len 2 payload length, BIG-endian u16
4 group 2 management group id, BIG-endian u16
6 seq 1 request sequence; response echoes it
7 id 1 command id within the group
- Response
op= requestop| 1; response echoesseq. - A single SMP response can span multiple BLE notifications — reassemble by
reading header
lenand buffering until8 + lenbytes arrive, then matchseqto the pending request. (SmpClientalready does this.) - Errors come back as SMP v1
{"rc": <int>}or SMP v2{"err":{"group","rc"}}— handle both. (Prototype currently only surfacesrc; adderrhandling.) - Chunk uploads to ATT MTU − 3 bytes.
Group ids: OS = 0, Image = 1, FS = 8, HPI_HS = 0x1000 (vendor range ≥ 64).
OS group (Phase 1) command ids: echo=0, taskstat=2, mpstat=3, datetime=4,
reset=5, mcumgr-params=6. Echo request payload {"d":"..."} → response {"r":"..."}.
OpenView uses provider for state and go_router for navigation.
Providers are wired manually and passed down, not created inline:
lib/main.dartbuilds the controller graph (UsbSerialService,BleService,WifiService,ConnectionController,ScanController,RecordingController,RecordingsBrowserController,SettingsController) and passes them toOpenViewApp. Add yourSmpControllerhere.lib/app.dartregisters them viaChangeNotifierProvider<T>.value(value: …)inside aMultiProvider. RegisterSmpControllerhere. It also intercepts window close inmain.dart's_CloseHandler— add SMP disconnect/teardown there too.
Navigation:
lib/ui/app_routes.dart—AppRoutesholds route constants + aGoRouterwith a singleShellRoutewrapping all screens inAdaptiveScaffold. Addstatic const deviceManager = '/device-manager';and aGoRoute.lib/ui/adaptive_scaffold.dart— the nav rail / bottom bar. The_destinationslist drives it, and_selectedIndex()maps a location to an index. Add a_NavDest(AppRoutes.deviceManager, 'Device Manager', Icons.dns_outlined, Icons.dns)and a matchinglocation.startsWith('/device-manager')case. (Mind the index math in_selectedIndex.)
Transport model (why we bypass it):
lib/transport/transport_service.dart— abstractTransportService(bytesstream in,send()out,scan/connect/disconnect) withTransportKind {ble, usb, wifi},TransportStatus,TransportTarget.lib/transport/ble_service.dart— the canonical example of theuniversal_bleAPI for scan/connect/notify/write. Read it and mirror its idioms (see §5). It is stream-oriented (one notify characteristic → byte stream), which is the wrong shape for SMP's request/response, so SMP gets its own transport rather than extending this.lib/controllers/connection_controller.dart— orchestrates streaming (framer→router→channel buffers). Not used by SMP.- BLE in OpenView is currently Sensything-only and filtered by
BoardRegistry.matchBle. Thehealthypidescriptor is USB/WiFi only and is "HealthyPi 5" (not the Move). SMP capability is independent ofBoardRegistry— do not add SMP devices to the registry; discover them by the SMP service UUID at connect time.
Theme: use lib/theme/app_spacing.dart (AppSpacing.xs/sm/md/lg/xl),
Theme.of(context).colorScheme, Card, FilledButton, etc. Match home_screen.dart
and scan_screen.dart visual patterns (cards, _CardHeader, hero, stat rows).
Create lib/smp/smp_ble_transport.dart implementing the ported abstract
SmpTransport, on top of universal_ble. It is a static/singleton API
keyed by device id — hold deviceId + resolved service/characteristic UUID strings
(not device objects). BleService and the Developer controller
(lib/controllers/developer_ble_controller.dart) already exercise every call below
against real hardware — copy their idioms. Capability checklist (universal_ble 2.x):
- Scan:
UniversalBle.startScan()+ listenUniversalBle.scanStream(Stream<BleDevice>).startScanhas no timeout — bound it yourself. Scan broadly (no service filter) — the SMP service is not advertised (gotcha §5.3). On web you MUST passwithServices(the SMP service) for post-connect access. - Connect:
UniversalBle.connect(deviceId, timeout: …). - Discover:
UniversalBle.discoverServices(deviceId)→List<BleService>; locate the SMP service8d53dc1d…+ characteristicda2e7828…. Compare UUIDs on the suffix (short 16-bit vs full 128-bit forms) — reuse ble_service's_sameUuid. - Notifications:
UniversalBle.subscribeNotifications(deviceId, service, char), then listenUniversalBle.characteristicValueStream(deviceId, char)and feed raw bytes intoSmpClientfor reassembly. - Write:
UniversalBle.write(deviceId, service, char, frame, withoutResponse: true)(SMP char is write-without-response). - Pairing (Move): handled by the OS on native connect (encrypted-char access);
UniversalBle.pair(deviceId)if you need to force it. See gotcha §5.4. - Already-connected devices (macOS bonded):
UniversalBle.getSystemDevices(withServices: […])(gotcha §5.2). Note: this returns only devices currently connected at the OS level, not merely bonded. - MTU:
UniversalBle.requestMtu(deviceId, 247)where supported; derivemaxWriteLength = mtu − 3(best-effort / OS-managed on some platforms — guard it).
-
The connection-state stream may replay its current value on subscribe. If you subscribe to
UniversalBle.connectionStream(deviceId)before connecting, you can get an immediate spuriousdisconnectedthat tears the connection down mid-flight. Subscribe only AFTER connect succeeds, or ignore the first replayed event. (BleService+ the Developer controller both do the former.) -
macOS scan does not return already-connected/bonded peripherals. CoreBluetooth omits connected devices from
scanForPeripherals. A device the OS has auto-connected (because it's bonded) will never appear in a scan — the scan returns zero results. You must ALSO query already-connected system devices:UniversalBle.getSystemDevices(withServices: […]). Query with the services the device exposes (SMP8d53dc1d…, plus common180D/180Fif relevant), match, and connect directly (no scan). Merge scan results + system devices in the picker. (getSystemDevicesis unsupported on Web — scan-only there.) -
Generic SMP discovery: most devices do not advertise the 128-bit SMP service UUID (advertising packet is only 31 bytes). So you cannot filter the scan by the SMP service. Scan broadly (no service filter), list all named devices (+ system devices), let the user pick, then verify the SMP service exists via
discoverServices()after connecting. If absent → show "not an SMP-enabled device" and disconnect. -
Bonded devices & pairing on native: the HealthyPi Move firmware forces
bt_conn_set_security(conn, BT_SECURITY_L2)on connect (see the firmware repoapp/src/ble_module.c), triggering passkey pairing. On native macOS/Android this is handled by the OS (system pairing dialog) — this is expected and fine, and is the whole reason native OpenView works where Web Bluetooth didn't. No app code needed beyond connecting. -
MTU settles after connect on macOS/iOS — don't cache it at connect. CoreBluetooth runs the ATT MTU exchange just after the connection is up, so
requestMturead insideconnect()returns the 23-byte default (→maxWriteLength20). This makes firmware DFU impossible (an SMP upload frame can't fit). Fix (implemented): pollrequestMtufor a few seconds post-connect (SmpController._settleMtu) and re-query right before an upload;ImgMgmtreadsmaxWriteLengthdynamically so the chunk size tracks it. Verified on a Move:max writerises from 20 → 244 B (MTU 247) once settled. If it stays 20, that's a firmware cap — raiseCONFIG_BT_L2CAP_TX_MTU(+ ACL buffer sizes) on the device; the app can't change it on macOS.
Create lib/controllers/smp_controller.dart — a ChangeNotifier that owns the
SMP subsystem. Model it on the prototype's lib/services/device_service.dart:
- Holds the active
SmpTransport+SmpClient, and the group facades (OsMgmt os, laterImgMgmt img,FsMgmt fs,HpiHs hs) — non-null only while connected. Future<List<SmpScanTarget>> scan()— broad BLE scan +systemDevices, returns pickable devices (name + id + rssi).Future<void> connect(deviceId)— connect, discover services, assert SMP service present (gate), wire notify/write intoSmpClient, exposeos. Also probe HPI_HS presence (attempths.hello(); success → reveal the Health Store screen; failure/rc→ hide it).disconnect(), connection-state stream, and a raw SMP console log (mirror every request/response — the prototype'sSmpClienttakes alogsink; wire it to a boundedList<ConsoleEntry>likeConnectionController.console).- Register in
main.dartgraph +app.dartMultiProvider +_CloseHandler.
Under lib/ui/screens/device_manager/:
device_manager_screen.dart— the top-level destination. When disconnected: a Scan button + device list (name, id, RSSI, a chip if the SMP service is confirmed after connect). When connected: show device identity- tabs/sections for Device Info and Console (later: Firmware, Files, Health Store — each shown only when its group is available).
- Device Info panel — port the prototype's
lib/ui/device_info_screen.dartlayout (SectionCards): Echo (smoke test), MCUmgr params, Task stats, Datetime get/set, Reset (with confirm). Wire each button tosvc.os!.…and show results inline / in the console. (The prototype has echo already wired as a reference:_echo(context)callingsvc.os!.echo('hello move').) - SMP Console — a raw request/response log view (reuse OpenView's
console_screen.dartstyling), fed bySmpController's log.
Phase-1 definition of done: open Device Manager → Scan → pick the device → Connect (native pairing completes if required) → Device Info → Send echo → the same string round-trips and appears in the console. That proves transport + framing + CBOR + fragment reassembly end-to-end against real hardware.
- Phase 0 —
universal_bleadoption + BLE spike. ✅ DONE.universal_ble(BSD-3) added topubspec.yaml; OpenView'sBleServiceruns on it behind the unchangedTransportServiceinterface (scan/connect/bytes/send); iOS deployment target bumped to 13.1; migration complete across all native platforms. BLE streaming (scan → connect → notify → write) is hardware-verified, and a Move connects + pairs + discovers services on macOS via the Developer tab. - Phase 1 — Device Info (OS group) + smoke test. ✅ DONE — echo hardware-verified.
Ported
lib/smp/(smp_messagew/ v1rc+ v2err,smp_clientreassembly,smp_transport),lib/mcumgr/os_mgmt.dart,lib/smp/smp_ble_transport.dart(universal_ble; gates on the SMP service),lib/controllers/smp_controller.dart, andlib/ui/screens/device_manager/device_manager_screen.dart(scan → connect → Device Info: echo/params/taskstat/datetime/reset + SMP console). Wired as the Device Manager nav destination.cboradded. Echo round-trips on a real Move (r:"…"returns correctly) → BLE transport + SMP framing + CBOR + fragment reassembly + seq matching all proven end-to-end. - Phase 2 — Generic polish. ✅ DONE. SMP v1
rcand v2errhandling (SmpMessage.rc/errorLabel, named codes); friendly "not an SMP device" gate (SmpBleTransportthrows → screen shows it); system-device merge (getSystemDevices); console export (SMP console →.logviafile_selector); robust auto-reconnect —SmpControllerremembers the last device and, on an unexpected drop (link lost / device reboot afteros reset/ DFU), tears down the dead transport and retries the same device up to 3× with backoff (a user disconnect sets_intentionalDisconnectand skips it). Device Manager shows a "Reconnecting…" view with Cancel. - Phase 3 — Firmware DFU (Image group). 🔨 BUILT — partially hardware-tested.
lib/mcumgr/img_mgmt.dart:list()(parses image slots),upload()(SHA-256, device-driven offset loop ported from Studio'simageUpload(), BLE-aware chunk sizing from the transport'smaxWriteLengthso each request fits one write),test()/confirm()/erase().SmpControllergains animgfacade; the Device Manager screen gains a Firmware tab (image list, file pick viafile_selector, upload progress bar, Test & Reset, Confirm running, Erase).cryptoadded; macOS user-selected-files entitlement already present. Analyze + macOS build pass. Hardware status:list()(read images) VERIFIED;upload()throughput VERIFIED (~12–13 kB/s at 216 B chunks once MTU settles to 244 B — see §5.5). Full DFU install (upload → test → reset → confirm boot) is a DEFERRED test — when run, likely blocked on the Move'sFailed to open flash area ID 1(secondary slot) firmware issue, not the app. - Phase 4 — File transfer (FS group). 🔨 BUILT — pending hardware test.
lib/mcumgr/fs_mgmt.dart:stat()(file size),download()+upload()(offset-loop chunking, BLE-aware). NOTE: stock Zephyr fs_mgmt has NO directory listing and NO delete — only file transfer + stat by absolute path — so this is a file-transfer panel (enter a/lfs/...path), not a browser.SmpControllergains anfsfacade; Device Manager gets a Files tab (stat / download-to-disk / upload-from-disk with progress). - Phase 5 — HPI_HS Health Store (ProtoCentral-only). 🔨 BUILT — pending hardware
test. Ported
lib/models/hs_type.dart+lib/models/hs_sample.dart(18-byte LE stride,<IqBBi) andlib/mcumgr/hpi_hs.dart(group0x1000: HELLO / TYPES / SYNC +syncAll/ SUMMARY / ACK / RECORDS list+get).SmpControllergains anhsfacade and probes HELLO on connect — the Health Store tab appears only when HELLO succeeds (non-HPI SMP devices reject the vendor group, so it stays hidden). The tab shows the HELLO handshake, fetches + displays the TYPES registry,syncAll→ resolves samples against the registry (real value + unit + quality) → CSV export, SUMMARY (typed dashboard cards viahs_summary.dart), ACK, and the RECORDS tier (Records→recordsList→ per-session download viaHpiHs.downloadRecordchunked-getloop + CRC-32 verify (utils/crc32.dart) →HsRecordSamplesdecode (bytes-per-sample inferred from the header) → fl_chart multi-channel viewer with Save-raw + ACK). Wire-detail caveats (all handled defensively): SUMMARY keys, RECORDS header/getfield names, record sample encoding — confirm against real Move responses. See the Move'sdocs/HPI_HS_API.md.
# from protocental_openview3/
flutter pub get
flutter analyze lib/
flutter run -d macos # native — bonding/pairing works here- Native macOS Bluetooth needs the entitlement + usage string. OpenView already
ships BLE, so
macos/Runner/*.entitlementsshould already havecom.apple.security.device.bluetoothandInfo.plistanNSBluetoothAlwaysUsageDescription— verify (grep them); add if missing. - Work on a feature branch (
git checkout -b feat/smp-device-manager); this is a release app. - If a bonded HealthyPi Move won't appear when scanning, that's gotcha §5.2 —
use
systemDevices. Confirm services viadiscoverServices()post-connect.
- Studio SMP client (RECOMMENDED base — proven OTA, multi-transport):
/Users/akw/Documents/GitHub/healthypi_studio/lib/services/smp_serial_client.dart(SMP body + CBOR + seq matching +imageUpload()OTA; serial + TCP transports — pure Dart, no BLE-plugin coupling). Alsolib/screens/firmware_update_screen.dartfor the OTA UI flow. - Prototype (cleanest per-group layer, BLE reassembly, HPI_HS models, protocol docs):
/Users/akw/Documents/GitHub/healthypi-move-webtool(ARCHITECTURE.md,docs/HPI_HS_API.md,lib/smp/,lib/mcumgr/,lib/models/). mcumgr_flutterplugin — reference only (native iOS/Android; not a dependency): https://pub.dev/packages/mcumgr_flutteruniversal_ble(the BLE layer — BSD-3, all platforms): https://pub.dev/packages/universal_ble · https://github.com/Navideck/universal_ble- Move firmware (why bonding is forced):
/Users/akw/Documents/GitHub/move-fw-next-workspc/healthypi-move-fw-next(app/src/ble_module.c→bt_conn_set_security(conn, BT_SECURITY_L2), passkey pairing;app/prj.conf→CONFIG_BT_BONDABLE=y, no SMP-char encryption perms). - OpenView seams (this repo):
lib/main.dart,lib/app.dart,lib/ui/app_routes.dart,lib/ui/adaptive_scaffold.dart,lib/transport/ble_service.dart(universal_bleAPI exemplar),lib/controllers/developer_ble_controller.dart(unfiltered BLE scan/connect/GATT),lib/transport/transport_service.dart,lib/theme/app_spacing.dart. - Web Bluetooth pairing limitation (context for why native): https://github.com/WebBluetoothCG/web-bluetooth/blob/main/implementation-status.md
Prepared from a prototype-integration investigation. Everything above was verified against the actual repos on this machine at handoff time; still re-check file paths/line numbers before editing, as they drift.