Skip to content

Conversation

@zamderax
Copy link
Contributor

@zamderax zamderax commented Dec 27, 2025

Summary

  • add Bluetooth CLI commands for connect/list/disconnect/forget with interactive defaults
  • add agent support for pair/trust/forget plus updated Bluetooth RPCs
  • update Noora dependency and improve dev-update agent script

IMPORTANT It requires: wendylabsinc/meta-wendyos-jetson#20 SPECIFICALLY NEEDS HOST TO HAVE BLUEZ running!

Testing

Whenever we release a wendy os install --nightly where bluez is available you can test this agent and cli

@zamderax zamderax requested a review from a team as a code owner December 27, 2025 06:21
@codecov
Copy link

codecov bot commented Dec 27, 2025

Codecov Report

❌ Patch coverage is 5.86531% with 1300 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
Sources/Wendy/cli/commands/BluetoothCommand.swift 2.63% 592 Missing ⚠️
...ources/WendyAgent/Services/WendyAgentService.swift 0.00% 310 Missing ⚠️
Sources/WendyAgent/Services/BluetoothManager.swift 17.85% 299 Missing ⚠️
Sources/Wendy/cli/commands/WiFiCommand.swift 0.00% 50 Missing ⚠️
Sources/Wendy/CLI+Support.swift 0.00% 49 Missing ⚠️

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #234      +/-   ##
==========================================
- Coverage   15.41%   14.71%   -0.70%     
==========================================
  Files         104      106       +2     
  Lines       17120    18493    +1373     
==========================================
+ Hits         2639     2722      +83     
- Misses      14481    15771    +1290     
Flag Coverage Δ
Linux 12.18% <5.86%> (-0.56%) ⬇️
macOS 15.65% <5.89%> (-0.77%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
Sources/Wendy/cli/commands/DeviceCommand.swift 0.00% <ø> (ø)
Sources/Wendy/CLI+Support.swift 0.00% <0.00%> (ø)
Sources/Wendy/cli/commands/WiFiCommand.swift 0.00% <0.00%> (ø)
Sources/WendyAgent/Services/BluetoothManager.swift 17.85% <17.85%> (ø)
...ources/WendyAgent/Services/WendyAgentService.swift 0.00% <0.00%> (ø)
Sources/Wendy/cli/commands/BluetoothCommand.swift 2.63% <2.63%> (ø)

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +30 to +47

// List available Bluetooth devices (uses BlueZ)
rpc ListBluetoothDevices(ListBluetoothDevicesRequest) returns (ListBluetoothDevicesResponse);

// Start Bluetooth discovery scan
rpc StartBluetoothScan(StartBluetoothScanRequest) returns (StartBluetoothScanResponse);

// Stop Bluetooth discovery scan
rpc StopBluetoothScan(StopBluetoothScanRequest) returns (StopBluetoothScanResponse);

// Connect to a Bluetooth device
rpc ConnectBluetoothDevice(ConnectBluetoothDeviceRequest) returns (ConnectBluetoothDeviceResponse);

// Disconnect from a Bluetooth device
rpc DisconnectBluetoothDevice(DisconnectBluetoothDeviceRequest) returns (DisconnectBluetoothDeviceResponse);

// Forget (remove) a Bluetooth device
rpc ForgetBluetoothDevice(ForgetBluetoothDeviceRequest) returns (ForgetBluetoothDeviceResponse);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to bundle this in a new service proto

bool connected = 5;

// Device type/class (e.g., "audio-headset", "keyboard", "mouse")
string device_type = 6;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a known enum we can reflect here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will make a task for this in Linear to track it, will address in a future PR.

let effectiveTimeoutSeconds =
timeoutSeconds == 0 ? Self.defaultScanTimeoutSeconds : timeoutSeconds

_ = Task.detached { [logger, self] in
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to be a (detached) background task. Can we just call this inline?

Comment on lines 79 to 80
if timeoutSeconds > 0 {
Task { [logger] in
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be worth having a func run() async throws on BluetoothManager, so we can signal the run-loop with start/stop commands in a structured way. If two calls happen simultaneously, both asking to scan with timeout, they'll conflict.

signalSource.cancel()
signal(SIGINT, SIG_DFL)
Task {
_ = try? await agent.stopBluetoothScan(.init())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work, the signal will happen immediately and not wait for the Task. You wan tto queue the signal after running this stopBluetoothScan.

}

guard let targetDevice else {
return
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a warning?

@zamderax
Copy link
Contributor Author

zamderax commented Dec 28, 2025

@Joannis I added ResponseStatus (success/info/warning/error) and corresponding logs so WiFi/Bluetooth operations can return typed status messages to the CLI.

- Fix race condition in BluetoothCommand.swift where defer block used
  fire-and-forget Task for stopBluetoothScan, causing the function to
  return before scan was actually stopped
- Convert BluetoothManager from struct to actor for proper concurrency
- Add run() method for structured lifecycle management
- Track scanTask to prevent concurrent scan conflicts
- Handle CancellationError gracefully when scans are stopped early
@zamderax zamderax force-pushed the wdy-617-bluetooth-connect-list-disconnect-forget branch from 732d630 to 21c4d5e Compare December 29, 2025 07:11
Comment on lines 54 to 55
INFO = 2;
WARNING = 3;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do INFO and WARNING mean? Are they successful status codes?

Comment on lines 28 to 30
func responseStatusLevelDescription(
_ level: Wendy_Agent_Services_V1_ResponseStatus.Level
) -> String {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func responseStatusLevelDescription(
_ level: Wendy_Agent_Services_V1_ResponseStatus.Level
) -> String {
extension Wendy_Agent_Services_V1_ResponseStatus.Level: CustomStringConvertible {
public var description: String {

@zamderax
Copy link
Contributor Author

Addressed Joannis notes:\n- Documented ResponseStatus levels (INFO/WARNING are successful with extra context).\n- Added CustomStringConvertible for ResponseStatus.Level and updated CLI JSON/text output to use it.\n- Bluetooth CLI SIGINT: stop scan inline and re-raise SIGINT after cleanup; removed detached stop-scan tasks.\n- Added a warning when no device is selected (disconnect/forget).\n\nBluetoothManager already has a run() loop + scanGeneration guard to prevent overlapping scan requests; can wire it into ServiceGroup if you want.

Comment on lines +46 to +54
// The run loop keeps the actor alive and allows for structured concurrency.
// When cancelled, any active scan task will be cancelled as well.
defer {
scanTask?.cancel()
scanTask = nil
}

// Wait indefinitely until cancelled
try await Task.sleep(for: .seconds(Int64.max))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still uses unstructured tasks with background logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, if I don't use it, the wendy-agent hangs. Let me investigate further.

read -p "Enter hostname [wendyos-merry-aurora.local]: " HOSTNAME
HOSTNAME=${HOSTNAME:-wendyos-merry-aurora.local}
USER=edgeos
HOSTNAME=wendyos-spirited-rose.local
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

host and user/password are hardcoded. I'd remove at least the user/password and let them be passed in interactively

Joannis pushed a commit that referenced this pull request Jan 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants