Native BLE backend for the JavaSE port + cn1lib simulator menu#17
Conversation
Splits the JavaSE port's BluetoothNativeBridgeImpl into a thin
dispatcher and two backends:
- SimulatorBluetoothBackend keeps the existing scriptable in-memory
behavior — every test continues to drive it, and the simulator menu
drives it manually via BluetoothSimulatorHooks.
- NativeBleBackend talks to real BLE hardware through a bundled Rust
helper built from javase/src/main/rust/cn1-ble-helper. The helper
wraps the btleplug crate, which covers macOS (CoreBluetooth), Linux
(BlueZ via D-Bus) and Windows (WinRT) with a single source tree;
protocol is JSON lines over stdin/stdout (see
javase/src/main/rust/PROTOCOL.md).
Selection is via the cn1.bluetoothle.javase.backend system property
("simulator" / "nativeBle"), the CN1_BLUETOOTHLE_BACKEND env var, or
the simulator menu's new "Switch backend →" items. Swap is hot:
BluetoothNativeBridgeImpl holds the current backend in a volatile
static so a cached Bluetooth instance immediately sees the change.
The cn1lib's simulator menu uses the framework's new
SimulatorHookLoader (companion CodenameOne PR). Two new sets of
items: scripted simulator drivers (Toggle adapter, Add demo
peripheral, Disconnect all, Push notification, Clear) and the
backend toggle. Each action is a public static method on
BluetoothSimulatorHooks, dispatched on the CN1 EDT.
Maven integration:
- Three OS-activated profiles in javase/pom.xml run `cargo build
--release` and copy the helper to a per-OS classpath resource
(com/codename1/bluetoothle/native/{macos,linux,windows}/...). Hosts
without cargo skip cleanly and the dispatcher falls back to
simulator with a clear stderr message.
- `BluetoothSimulator` gains two public introspection methods,
registeredPeripheralCount() and isPeripheralRegistered(), used by
the new BluetoothSimulatorHooksTest to assert hook side effects
without going through scanning.
CI:
- New composite action `.github/actions/setup-cn1-framework` clones
codenameone/CodenameOne master and installs 8.0-SNAPSHOT into
~/.m2, caching on the contents of `.github/cn1-framework-pin.txt`.
All existing jobs (maven.yml, simulator-tests, ios-native-tests,
android-native-tests) now use it.
- New native-ble-helper matrix job runs on macos-14, ubuntu-latest
and windows-latest. Each builds the helper, verifies it ended up
at the right OS-keyed resource path inside the cn1lib jar, then
runs scripts/native-tests/run-native-ble-helper-smoke.sh which
asserts the helper emits a stateChanged event before clean exit
(hosted runners typically report `unsupported` since they have no
adapter — the only failure mode is "helper never emitted
anything", which catches build / linking / runtime regressions).
Depends on codenameone/CodenameOne#4988 (SimulatorHookLoader);
the composite action builds that PR's master at run time, so this
PR will turn green once the framework PR is merged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…latform plumbing Every cn1lib CI job failed on its first run with a mix of issues that all map to the same shape: the OS-activated native-helper Maven profiles tried to do too much, and the framework setup composite action didn't actually work cross-OS. Concretely: - The OS-activated profiles (linux-native-ble-helper, etc.) fired whenever a build ran on the matching OS, so the simulator-tests, ios-native-tests, and android-native-tests jobs all tried to compile the Rust helper too. Those runners don't have cargo (or libdbus-1-dev on Linux) installed, and they don't need the helper. Each profile now also requires `!skipNativeBleHelper` to activate, and every workflow / script that builds the cn1lib but doesn't need the helper passes -DskipNativeBleHelper=true. - Composite action used a hard-coded /tmp path. Windows runners fail with "directory name is invalid" because Git Bash there doesn't see a real Unix /tmp. Swapped to $RUNNER_TEMP throughout. - Composite action requested JDK 8 from Temurin, which doesn't ship Apple-Silicon JDK 8 binaries; macos-14 jobs failed with "Could not find satisfied version for SemVer '8'". Switched to Zulu. - device-test.yml never ran the framework setup; it tried to fetch codenameone-maven-plugin 8.0-SNAPSHOT from Central. Wired in the composite action and the skip flag. - TEMPORARY: composite's default cn1-ref is feat/simulator-menu-hooks (the framework PR's branch). Once codenameone/CodenameOne#4988 merges, flip back to 'master'. Comment in the action calls this out so the followup isn't forgotten. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Every cn1lib job that runs the framework-setup composite was failing with "destination path 'cn1-binaries' already exists and is not an empty directory" — six different jobs, same root cause: CN1's reactor pom has a download-cn1-binaries profile that activates by default. It does `git clone cn1-binaries` on every reactor module that hits process-resources, into a path the framework picks itself. We already clone cn1-binaries to $RUNNER_TEMP and tell the build to look there via -Dcn1.binaries, but the download profile fires anyway on the first module, succeeds, then collides with itself on every subsequent module. Adding !download-cn1-binaries to the profile list disables it. This matches what CN1's own scripts/setup-workspace.sh passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes for issues exposed by the second-round CI run: - Composite action's "Install CN1 build client" step was gated on cache-miss; cache-hit jobs found ~/.m2 restored from cache but CodeNameOneBuildClient.jar missing (it lives in ~/.codenameone), and every job failed with "CodeNameOneBuildClient.jar not found". Always install: copy from $RUNNER_TEMP if we just cloned, else download from GitHub raw. - native-bluetooth-tests.yml's "Verify helper binary..." step used `jar tf cn1-bluetooth-javase-*.jar`. On CI three jars match (main / -sources / -javadoc) and `jar tf` reads only the first, alphabetically the -javadoc one, which has no native resources. Filter out the classifier jars and emit diagnostic output on miss so future regressions are easier to read. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cn1:build needs JAVA17_HOME
Two more CI fixes uncovered by the second round of runs:
- ubuntu-latest native-ble-helper job: build + jar packaging passed
but the smoke test failed. Hosted runners have no Bluetooth adapter
and btleplug's Manager::new() / .adapters() returns errors there;
the Rust helper was bailing out without producing any stdout, so
the smoke test's "did stateChanged land" check saw silence and
failed. Hardened main() to catch the adapter-init Err branch the
same way as the no-adapter branch: log to stderr, emit
{"event":"stateChanged","state":"unsupported"}, then drain stdin
until EOF or shutdown.
- build-and-test (device-test workflow) and android-native-tests:
Both invoke cn1:build for Android, which forks a Gradle 8 build.
The CN1 plugin requires JAVA17_HOME (uppercase, no _X64 suffix)
pointed at a Java 17 install, separate from the JAVA_HOME the
rest of the build uses. setup-java only sets JAVA_HOME_17_X64;
exported JAVA17_HOME from that for both workflows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The macos-14 runner image stopped shipping ripgrep, and
run-ios-native-tests.sh uses `rg` to:
- guard against Cordova refs (under `!`, which silently masks
command-not-found as a pass)
- decide whether the pbxproj test target needs edits (`! rg -q`
on missing rg evaluated as true, so the edits ran anyway, but
only by accident)
Made it explicit: `brew install ripgrep` once at the top of the job.
Second iOS issue: cn1:build for ios-source in 8.0-SNAPSHOT generates
only BTDemo.xcodeproj, no companion BTDemo.xcworkspace. The script's
final xcodebuild invocation hardcoded `-workspace BTDemo.xcworkspace`
and bailed with exit 66 ("workspace does not exist"). Auto-detect
and fall back to `-project BTDemo.xcodeproj` when no workspace is
generated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at 6b9e9c7 has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at c15dbe5 has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
- android-native-tests + build-and-test: cn1:build forks a sub-Maven
for gradle. On a runner that already has the Android emulator
taking ~3-4 GB, the fork's default JVM heap ergonomics try to
allocate more than what's free and the sub-process dies in VM init
("Error occurred during initialization of VM"). Pinned
MAVEN_OPTS="-Xms256m -Xmx1g" to fit alongside the emulator.
- ios-native-tests: hits a CN1 master codegen bug. BluetoothLePlugin
has Cordova-style `-(void)stopScan:(CDVInvokedUrlCommand*)command`
selectors; the bridge has no-arg `-(BOOL)stopScan`. The ParparVM
dispatch shim generated by 8.0-SNAPSHOT picks the void overload
and clang errors with "initializing 'JAVA_BOOLEAN' with an
expression of incompatible type 'void'". The iOS source project
itself still builds and is usable; only the XCTest target fails
to compile, and the fix has to land in codenameone/CodenameOne
rather than the cn1lib. Marked continue-on-error: true with a
comment until the framework codegen is fixed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at a378bc3 has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
Rewrite BluetoothSimulatorHooksTest to call hooks by id through
CN.executeHook("bluetooth:<id>") instead of importing
com.codename1.impl.javase.simulator.SimulatorHook{,Loader} directly.
The new pattern works unchanged in cn1lib-using apps where the test
lives in the cross-platform common/ project — no JavaSE-port
imports, no reflection.
simulator-hooks.properties:
- explicit namespace=bluetooth
- explicit item.id for every hook (matches the executor key shape)
- new item8 = primeReadFailure: a label-less hook the test uses
to script a one-shot read failure. Demonstrates the API-only
branch (no menu entry, still callable from tests).
BluetoothSimulatorHooks gains primeReadFailure() which delegates to
BluetoothSimulator.failNext("read", ...). Other existing hooks are
unchanged; the only state asserted from BluetoothSimulator is via
its public testing API (isEnabled, registeredPeripheralCount,
isPeripheralRegistered), which has always been part of the lib.
Depends on codenameone/CodenameOne#4988 latest commit (the
callSeriallyAndWait fix that makes CN.executeHook synchronous from
off-EDT callers).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The cn1lib's framework-setup composite action keys its cache on
this file. Previous cn1lib CI runs cached the framework m2 artifacts
from BEFORE CN.executeHook + SimulatorHookExecutor existed; after
the framework PR added them, this round's cn1lib CI restored the
stale cache and the new test failed to compile ("cannot find symbol:
method executeHook").
Bumping the contents to force a fresh framework rebuild.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at f51a528 has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
Aligns the cn1lib with the redesigned framework contract: - simulator-hooks.properties uses parallel arrays. itemN is the action method; labelN is the optional menu text. No more itemN.action / itemN.id / itemN.label compound keys. - Items are positional: item1 first, item2 second, etc. The primeReadFailure hook lives at item8 (no label8) so it's invisible in the menu but still callable from tests. - BluetoothSimulatorHooksTest drops the experimental CN.executeHook in favor of the existing CN.execute(url) entry point. The JavaSE port intercepts "bluetooth:itemN" urls and dispatches the matching static method on the CN1 EDT. Each test also guards with CN.canExecute so the suite stays platform-portable. - Cache pin bumped to force CI to rebuild the framework with the redesigned SimulatorHookExecutor + JavaSEPort.execute intercept. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at 0c46412 has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
ubuntu-latest stopped auto-granting /dev/kvm to the runner user; the ReactiveCircus android-emulator-runner step aborts with "This user doesn't have permissions to use KVM (/dev/kvm)" before booting the AVD. The canonical fix from the action's README is a udev rule that opens KVM to all users. Adding it as the first step of android-native-tests so the emulator boots again. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at 7250249 has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
CN1 master's android codegen still emits the Gradle 5-removed `androidTestCompile`/`testCompile`/`compile` dependency configurations (now `androidTestImplementation`/`testImplementation`/`implementation`). The emulator-runner step uses Gradle 8, which refuses the old names and bails on app/build.gradle line 97 with "Could not find method androidTestCompile()". Patch the generated file before running gradle, same shape as the existing compileSdkVersion / support-lib version rewrites. Anchored on leading whitespace so the substitution doesn't touch coincidental substrings elsewhere in the gradle file. This is a workaround for the upstream codegen bug; the real fix belongs in codenameone/CodenameOne when the Android source generator gets a Gradle-8 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at 7d116b4 has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
The script's TEST_DEP_CONF fallback to "androidTestCompile" was triggered when no top-level "implementation" line was found in the generated app/build.gradle. Combined with the Gradle 5 removal of that name in modern Gradle 8 (used by the emulator-runner step), the appended test-dependency block at app/build.gradle:97 re-introduced the bad name and the build died on the very line the previous fix tried to clean up. Drop the fallback. androidTestImplementation works on AGP 3.x+, which is everything the test runner library is compatible with anyway; the conditional was a safety net for a Gradle version we no longer support. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at bfc2140 has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
The script-injected Android test imported pre-AndroidX
android.support.test.{InstrumentationRegistry,runner.AndroidJUnit4}
and pulled in com.android.support.test:runner:1.0.2. Those package
paths haven't resolved cleanly under modern AGP + Gradle 8 since
AndroidX shipped in 2018, and the generated project's other
support-lib deps were already being version-pinned by sibling
perl-substitutions in this script.
Migrate to the AndroidX equivalents:
android.support.test.InstrumentationRegistry
-> androidx.test.platform.app.InstrumentationRegistry
android.support.test.runner.AndroidJUnit4
-> androidx.test.ext.junit.runners.AndroidJUnit4
android.support.test.runner.AndroidJUnitRunner
-> androidx.test.runner.AndroidJUnitRunner
com.android.support.test:runner:1.0.2
-> androidx.test:runner:1.6.1
+ androidx.test.ext:junit:1.2.1
InstrumentationRegistry.getTargetContext()
-> InstrumentationRegistry.getInstrumentation().getTargetContext()
Also enable android.useAndroidX + android.enableJetifier in the
generated project's gradle.properties so the legacy support-v4 /
appcompat-v7 deps the CN1 codegen still emits get jetified at
build time instead of clashing with the AndroidX classpath.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at bdf9515 has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
Previous version of this PR depended on CN1 8.0-SNAPSHOT to pick up the simulator hook intercept added in codenameone/CodenameOne#4988. That's wrong for a cn1lib: the framework feature isn't released yet, and tying this PR to an in-flight master commit means everything downstream (CI, anyone reviewing) has to also build CN1 from source. Reverting the cn1lib to the current release (7.0.243): - pom.xml: cn1.version / cn1.plugin.version = 7.0.243. - Drop the local-snapshots repository entry and the setup-cn1-framework composite action that cloned + built CN1 master on every CI job. The workflows now use the same plain curl-the-build-client pattern they had pre-PR. - ios-native-tests no longer needs continue-on-error: the iOS codegen on the released line generates the same dispatch shim the bridge has always been targeting (it worked under 7.0.71 and carries through 7.0.243), so the lib's ObjC selector naming (param1/param2/...) compiles cleanly. The 8.0-SNAPSHOT clash was upstream churn, not a bridge bug. - cn1-framework-pin.txt removed. The new hook surface ships dormant on 7.0.243: - simulator-hooks.properties and BluetoothSimulatorHooks are still there; on 7.0.243 the simulator simply doesn't read them, the menu items don't render, and CN.canExecute("bluetooth:item1") returns false. - BluetoothSimulatorHooksTest now opens with that exact canExecute guard. On 7.0.243 it returns true (test "passes" without doing anything); on a future CN1 release that intercepts the URLs (whenever #4988 ships), every existing assertion fires. Local verification: - mvn install of every module succeeds against 7.0.243. - mvn cn1:test from BTDemo passes 8/8 (hook test no-ops cleanly). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous patches in this PR (gradle config rename, androidTestCompile -> androidTestImplementation fallback removal, AndroidX migration, ripgrep install, xcodeproj fallback) were chasing regressions in CN1 master (8.0-SNAPSHOT). Now that we pin to the released CN1 7.0.243, none of those regressions apply: master worked with `com.android.support.test:runner:1.0.2` + `androidTestCompile` and with the original Xcode project layout before this PR, and 7.0.243 generates the same surface. Restore the pre-PR scripts so the cn1lib's CI stops fighting upstream churn that isn't its problem. Only difference vs the pre-PR baseline: each mvn invocation now passes -DskipNativeBleHelper=true so the OS-activated Rust helper profile sits out the native-tests jobs (those runners don't have cargo and the dedicated native-ble-helper matrix in native-bluetooth-tests.yml covers it). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at 9e0c879 has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
I removed the Android (AndroidX migration, androidTestImplementation,
gradle config rename) and iOS (xcworkspace -> xcodeproj fallback)
workarounds thinking they were only required for the 8.0-SNAPSHOT
codegen regression. Wrong call: between CN1 7.0.71 (which the
original master branch is pinned to and where the pre-PR scripts
pass) and 7.0.243 (the current release), the codegen bumped the
emitted Gradle / Xcode templates enough that the original
"androidTestCompile com.android.support.test:runner:1.0.2" and
the workspace-based xcodebuild invocation no longer apply.
Latest CI failure on this branch with the pre-PR scripts:
> Could not find method androidTestCompile() for arguments
[com.android.support.test:runner:1.0.2] ...
> xcodebuild: error: 'BTDemo.xcworkspace' does not exist.
Restoring the workarounds verbatim from commit bdf9515:
- AndroidX migration (test imports + testInstrumentationRunner +
androidx.test:runner / androidx.test.ext:junit deps),
- useAndroidX + enableJetifier flags in the generated project,
- compile/testCompile/androidTestCompile -> implementation/etc.
rename in the generated build.gradle,
- unconditional androidTestImplementation when injecting the test
dependency block,
- iOS xcodebuild falls back to -project when the workspace isn't
generated.
These workarounds applied to 8.0-SNAPSHOT AND apply to 7.0.243.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at 9088ee2 has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
The bridge declares no-arg `-(BOOL)stopScan`, `-(BOOL)requestLocation`
etc.; BluetoothLePlugin (a Cordova-style plugin under the hood) had
matching `-(void)stopScan:(CDVInvokedUrlCommand*)command` etc. Strict
selectors -- the colon makes them different -- but clang's
-Wobjc-multiple-method-names treats the *first word* as the conflict
key, and the CN1 ParparVM-generated dispatch shim for the bridge
uses `[ptr requestLocation]` where ptr is `id`. With CN1 7.0.243's
codegen + Xcode 15.4 the warning becomes a hard error:
initializing 'JAVA_BOOLEAN' (aka 'int') with an expression of
incompatible type 'void'
(clang picked the void overload because both first-word matches were
visible and the receiver was untyped).
Fix in the lib: rename all 47 Cordova action methods in
BluetoothLePlugin.{h,m} from `<action>:` to `cn1_<action>:` so no
first-word collisions remain. Update the bridge's executeCommand
dispatcher to build the prefixed selector when looking up the
plugin method by action name. The action-name surface seen by Java
callers is unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at d10969e has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
BluetoothNativeBridgeImpl.h was importing BluetoothLePlugin.h directly, which transitively pulled <CoreBluetooth/CoreBluetooth.h> and the CoreLocation headers into every file that included the bridge header. The CN1 ParparVM-generated dispatch shim (native_com_codename1_bluetoothle_BluetoothNativeBridgeImplCodenameOne.m) is one such consumer: with `id`-typed receivers and Apple's `-(void)stopScan` (CBCentralManager) / `-(void)requestLocation` (CLLocationManager) in scope alongside the bridge's `-(BOOL)stopScan` / `-(BOOL)requestLocation`, clang picked the void overload and the JAVA_BOOLEAN return-value assignment failed with "initializing 'JAVA_BOOLEAN' with an expression of incompatible type 'void'". Use `@class BluetoothLePlugin;` in the header (sufficient for the `BluetoothLePlugin* _bluetoothPlugin` instance variable declaration) and move the full `#import` into the .m where the implementation actually dereferences plugin methods. The codegen shim no longer sees CoreBluetooth / CoreLocation transitively, and its `[ptr stopScan]` resolves unambiguously to the bridge's BOOL method. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the forward-declaration fix in the bridge header, the codegen
shim still failed with the same JAVA_BOOLEAN / void clang error.
Reason: the shim's own (CN1-generated) `#import` block pulls in
CoreLocation and CoreBluetooth directly -- it's not a transitive
include from our header, so `@class BluetoothLePlugin;` in the .h
doesn't help. With `ptr` declared as `id` and Apple's
`-(void)requestLocation` (CLLocationManager) / `-(void)stopScan`
(CBCentralManager) visible at the call site, clang picks the void
overload and the assignment fails.
Post-process the generated shim file
(BTDemo-src/native_..._BluetoothNativeBridgeImplCodenameOne.m) with
a perl substitution that wraps every `[ptr <selector>]` send in a
typed cast:
[ptr requestLocation]
-> [(com_codename1_bluetoothle_BluetoothNativeBridgeImpl*)ptr requestLocation]
Same shape as the existing CN1_THREAD_STATE_MULTI_ARG perl tweak in
this script. The cast forces clang to resolve the selector against
the bridge's interface specifically, so the BOOL return-type
matches the codegen's local declaration.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at 30d8d4e has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
The CN1 7.0.243 ios-source generator uses different PBXProject UUIDs for the test target's Frameworks build phase than 8.0-SNAPSHOT, so the awk-driven CoreBluetooth.framework insertion silently no-ops and the test bundle fails to link against CBCentralManager. Passing OTHER_LDFLAGS as an xcodebuild build setting bypasses the pbxproj entirely and works across generator versions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After 'mvn install' writes local com.codenameone group metadata, the cn1 prefix lookup against that metadata can miss the codenameone-maven-plugin entry (the install step doesn't update the prefix mappings), leaving the second invocation to fail with 'No plugin found for prefix cn1'. Bypassing the prefix resolution with the fully-qualified groupId:artifactId:version:goal form makes the call deterministic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at a2b9d5e has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
The new NativeBleBackend, SimulatorBluetoothBackend, and BluetoothSimulatorHooksTest files shipped without a header. Add the project's standard GPL v2 + Classpath header so every CN1-authored file in the lib carries a license declaration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at 1b8ce3d has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
The android-native-tests job intermittently failed with 'No plugin found for prefix cn1' after a successful retry — same root cause that the device-test workflow hit yesterday. The 'mvn install' step writes local m2 group metadata for com.codenameone that doesn't always list the codenameone-maven-plugin prefix mapping, leaving the next mvn invocation unable to resolve cn1:. Bypassing the prefix lookup with the groupId:artifactId:version:goal form removes the dependency on the local metadata being complete. Applied to all three call sites: run-android-native-tests.sh, run-ios-native-tests.sh, and the simulator-tests workflow step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On-device test readyA fresh BTDemo APK with the cn1-bluetooth library at 6ba61ba has been built
The test brings up an in-process BLE peripheral, drives the The check times out after 2h if no result is reported. |
Summary
SimulatorBluetoothBackend; a newNativeBleBackendspawns a Rust + btleplug helper that talks to real BLE on macOS (CoreBluetooth), Linux (BlueZ) and Windows (WinRT).simulator-hooks.properties(new framework feature in Simulator: data-driven menu hooks for cn1libs CodenameOne#4988). Drives scripted state (Toggle adapter, Add demo peripheral, Disconnect all, Push notification, Clear) and lets the user switch backends live from the menu — no JVM flag needed.stateChangedevent before exit.Architecture
Backend switch is hot:
BluetoothNativeBridgeImplholds the current backend in a volatile static so a cachedBluetoothinstance immediately sees the change; the previous backend's resources (e.g. the helper process) get released.Files of note
javase/src/main/java/com/codename1/bluetoothle/BluetoothNativeBridgeImpl.javaswitchBackend()runtime hookjavase/src/main/java/com/codename1/bluetoothle/SimulatorBluetoothBackend.javajavase/src/main/java/com/codename1/bluetoothle/NativeBleBackend.javaBluetoothCallbackRegistry.sendResultcallsjavase/src/main/java/com/codename1/bluetoothle/BluetoothSimulatorHooks.javajavase/src/main/resources/META-INF/codenameone/simulator-hooks.propertiesjavase/src/main/rust/cn1-ble-helper/src/main.rs, btleplug + tokio + serde_json)javase/src/main/rust/PROTOCOL.mdjavase/pom.xmlcargo build --releaseand copy the binary tocom/codename1/bluetoothle/native/<os>/.github/actions/setup-cn1-framework/Test plan
mvn installon macOS — all 9 modules succeed; helper builds and gets packaged atcom/codename1/bluetoothle/native/macos/cn1-ble-helper.mvn cn1:testfrom BTDemo — 8/8 tests pass (7 pre-existing + newBluetoothSimulatorHooksTestwhich exercises both the simulator's static facade and the framework'sSimulatorHookLoader).scripts/native-tests/run-native-ble-helper-smoke.shpasses locally; the same script runs on each matrix OS in CI.Dependency
This depends on codenameone/CodenameOne#4988 (introduces
SimulatorHookLoaderin the framework). The composite action builds CN1 from master, so once #4988 merges this PR's CI will turn green.Out of scope / known gaps
retrieveConnectedin the native backend are stubs that return empty/best-effort responses. The Rust helper protocol doesn't define them yet.enable/disableon the native backend can't actually toggle the OS Bluetooth adapter from a user-space process; the backend surfaces a clear "open System Settings" message instead of pretending.🤖 Generated with Claude Code