You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Four related changes to the plugin system, all iOS-focused.
Verified end to end: clean swift build + swift test (38 tests), xcodebuild archive (13 .wasm
plugins bundled), and the app run in the simulator — see the runtime note below.
1. No built-in decoders — plugins only
NativeParsers.swift (the native iBeacon and well-known-characteristic parsers) is deleted, and PluginManager no longer seeds the registry with them. All decoding now goes through WASM plugins.
Coverage is unchanged: the bundled ibeacon.wasm and the GATT group plugins already cover what the
native parsers did. Tests that used the native parsers as an oracle now assert against hand-computed
values instead.
2. Bundled plugins referenced from the bundle, not copied to Documents
Bundled plugins load directly from Bundle.module and are no longer installed into Documents/Plugins. Only user-imported plugins live on disk now. PluginDirectory loses the
bundled-install machinery (install record, first-launch/refresh/idempotence logic) and keeps just
import, list, and delete.
3. Imported plugins can be deleted
PluginState gains isRemovable (true only for imported plugins). Bundled plugins are read-only in
the app bundle — they can be disabled but not deleted; imported plugins can be deleted, removing them
from Documents/Plugins. The Plugins screen's swipe-to-delete now applies only to imported rows.
4. BLEPluginSDK is a product of the root package
BLEPluginSDK is now a .library product of the root bluetooth-explorer package, so an external
plugin author can depend on this repository and import BLEPluginSDK. It is not a dependency of the
app, so the app and the iOS archive never build it. Sources are shared with the standalone PluginSDK/BLEPluginSDK package the in-repo examples use.
Caveat, documented in PluginSDK/README.md: consuming the root package this way does not resolve
yet. The app's AndroidSwiftUI dependency uses a local sub-package, which SwiftPM forbids in a
branch-pinned dependency graph, so any transitive consumer of the root package fails to resolve
(the app builds only because it is the root). Until AndroidSwiftUI stops using a local sub-package,
authors build against the standalone PluginSDK/BLEPluginSDK package — which is what the examples do.
The product is in place and becomes usable the moment that upstream change lands.
Runtime verification
Removing the native parsers and referencing bundled plugins from the bundle were confirmed together
on device: the simulator's mock iBeacon advertisement still decodes (UUID / major / minor / RSSI) —
which can now only be the bundled ibeacon.wasm plugin running under the interpreter — while Documents/Plugins is empty, proving nothing was copied out of the bundle.
Tests
38 pass (down from 44: the native-parser suite and the two wasm-vs-native parity tests are gone; a
routing-stub test and imported-store import/delete tests are added).
Audited the app against the 16 members of CentralManager. Three were never exercised — they existed only as MockCentral protocol conformance, with no Store method and no UI:
API
Was
Now
maximumTransmissionUnit(for:)
never called
read on connect, shown in a Connection section
rssi(for:)
never called
read on connect, shown as Signal Strength
disconnectAll()
never called
Disconnect All button, shown only when something is connected
rssi(for:) was a genuine gap despite the list already showing an RSSI — that value comes from advertisement scan data, not from reading the connected peripheral. All 16 members are now reachable from Store.
This surfaced a real crash
Wiring rssi(for:) immediately crashed the app. MockCentral did:
return .init(rawValue:127)!
RSSI.init?(rawValue:) only accepts -127...20 dBm, so that force-unwrap was always nil — a guaranteed trap that survived only because nothing ever called the method. Fixed to return an in-range value and to throw .disconnected when not connected, matching the sibling MTU method.
Also fixed a race: readConnectionInfo() was gated on isConnected, which is refreshed asynchronously and so still reads false right after connect() returns — leaving both values blank on first load. The central already throws .disconnected, which is caught.
Tests
New BluetoothExplorerModelTests target, 4 tests covering MTU, RSSI (including the range regression), disconnect-required behavior, and disconnectAll. Suite is now 42 passing.
Verified on device
Connected to a peripheral in the simulator: MTU 23 and Signal Strength -60 dBm populate on first load, Refresh no longer crashes, and Disconnect All appears while connected and disappears after tapping it. iOS archive succeeds.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four related changes to the plugin system, all iOS-focused.
Verified end to end: clean
swift build+swift test(38 tests),xcodebuild archive(13.wasmplugins bundled), and the app run in the simulator — see the runtime note below.
1. No built-in decoders — plugins only
NativeParsers.swift(the native iBeacon and well-known-characteristic parsers) is deleted, andPluginManagerno longer seeds the registry with them. All decoding now goes through WASM plugins.Coverage is unchanged: the bundled
ibeacon.wasmand the GATT group plugins already cover what thenative parsers did. Tests that used the native parsers as an oracle now assert against hand-computed
values instead.
2. Bundled plugins referenced from the bundle, not copied to Documents
Bundled plugins load directly from
Bundle.moduleand are no longer installed intoDocuments/Plugins. Only user-imported plugins live on disk now.PluginDirectoryloses thebundled-install machinery (install record, first-launch/refresh/idempotence logic) and keeps just
import, list, and delete.
3. Imported plugins can be deleted
PluginStategainsisRemovable(true only for imported plugins). Bundled plugins are read-only inthe app bundle — they can be disabled but not deleted; imported plugins can be deleted, removing them
from
Documents/Plugins. The Plugins screen's swipe-to-delete now applies only to imported rows.4. BLEPluginSDK is a product of the root package
BLEPluginSDKis now a.libraryproduct of the rootbluetooth-explorerpackage, so an externalplugin author can depend on this repository and
import BLEPluginSDK. It is not a dependency of theapp, so the app and the iOS archive never build it. Sources are shared with the standalone
PluginSDK/BLEPluginSDKpackage the in-repo examples use.Caveat, documented in
PluginSDK/README.md: consuming the root package this way does not resolveyet. The app's
AndroidSwiftUIdependency uses a local sub-package, which SwiftPM forbids in abranch-pinned dependency graph, so any transitive consumer of the root package fails to resolve
(the app builds only because it is the root). Until AndroidSwiftUI stops using a local sub-package,
authors build against the standalone
PluginSDK/BLEPluginSDKpackage — which is what the examples do.The product is in place and becomes usable the moment that upstream change lands.
Runtime verification
Removing the native parsers and referencing bundled plugins from the bundle were confirmed together
on device: the simulator's mock iBeacon advertisement still decodes (UUID / major / minor / RSSI) —
which can now only be the bundled
ibeacon.wasmplugin running under the interpreter — whileDocuments/Pluginsis empty, proving nothing was copied out of the bundle.Tests
38 pass (down from 44: the native-parser suite and the two wasm-vs-native parity tests are gone; a
routing-stub test and imported-store import/delete tests are added).