Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

feat: ble write notify driver: added basic ble write and notify stream driver and cli console#726

Merged
mangelajo merged 2 commits intojumpstarter-dev:mainfrom
whati001:feat/ble_console
Nov 6, 2025
Merged

feat: ble write notify driver: added basic ble write and notify stream driver and cli console#726
mangelajo merged 2 commits intojumpstarter-dev:mainfrom
whati001:feat/ble_console

Conversation

@whati001
Copy link
Copy Markdown
Contributor

@whati001 whati001 commented Nov 3, 2025

Summary by CodeRabbit

  • New Features

    • BLE driver: async connect/streaming, device info, CLI commands and interactive console for device I/O.
  • Documentation

    • New package README with install, API/CLI examples and YAML config; docs index and reference updated.
  • Chores

    • BLE driver package added to workspace and project dependencies; example exporter manifest added.
  • Tests

    • Test placeholder added for future BLE driver tests.

@netlify
Copy link
Copy Markdown

netlify Bot commented Nov 3, 2025

Deploy Preview for jumpstarter-docs ready!

Name Link
🔨 Latest commit bb43a04
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/690ca2e25dc7bc0008d37a63
😎 Deploy Preview https://deploy-preview-726--jumpstarter-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 3, 2025

Walkthrough

Adds a new BLE driver package jumpstarter-driver-ble (driver, client, console, README, example, minimal test, project metadata) and registers it in the workspace and meta-package dependencies.

Changes

Cohort / File(s) Summary
Meta-package & workspace
packages/jumpstarter-all/pyproject.toml, pyproject.toml
Added jumpstarter-driver-ble to the meta-package dependencies and registered the package as a workspace source.
BLE package metadata
packages/jumpstarter-driver-ble/pyproject.toml
New pyproject configuring project metadata, dependencies (anyio, jumpstarter, bleak), test settings, and Hatch build hooks.
Documentation & example
packages/jumpstarter-driver-ble/README.md, packages/jumpstarter-driver-ble/examples/exporter.yaml, docs/source/reference/package-apis/drivers/ble.md, docs/source/reference/package-apis/drivers/index.md
Added README, API doc link, drivers index entry, and a Kubernetes exporter YAML example for BLE configuration.
BLE driver implementation
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py
New async BLE driver: BleWriteNotifyStream driver, AsyncBleConfig, AsyncBleWrapper, notify handler, characteristic validation, connect() exportstream with Bleak client, send/receive queue, and cleanup/error handling.
BLE client & CLI
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py
New BleWriteNotifyStreamClient with info(), open(), pexpect() contextmanager, and Click CLI commands (info, start-console).
Interactive console
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py
New BleConsole implementing raw-terminal mode, concurrent stdin→BLE and BLE→stdout tasks, triple‑Ctrl‑B exit handling, and binary-safe I/O using FileReadStream/FileWriteStream.
Tests
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py
Added test placeholder with a TODO line.

Sequence Diagram(s)

sequenceDiagram
    participant CLI
    participant Client as BleClient
    participant Driver as BleWriteNotifyStream
    participant Device as BleakClient
    participant Console

    CLI->>Client: start-console / info
    Client->>Driver: connect() (exportstream)
    alt successful connect
        Driver->>Device: BleakClient.connect()
        Driver->>Device: discover & validate service/characteristics
        Note right of Driver `#DDEEF7`: register notify handler → enqueue to notify_queue
        Driver->>Client: yield AsyncBleWrapper (stream)
        Client->>Console: run(stream)
        par IO
            Console->>Driver: stream.send(data) → Device.write_gatt_char
        and
            Device->>Driver: notify(data) → enqueue
            Driver->>Console: notify_queue → Console writes to stdout
        end
    else failure
        Driver->>Client: raise BleakError / disconnect
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py — async lifecycle, Bleak API usage, characteristic validation, notification handler and teardown.
    • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py — terminal raw mode, concurrency, Ctrl‑B detection, binary I/O correctness.
    • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py — pexpect integration and CLI wiring.

Possibly related PRs

Suggested labels

backport release-0.7

Suggested reviewers

  • NickCao
  • kirkbrauer
  • mangelajo

Poem

🐇 I hopped into code with a tiny bell,
New BLE trails where quiet packets dwell.
Triple taps hush the chatter's race,
Streams of bytes find their snug place.
The rabbit grins — connections hum in grace.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: a new BLE (Bluetooth Low Energy) driver with write/notify streaming capability and CLI console integration.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (6)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py (1)

1-1: Address the TODO: Add test coverage for the BLE driver.

Test coverage is missing for the new BLE driver functionality. Consider adding tests for connection, streaming, and error handling scenarios.

Would you like me to generate a test suite template or open an issue to track this task?

packages/jumpstarter-driver-ble/README.md (1)

48-60: Add language identifier to the code block.

The CLI usage example should specify a language identifier (e.g., console or text) for proper rendering.

Apply this diff:

-```
+```console
 jumpstarter ⚡ local ➤ j ble
 Usage: j ble [OPTIONS] COMMAND [ARGS]...
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (4)

14-20: Use proper logging instead of print statement.

The notification handler uses print() for warnings. While this is a callback function without direct logger access, consider passing a logger instance to the handler for consistent logging across the driver.

Example approach - modify the handler signature:

-def _ble_notify_handler(_sender: BleakGATTCharacteristic, data: bytearray, data_queue: asyncio.Queue):
+def _ble_notify_handler(_sender: BleakGATTCharacteristic, data: bytearray, data_queue: asyncio.Queue, logger=None):
     """Notification handler that puts received data into a queue."""
     try:
         data_queue.put_nowait(data)
     except asyncio.QueueFull:
-        print("Warning: Data queue is full, dropping message")
+        if logger:
+            logger.warning("Data queue is full, dropping message")
+        else:
+            print("Warning: Data queue is full, dropping message")

Then update the handler creation at line 115-116 to pass the logger.


22-34: Use @DataClass for consistency.

AsyncBleConfig is a simple data holder that could benefit from the @dataclass decorator for consistency with AsyncBleWrapper below.

+@dataclass
 class AsyncBleConfig():
-    def __init__(
-        self,
-        address: str,
-        service_uuid: str,
-        write_char_uuid: str,
-        notify_char_uuid: str,
-    ):
-        self.address = address
-        self.service_uuid = service_uuid
-        self.write_char_uuid = write_char_uuid
-        self.notify_char_uuid = notify_char_uuid
+    address: str
+    service_uuid: str
+    write_char_uuid: str
+    notify_char_uuid: str

114-120: Remove unnecessary truthiness check.

Line 114 checks if notify_char_uuid is truthy, but it's a required field in the class definition (line 63). This check is unnecessary unless you intend to make this field optional.

If notify_char_uuid should always be present, remove the check:

-    # register notification handler if notify_char_uuid is provided
-    if self.notify_char_uuid:
-        notify_handler = partial(
-            _ble_notify_handler, data_queue=self.notify_queue)
-        await client.start_notify(self.notify_char_uuid, notify_handler)
-        self.logger.info(
-            "Setting up notification handler for characteristic UUID: %s", self.notify_char_uuid)
+    # register notification handler
+    notify_handler = partial(
+        _ble_notify_handler, data_queue=self.notify_queue)
+    await client.start_notify(self.notify_char_uuid, notify_handler)
+    self.logger.info(
+        "Setting up notification handler for characteristic UUID: %s", self.notify_char_uuid)

Alternatively, if the field should be optional, add a type hint and default value to make that explicit.


128-128: Remove commented-out code.

Line 128 contains commented-out code for read_char_uuid. Remove it or implement the functionality if needed.

             config=AsyncBleConfig(
                 address=self.address,
                 service_uuid=self.service_uuid,
                 write_char_uuid=self.write_char_uuid,
-                # read_char_uuid=self.read_char_uuid,
                 notify_char_uuid=self.notify_char_uuid,
             ),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca77ec1 and bbf7ba3.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • packages/jumpstarter-all/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-ble/README.md (1 hunks)
  • packages/jumpstarter-driver-ble/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-ble/pyproject.toml (1 hunks)
  • pyproject.toml (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-01-29T11:52:43.554Z
Learnt from: bennyz
Repo: jumpstarter-dev/jumpstarter PR: 241
File: packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/client.py:52-60
Timestamp: 2025-01-29T11:52:43.554Z
Learning: The TFTP driver (packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/driver.py) handles all low-level concerns like path validation, error handling, and checksum computation. The client (packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/client.py) should remain simple as it delegates these responsibilities to the driver.

Applied to files:

  • packages/jumpstarter-all/pyproject.toml
🧬 Code graph analysis (3)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (4)
packages/jumpstarter-driver-network/jumpstarter_driver_network/adapters/pexpect.py (1)
  • PexpectAdapter (11-19)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
  • BleConsole (16-65)
  • run (20-22)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
packages/jumpstarter/jumpstarter/client/decorators.py (2)
  • driver_click_group (13-54)
  • command (90-102)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (2)
packages/jumpstarter/jumpstarter/driver/base.py (1)
  • Driver (56-273)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (2)
  • info (20-22)
  • info (52-54)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (3)
  • client (73-74)
  • receive (45-46)
  • send (42-43)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
🪛 GitHub Actions: Lint
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py

[error] 1-1: Ruff formatting check failed. Import block is un-sorted or un-formatted. (I001)

🪛 GitHub Check: ruff
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py

[failure] 1-11: Ruff (I001)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py:1:1: I001 Import block is un-sorted or un-formatted

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py

[failure] 91-91: Ruff (C400)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py:91:30: C400 Unnecessary generator (rewrite as a list comprehension)


[failure] 11-11: Ruff (F811)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py:11:19: F811 Redefinition of unused BleakClient from line 9


[failure] 1-14: Ruff (I001)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py:1:1: I001 Import block is un-sorted or un-formatted

🪛 markdownlint-cli2 (0.18.1)
packages/jumpstarter-driver-ble/README.md

48-48: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: build
  • GitHub Check: e2e
🔇 Additional comments (7)
packages/jumpstarter-driver-ble/pyproject.toml (1)

1-37: LGTM: Package configuration follows repository standards.

The pyproject.toml configuration is well-structured and consistent with other driver packages in the repository. Dependencies are appropriate for a BLE driver implementation.

packages/jumpstarter-all/pyproject.toml (1)

15-15: LGTM: BLE driver correctly added to meta-package.

The new BLE driver has been properly added to the meta-package dependencies in alphabetical order.

pyproject.toml (1)

10-10: LGTM: BLE driver workspace member configured correctly.

The workspace member entry is properly positioned and follows the repository's workspace configuration pattern.

packages/jumpstarter-driver-ble/examples/exporter.yaml (1)

1-16: LGTM: Example configuration is clear and complete.

The exporter configuration provides a good example with all required BLE parameters (address, service UUID, characteristic UUIDs).

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1)

11-64: LGTM: Client implementation follows driver patterns correctly.

The BleClient class properly extends DriverClient and implements the expected interface patterns:

  • Method delegation via self.call()
  • Pexpect adapter integration
  • CLI command structure using driver_click_group decorator

Once the import order is fixed, this implementation is ready.

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)

16-65: LGTM: Console implementation is well-structured with proper async I/O handling.

The BleConsole class correctly:

  • Manages terminal raw mode with proper cleanup
  • Uses AnyIO task groups for concurrent bidirectional streaming
  • Implements triple Ctrl-B exit mechanism
  • Handles screen clearing on exit

52-65: Verify Ctrl-B forwarding behavior to remote device.

The current implementation sends Ctrl-B characters to the remote device before checking if three consecutive presses occurred. This means the remote device receives all Ctrl-B bytes before the console exits. Verify this is the intended behavior, as users might expect Ctrl-B to be consumed locally without being forwarded.

Comment thread packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py
Comment thread packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py Outdated
Comment thread packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py
Comment thread packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py Outdated
Comment thread packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py
@whati001 whati001 closed this Nov 3, 2025
@whati001 whati001 reopened this Nov 3, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/jumpstarter-driver-ble/README.md (1)

48-60: Add language identifier to the fenced code block.

The code block should specify a language for proper syntax highlighting and rendering.

Apply this diff:

-```
+```console
 jumpstarter ⚡ local ➤ j ble
 Usage: j ble [OPTIONS] COMMAND [ARGS]...

As per static analysis hints.

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (3)

13-18: Use proper logging instead of print statement.

Library code should use logging instead of print() to allow users to control log output and levels.

Consider passing a logger to the notification handler:

-def _ble_notify_handler(_sender: BleakGATTCharacteristic, data: bytearray, data_queue: asyncio.Queue):
+def _ble_notify_handler(_sender: BleakGATTCharacteristic, data: bytearray, data_queue: asyncio.Queue, logger=None):
     """Notification handler that puts received data into a queue."""
     try:
         data_queue.put_nowait(data)
     except asyncio.QueueFull:
-        print("Warning: Data queue is full, dropping message")
+        if logger:
+            logger.warning("Data queue is full, dropping message")

Then update the partial call at line 116-117 to include the logger:

notify_handler = partial(
    _ble_notify_handler, data_queue=notify_queue, logger=self.logger)

21-32: Consider using dataclass for consistency and clarity.

AsyncBleConfig could be a dataclass like AsyncBleWrapper for consistency and to benefit from automatic __init__, __repr__, and type annotations.

Apply this diff:

+@dataclass
 class AsyncBleConfig():
-    def __init__(
-        self,
-        address: str,
-        service_uuid: str,
-        write_char_uuid: str,
-        notify_char_uuid: str,
-    ):
-        self.address = address
-        self.service_uuid = service_uuid
-        self.write_char_uuid = write_char_uuid
-        self.notify_char_uuid = notify_char_uuid
+    address: str
+    service_uuid: str
+    write_char_uuid: str
+    notify_char_uuid: str

122-131: Remove commented-out code.

Line 129 contains commented-out code that references a non-existent read_char_uuid attribute. This should be removed.

Apply this diff:

                     async with AsyncBleWrapper(
                         client=client,
                         notify_queue=notify_queue,
                         config=AsyncBleConfig(
                             address=self.address,
                             service_uuid=self.service_uuid,
                             write_char_uuid=self.write_char_uuid,
-                            # read_char_uuid=self.read_char_uuid,
                             notify_char_uuid=self.notify_char_uuid,
                         ),
                     ) as stream:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbf7ba3 and 95018c1.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • packages/jumpstarter-all/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-ble/README.md (1 hunks)
  • packages/jumpstarter-driver-ble/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-ble/pyproject.toml (1 hunks)
  • pyproject.toml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/jumpstarter-all/pyproject.toml
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py
  • pyproject.toml
  • packages/jumpstarter-driver-ble/examples/exporter.yaml
  • packages/jumpstarter-driver-ble/pyproject.toml
🧰 Additional context used
🧬 Code graph analysis (2)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (3)
  • client (69-70)
  • receive (44-45)
  • send (41-42)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (2)
packages/jumpstarter/jumpstarter/driver/base.py (1)
  • Driver (56-273)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (2)
  • info (21-23)
  • info (53-55)
🪛 markdownlint-cli2 (0.18.1)
packages/jumpstarter-driver-ble/README.md

48-48: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: e2e
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: build
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
🔇 Additional comments (2)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (1)

82-145: LGTM! Core BLE driver implementation is solid.

The connection management, characteristic validation, and error handling are well-implemented. Previous critical issues from earlier reviews have been properly addressed:

  • Queue is created fresh per connection (line 107)
  • Service validation now properly errors if service is missing (lines 96-97)
  • Imports are clean and properly ordered

The async context manager pattern is correctly used, and the BLE stream wrapper appropriately implements the ObjectStream interface.

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (1)

16-65: LGTM! Clean console implementation with proper terminal handling.

The console implementation demonstrates good practices:

  • Proper terminal state save/restore with cleanup (lines 24-33)
  • Binary-safe I/O using buffer for BLE data
  • Clear user communication about the exit sequence (line 38)
  • Appropriate use of task groups for concurrent I/O
  • Exception group handling with except* for clean exit

The triple Ctrl-B detection mechanism is user-friendly and the raw mode handling ensures all keypresses are captured correctly.

Comment thread packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1)

1-10: Fix import order to resolve pipeline failure.

The imports remain incorrectly ordered, causing the ruff check to fail. Standard library imports must come first, followed by third-party imports (click, pexpect), then first-party imports (jumpstarter, jumpstarter_driver_network), and finally local imports.

Apply this diff to fix the import order:

 from contextlib import contextmanager
 
 import click
-from jumpstarter_driver_network.adapters import PexpectAdapter
 from pexpect.fdpexpect import fdspawn
+from jumpstarter_driver_network.adapters import PexpectAdapter
 
-from .console import BleConsole
 from jumpstarter.client import DriverClient
 from jumpstarter.client.decorators import driver_click_group
+
+from .console import BleConsole
🧹 Nitpick comments (4)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (1)

24-33: Consider adding TTY check before terminal manipulation.

If stdin is not a TTY (e.g., piped input or non-interactive session), termios.tcgetattr() will raise an OSError. While the console is intended for interactive use, adding a guard would make the error message clearer.

Consider this enhancement:

 @contextmanager
 def setraw(self):
+    if not sys.stdin.isatty():
+        raise RuntimeError("BLE console requires an interactive terminal (stdin is not a TTY)")
     original = termios.tcgetattr(sys.stdin.fileno())
     try:
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (3)

13-18: Consider using logging module for warnings.

The warning message uses print, which doesn't integrate with the application's logging system. Since this is a module-level function, use the logging module directly.

Apply this diff:

+import logging
+
+_logger = logging.getLogger(__name__)
+
 def _ble_notify_handler(_sender: BleakGATTCharacteristic, data: bytearray, data_queue: asyncio.Queue):
     """Notification handler that puts received data into a queue."""
     try:
         data_queue.put_nowait(data)
     except asyncio.QueueFull:
-        print("Warning: Data queue is full, dropping message")
+        _logger.warning("Data queue is full, dropping message")

21-32: Consider using @DataClass for consistency.

AsyncBleConfig manually implements __init__, while other classes in the file (AsyncBleWrapper, Ble) use the @dataclass decorator. Using @dataclass here would improve consistency and reduce boilerplate.

Apply this diff:

+@dataclass
 class AsyncBleConfig():
-    def __init__(
-        self,
-        address: str,
-        service_uuid: str,
-        write_char_uuid: str,
-        notify_char_uuid: str,
-    ):
-        self.address = address
-        self.service_uuid = service_uuid
-        self.write_char_uuid = write_char_uuid
-        self.notify_char_uuid = notify_char_uuid
+    address: str
+    service_uuid: str
+    write_char_uuid: str
+    notify_char_uuid: str

122-133: Remove commented-out parameter.

Line 129 contains a commented-out read_char_uuid parameter that should be removed to clean up the code.

Apply this diff:

                     async with AsyncBleWrapper(
                         client=client,
                         notify_queue=notify_queue,
                         config=AsyncBleConfig(
                             address=self.address,
                             service_uuid=self.service_uuid,
                             write_char_uuid=self.write_char_uuid,
-                            # read_char_uuid=self.read_char_uuid,
                             notify_char_uuid=self.notify_char_uuid,
                         ),
                     ) as stream:
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95018c1 and 306fd44.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • packages/jumpstarter-all/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-ble/README.md (1 hunks)
  • packages/jumpstarter-driver-ble/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-ble/pyproject.toml (1 hunks)
  • pyproject.toml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/jumpstarter-driver-ble/examples/exporter.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/jumpstarter-driver-ble/pyproject.toml
  • pyproject.toml
  • packages/jumpstarter-all/pyproject.toml
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py
🧰 Additional context used
🧬 Code graph analysis (3)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (5)
packages/jumpstarter-driver-network/jumpstarter_driver_network/adapters/pexpect.py (1)
  • PexpectAdapter (11-19)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
  • BleConsole (16-65)
  • run (20-22)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (2)
  • client (69-70)
  • info (73-80)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
packages/jumpstarter/jumpstarter/client/decorators.py (2)
  • driver_click_group (13-54)
  • command (90-102)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (3)
  • client (69-70)
  • receive (44-45)
  • send (41-42)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (2)
packages/jumpstarter/jumpstarter/driver/base.py (1)
  • Driver (56-273)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (2)
  • info (21-23)
  • info (53-55)
🪛 markdownlint-cli2 (0.18.1)
packages/jumpstarter-driver-ble/README.md

48-48: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: build
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: e2e
🔇 Additional comments (17)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (4)

21-23: LGTM!

The method correctly delegates to the driver's exported info() method.


25-33: LGTM!

The method correctly uses the stack context manager to open a pexpect session, consistent with other Jumpstarter drivers.


35-44: LGTM!

The context manager correctly provides a PexpectAdapter for BLE interactions.


46-65: LGTM!

The CLI implementation correctly follows Jumpstarter patterns and properly integrates the BLE console with clear user instructions.

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (6)

1-10: LGTM!

Import order correctly follows Python conventions (stdlib → third-party → first-party).


12-13: LGTM!

Custom exception appropriately used for console exit signaling with AnyIO exception groups.


20-22: LGTM!

Correctly uses the portal pattern to bridge synchronous and asynchronous execution contexts.


35-43: LGTM!

Properly uses AnyIO task groups for concurrent bidirectional I/O and exception group handling.


45-50: LGTM!

The implementation correctly handles binary-safe streaming from BLE to stdout with appropriate flushing for interactive console use.


52-65: LGTM!

The stdin-to-BLE forwarding correctly implements the triple Ctrl-B exit sequence with proper counter management. Byte-by-byte reading ensures immediate detection of the escape sequence.

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (7)

1-11: LGTM!

Import order now correctly follows conventions (stdlib → third-party → first-party) with no duplicates.


35-52: LGTM!

AsyncBleWrapper correctly implements the ObjectStream interface for BLE characteristics, with send_eof() appropriately implemented as a no-op for BLE.


55-66: LGTM!

Class declaration and initialization follow Jumpstarter Driver patterns correctly, with proper docstring syntax.


68-70: LGTM!

Correctly returns the client class path as required by the Driver base class.


72-80: LGTM!

The exported info() method correctly provides BLE connection details in a readable format.


82-97: LGTM!

Service and characteristic validation is now complete, properly raising errors when required UUIDs are not found on the device.


99-145: LGTM!

The connect() method correctly implements the BLE connection lifecycle with proper error handling, characteristic validation, and per-connection queue initialization. All previously identified issues have been addressed.

Comment thread packages/jumpstarter-driver-ble/README.md Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1)

1-10: Fix import order to resolve linter failure.

The imports are still not correctly ordered. Third-party imports (pexpect on line 5) must come before first-party imports (jumpstarter_driver_network on line 4).

Apply this diff to fix the import order:

 from contextlib import contextmanager
 
 import click
-from jumpstarter_driver_network.adapters import PexpectAdapter
 from pexpect.fdpexpect import fdspawn
 
-from .console import BleConsole
+from jumpstarter_driver_network.adapters import PexpectAdapter
 from jumpstarter.client import DriverClient
 from jumpstarter.client.decorators import driver_click_group
+
+from .console import BleConsole
🧹 Nitpick comments (5)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (3)

13-18: Replace print() with proper logging.

Using print() for warnings in driver code bypasses the logging framework and doesn't respect log level configuration. The Ble driver class already has self.logger available.

Apply this diff to use logging:

-def _ble_notify_handler(_sender: BleakGATTCharacteristic, data: bytearray, data_queue: asyncio.Queue):
+def _ble_notify_handler(_sender: BleakGATTCharacteristic, data: bytearray, data_queue: asyncio.Queue, logger=None):
     """Notification handler that puts received data into a queue."""
     try:
         data_queue.put_nowait(data)
     except asyncio.QueueFull:
-        print("Warning: Data queue is full, dropping message")
+        if logger:
+            logger.warning("Data queue is full, dropping message")

Then update the handler creation at line 116-117:

                     if self.notify_char_uuid:
                         notify_handler = partial(
-                            _ble_notify_handler, data_queue=notify_queue)
+                            _ble_notify_handler, data_queue=notify_queue, logger=self.logger)

21-32: Consider using @dataclass for consistency.

The file uses @dataclass for AsyncBleWrapper and Ble, but AsyncBleConfig is a plain class. Using a dataclass would reduce boilerplate and maintain consistency.

Apply this diff:

+@dataclass
 class AsyncBleConfig():
-    def __init__(
-        self,
-        address: str,
-        service_uuid: str,
-        write_char_uuid: str,
-        notify_char_uuid: str,
-    ):
-        self.address = address
-        self.service_uuid = service_uuid
-        self.write_char_uuid = write_char_uuid
-        self.notify_char_uuid = notify_char_uuid
+    address: str
+    service_uuid: str
+    write_char_uuid: str
+    notify_char_uuid: str

122-132: Remove commented-out code.

Line 129 contains a commented-out parameter read_char_uuid that should either be implemented or removed to avoid confusion.

Apply this diff to remove the commented line:

                     async with AsyncBleWrapper(
                         client=client,
                         notify_queue=notify_queue,
                         config=AsyncBleConfig(
                             address=self.address,
                             service_uuid=self.service_uuid,
                             write_char_uuid=self.write_char_uuid,
-                            # read_char_uuid=self.read_char_uuid,
                             notify_char_uuid=self.notify_char_uuid,
                         ),
                     ) as stream:
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)

45-50: Add error handling for stream closure.

The method has no handling for stream closure or disconnection. If the BLE device disconnects, stream.receive() may raise an exception, causing the task to crash and potentially leaving the terminal in an inconsistent state.

Apply this diff to handle stream closure gracefully:

 async def __ble_to_stdout(self, stream):
     stdout = FileWriteStream(sys.stdout.buffer)
-    while True:
-        data = await stream.receive()
-        await stdout.send(data)
-        sys.stdout.flush()
+    try:
+        while True:
+            data = await stream.receive()
+            await stdout.send(data)
+            sys.stdout.flush()
+    except Exception:
+        # Stream closed or error - exit console
+        raise BleConsoleExit

52-65: Add error handling for stream closure.

Similar to __ble_to_stdout, this method lacks error handling for stream closure. If the BLE device disconnects, stream.send() may fail, causing the task to crash.

Apply this diff:

 async def __stdin_to_ble(self, stream):
     stdin = FileReadStream(sys.stdin.buffer)
     ctrl_b_count = 0
-    while True:
-        data = await stdin.receive(max_bytes=1)
-        if not data:
-            continue
-        if data == b"\x02":  # Ctrl-B
-            ctrl_b_count += 1
-            if ctrl_b_count == 3:
-                raise BleConsoleExit
-        else:
-            ctrl_b_count = 0
-        await stream.send(data)
+    try:
+        while True:
+            data = await stdin.receive(max_bytes=1)
+            if not data:
+                continue
+            if data == b"\x02":  # Ctrl-B
+                ctrl_b_count += 1
+                if ctrl_b_count == 3:
+                    raise BleConsoleExit
+            else:
+                ctrl_b_count = 0
+            await stream.send(data)
+    except Exception:
+        # Stream closed or error - exit console
+        raise BleConsoleExit
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 306fd44 and d231a7e.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • packages/jumpstarter-all/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-ble/README.md (1 hunks)
  • packages/jumpstarter-driver-ble/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-ble/pyproject.toml (1 hunks)
  • pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/jumpstarter-driver-ble/examples/exporter.yaml
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py
  • packages/jumpstarter-driver-ble/pyproject.toml
  • packages/jumpstarter-all/pyproject.toml
  • pyproject.toml
  • packages/jumpstarter-driver-ble/README.md
🧰 Additional context used
🧬 Code graph analysis (3)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (3)
  • client (69-70)
  • receive (44-45)
  • send (41-42)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (2)
packages/jumpstarter/jumpstarter/driver/base.py (1)
  • Driver (56-273)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (2)
  • info (21-23)
  • info (53-55)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (3)
packages/jumpstarter-driver-network/jumpstarter_driver_network/adapters/pexpect.py (1)
  • PexpectAdapter (11-19)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
  • BleConsole (16-65)
  • run (20-22)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: build
  • GitHub Check: e2e
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.12)

Comment thread packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py
Comment thread packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1)

1-9: Import order still not matching standard conventions.

The imports are not fully sorted according to standard Python conventions (stdlib → third-party → first-party → local). Lines 4-5 show jumpstarter_driver_network (first-party) before pexpect (third-party), and the local import .console comes before other first-party jumpstarter imports.

The past review suggested this ordering:

from contextlib import contextmanager

import click
from pexpect.fdpexpect import fdspawn
from jumpstarter_driver_network.adapters import PexpectAdapter

from jumpstarter.client import DriverClient
from jumpstarter.client.decorators import driver_click_group

from .console import BleConsole
🧹 Nitpick comments (4)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (3)

13-18: Use logger instead of print for dropped messages.

The notification handler uses print() to warn about dropped messages. In a driver/library context, this should use the driver's logger for consistency with the rest of the codebase.

Consider passing the logger to the handler and logging the warning:

def _ble_notify_handler(_sender: BleakGATTCharacteristic, data: bytearray, data_queue: asyncio.Queue, logger=None):
    """Notification handler that puts received data into a queue."""
    try:
        data_queue.put_nowait(data)
    except asyncio.QueueFull:
        if logger:
            logger.warning("Data queue is full, dropping message")

Then update the partial at line 114-115 to include the logger:

notify_handler = partial(
    _ble_notify_handler, data_queue=notify_queue, logger=self.logger)

21-32: Consider using @DataClass for consistency.

AsyncBleConfig manually defines __init__, while AsyncBleWrapper (line 35) uses the @dataclass decorator. For consistency and reduced boilerplate, consider converting this class to a dataclass.

Apply this diff to use @DataClass:

+@dataclass
 class AsyncBleConfig():
-    def __init__(
-        self,
-        address: str,
-        service_uuid: str,
-        write_char_uuid: str,
-        notify_char_uuid: str,
-    ):
-        self.address = address
-        self.service_uuid = service_uuid
-        self.write_char_uuid = write_char_uuid
-        self.notify_char_uuid = notify_char_uuid
+    address: str
+    service_uuid: str
+    write_char_uuid: str
+    notify_char_uuid: str

127-127: Remove commented code.

Line 127 contains commented-out code for read_char_uuid. This should be removed to keep the codebase clean.

Apply this diff:

                         config=AsyncBleConfig(
                             address=self.address,
                             service_uuid=self.service_uuid,
                             write_char_uuid=self.write_char_uuid,
-                            # read_char_uuid=self.read_char_uuid,
                             notify_char_uuid=self.notify_char_uuid,
                         ),
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1)

57-63: Consider clarifying the exit instruction message.

Line 61 says "exit with CTRL+B x 3 times" which is slightly informal. For consistency with the console message (console.py line 39), consider using clearer phrasing.

Apply this diff for clearer messaging:

         @base.command()
         def start_console():
             """Start BLE console"""
             click.echo(
-                "\nStarting ble console ... exit with CTRL+B x 3 times\n")
+                "\nStarting BLE console. Press Ctrl-B three times to exit.\n")
             console = BleConsole(ble_client=self)
             console.run()
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d231a7e and cdc37c3.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • packages/jumpstarter-all/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-ble/README.md (1 hunks)
  • packages/jumpstarter-driver-ble/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-ble/pyproject.toml (1 hunks)
  • pyproject.toml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/jumpstarter-driver-ble/examples/exporter.yaml
  • pyproject.toml
  • packages/jumpstarter-driver-ble/README.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-01-29T11:52:43.554Z
Learnt from: bennyz
Repo: jumpstarter-dev/jumpstarter PR: 241
File: packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/client.py:52-60
Timestamp: 2025-01-29T11:52:43.554Z
Learning: The TFTP driver (packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/driver.py) handles all low-level concerns like path validation, error handling, and checksum computation. The client (packages/jumpstarter-driver-tftp/jumpstarter_driver_tftp/client.py) should remain simple as it delegates these responsibilities to the driver.

Applied to files:

  • packages/jumpstarter-all/pyproject.toml
🧬 Code graph analysis (3)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (3)
  • client (69-70)
  • receive (44-45)
  • send (41-42)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (2)
packages/jumpstarter/jumpstarter/driver/base.py (1)
  • Driver (56-273)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (2)
  • info (21-23)
  • info (53-55)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (4)
packages/jumpstarter-driver-network/jumpstarter_driver_network/adapters/pexpect.py (1)
  • PexpectAdapter (11-19)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
  • BleConsole (16-66)
  • run (20-22)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
packages/jumpstarter/jumpstarter/client/decorators.py (2)
  • driver_click_group (13-54)
  • command (90-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: build
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: e2e
🔇 Additional comments (18)
packages/jumpstarter-all/pyproject.toml (1)

14-16: Dependency placement is alphabetically correct.

The new BLE driver dependency is properly positioned within the alphabetically-ordered list and follows the established pattern.

packages/jumpstarter-driver-ble/pyproject.toml (2)

23-27: Pytest configuration looks good.

The pytest setup with coverage reporting, log handling, and testpaths follows a standard pattern for test automation within the Jumpstarter framework.


15-21: VCS versioning and build tooling align with existing patterns.

The Hatch configuration for version control, metadata URLs, and build system requirements is consistent with other driver packages in the repository.

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (6)

1-9: LGTM!

The imports are well-organized and appropriate for the terminal-based BLE console implementation.


12-13: LGTM!

Clean exception definition for handling console exit signals.


16-22: LGTM!

The initialization and run method correctly bridge synchronous and asynchronous execution using the portal pattern.


24-33: LGTM!

The terminal raw mode handling is correct, with proper attribute saving/restoration and screen cleanup on exit.


35-44: LGTM!

The async streaming logic is well-structured with proper exception group handling. The connection message now correctly uses \r\n for raw mode display.


46-66: LGTM!

The bidirectional I/O streaming logic is well-implemented with proper exit detection. The Ctrl-B bytes are forwarded to the device before exit detection (line 66), which means all user input is transmitted - this appears intentional for a transparent console session.

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (6)

1-10: LGTM!

The imports are properly organized following Python conventions (stdlib, third-party, local) with no duplicates.


35-52: LGTM!

The AsyncBleWrapper correctly implements the ObjectStream interface with appropriate send/receive/aclose methods. The no-op send_eof is appropriate since BLE characteristics lack explicit EOF signaling.


55-66: LGTM!

The driver class is properly structured with required BLE configuration fields and correct initialization chaining.


68-78: LGTM!

The client locator and info methods are correctly implemented with clean formatting.


80-95: LGTM!

The characteristic validation properly checks for service existence and required characteristics, raising descriptive errors when components are missing.


135-143: LGTM!

The error handling properly logs connection failures and raises descriptive exceptions.

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (3)

12-23: LGTM!

The class documentation is clear, and the info method correctly delegates to the driver.


25-44: LGTM!

The pexpect integration correctly uses PexpectAdapter and follows established patterns from other drivers in the codebase.


46-65: LGTM!

The CLI implementation correctly uses the driver_click_group pattern and provides appropriate commands for BLE interaction.

Comment on lines +113 to +122
if self.notify_char_uuid:
notify_handler = partial(
_ble_notify_handler, data_queue=notify_queue)
await client.start_notify(self.notify_char_uuid, notify_handler)
self.logger.info(
"Setting up notification handler for characteristic UUID: %s", self.notify_char_uuid)

async with AsyncBleWrapper(
client=client,
notify_queue=notify_queue,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Potential hang if receive() is called without notification handler.

When notify_char_uuid is falsy (line 113), no notification handler is registered, but the AsyncBleWrapper is still created with the notify_queue. If a consumer calls receive() on the stream without notifications being enabled, it will block indefinitely waiting for data that will never arrive.

Consider one of these approaches:

  1. Document that receive() should only be called when notify_char_uuid is configured
  2. Make notify_char_uuid a required field if receiving is a core feature
  3. Have receive() raise a descriptive error if called without notification support
🤖 Prompt for AI Agents
In packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py around lines
113-122, creating AsyncBleWrapper with a notify_queue even when
self.notify_char_uuid is falsy can cause receive() to block forever; fix by (1)
only create and pass notify_queue to AsyncBleWrapper when self.notify_char_uuid
is truthy (avoid creating an unused queue), and (2) add a clear guard in the
stream.receive() implementation to raise a descriptive exception if
notifications are not enabled (e.g., raise RuntimeError/ValueError with
guidance) so callers get immediate, actionable feedback instead of hanging.

Comment thread packages/jumpstarter-driver-ble/pyproject.toml
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (2)

13-18: Use logger instead of print() for warnings.

The print() statement on line 18 bypasses the driver's logging infrastructure. This warning won't appear in structured logs and can't be filtered by log level.

Since this is a module-level function without access to a logger instance, consider passing a logger as a parameter:

-def _ble_notify_handler(_sender: BleakGATTCharacteristic, data: bytearray, data_queue: asyncio.Queue):
+def _ble_notify_handler(_sender: BleakGATTCharacteristic, data: bytearray, data_queue: asyncio.Queue, logger):
     """Notification handler that puts received data into a queue."""
     try:
         data_queue.put_nowait(data)
     except asyncio.QueueFull:
-        print("Warning: Data queue is full, dropping message")
+        logger.warning("Data queue is full, dropping message")

Then update the handler registration in connect() around line 113:

     notify_handler = partial(
-        _ble_notify_handler, data_queue=notify_queue)
+        _ble_notify_handler, data_queue=notify_queue, logger=self.logger)

21-32: Consider using @DataClass for consistency.

The AsyncBleConfig class uses manual __init__, while other classes in this file (AsyncBleWrapper, Ble) use the @dataclass decorator. Using @dataclass would reduce boilerplate and maintain consistency.

Apply this diff:

+@dataclass
 class AsyncBleConfig():
-    def __init__(
-        self,
-        address: str,
-        service_uuid: str,
-        write_char_uuid: str,
-        notify_char_uuid: str,
-    ):
-        self.address = address
-        self.service_uuid = service_uuid
-        self.write_char_uuid = write_char_uuid
-        self.notify_char_uuid = notify_char_uuid
+    address: str
+    service_uuid: str
+    write_char_uuid: str
+    notify_char_uuid: str
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cdc37c3 and 3e3d05e.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • packages/jumpstarter-all/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-ble/README.md (1 hunks)
  • packages/jumpstarter-driver-ble/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-ble/pyproject.toml (1 hunks)
  • pyproject.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/jumpstarter-driver-ble/pyproject.toml
  • pyproject.toml
  • packages/jumpstarter-driver-ble/README.md
  • packages/jumpstarter-all/pyproject.toml
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py
🧰 Additional context used
🧬 Code graph analysis (3)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (2)
packages/jumpstarter/jumpstarter/driver/base.py (1)
  • Driver (56-273)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (2)
  • info (21-23)
  • info (53-55)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (5)
packages/jumpstarter-driver-network/jumpstarter_driver_network/adapters/pexpect.py (1)
  • PexpectAdapter (11-19)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
  • BleConsole (16-66)
  • run (20-22)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (2)
  • client (69-70)
  • info (73-78)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
packages/jumpstarter/jumpstarter/client/decorators.py (2)
  • driver_click_group (13-54)
  • command (90-102)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (3)
  • client (69-70)
  • receive (44-45)
  • send (41-42)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Redirect rules - jumpstarter-docs
  • GitHub Check: Header rules - jumpstarter-docs
  • GitHub Check: Pages changed - jumpstarter-docs
  • GitHub Check: build
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.12)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: e2e
🔇 Additional comments (15)
packages/jumpstarter-driver-ble/examples/exporter.yaml (1)

1-16: LGTM! Example configuration is well-structured.

The ExporterConfig manifest correctly defines the BLE driver type and all required configuration fields (address, service_uuid, write_char_uuid, notify_char_uuid) that align with the Ble driver class implementation.

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (4)

35-52: LGTM! Stream wrapper implementation is correct.

The AsyncBleWrapper properly implements the ObjectStream interface with appropriate BLE-specific behavior:

  • send() writes to the GATT characteristic
  • receive() retrieves from the notification queue
  • send_eof() is correctly a no-op (BLE characteristics lack explicit EOF)
  • aclose() handles disconnection

55-78: LGTM! Driver class structure and info() method are well-implemented.

The Ble driver class correctly:

  • Defines all required configuration fields
  • Implements the required client() classmethod
  • Provides a clear info() method that returns formatted configuration details
  • Properly chains __post_init__ to parent class

80-95: LGTM! Characteristic validation is thorough.

The _check_ble_characteristics method properly validates:

  • Service UUID existence on the device
  • Write characteristic UUID presence in the service
  • Notify characteristic UUID presence in the service

All error cases raise descriptive BleakError exceptions.


97-142: Verify whether notify characteristic should be optional for write-only use cases.

The current implementation requires notify_char_uuid (line 62, typed as str not Optional[str]) and always registers a notification handler (line 115). While _check_ble_characteristics validates its existence, this design assumes all BLE interactions need bidirectional communication.

For write-only BLE devices (e.g., controlling actuators without feedback), requiring a notify characteristic may be overly restrictive.

Consider whether notify_char_uuid should be optional:

notify_char_uuid: str | None = None

Then conditionally register the notification handler:

if self.notify_char_uuid:
    notify_handler = partial(
        _ble_notify_handler, data_queue=notify_queue)
    await client.start_notify(self.notify_char_uuid, notify_handler)
    self.logger.info(
        "Setting up notification handler for characteristic UUID: %s", 
        self.notify_char_uuid)

If bidirectional communication is always required by your use case, the current design is appropriate and you can disregard this suggestion.

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (5)

1-14: LGTM! Imports and exception definition are appropriate.

The imports cover terminal control (termios, tty), async operations (anyio), and the custom BleConsoleExit exception is correctly defined for graceful exit signaling.


16-23: LGTM! Console initialization and run flow are correct.

The BleConsole properly stores the client reference and orchestrates the raw terminal mode setup with the async run loop via the portal.


24-33: LGTM! Terminal raw mode handling is correct.

The setraw() context manager properly:

  • Saves original terminal attributes
  • Sets raw mode for direct character input
  • Restores attributes on exit
  • Clears the screen with ANSI escape sequences

35-44: LGTM! Async run flow correctly orchestrates bidirectional I/O.

The __run() method properly:

  • Opens the BLE stream connection
  • Prints the connection message with correct line endings for raw mode (\r\n)
  • Sets up concurrent tasks for bidirectional data flow
  • Handles BleConsoleExit gracefully with exception groups

46-51: LGTM! BLE-to-stdout streaming is correctly implemented.

The __ble_to_stdout() method properly receives data from the BLE stream and writes it to stdout with immediate flushing for responsive output.

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (5)

1-19: LGTM! Imports and documentation are well-organized.

The imports follow proper ordering (stdlib, third-party, local), and the class docstring clearly explains the BLE client's communication model using write and notify characteristics.


21-23: LGTM! Info method correctly delegates to driver.

The info() method properly proxies the call to the driver's info() method.


25-33: LGTM! Pexpect session management is correct.

The open() method properly manages the pexpect session lifecycle using stack.enter_context() and includes helpful documentation referencing pexpect docs.


35-44: LGTM! Pexpect adapter context manager is correctly implemented.

The pexpect() method properly wraps the PexpectAdapter in a context manager, passing the client instance appropriately.


46-65: LGTM! CLI commands are well-implemented and user-friendly.

The cli() method provides:

  • An info command that displays target information
  • A start_console command that launches the interactive BLE console
  • Clear user messaging about exit behavior (Ctrl-B × 3)

The Click integration follows the driver framework patterns.

Comment thread packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py
Copy link
Copy Markdown
Member

@mangelajo mangelajo left a comment

Choose a reason for hiding this comment

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

It looks great in general, just a couple of nit comments, the only important one now is the class name so we leave room for additional functionality in the driver down the line.



@dataclass(kw_only=True)
class Ble(Driver):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
class Ble(Driver):
class BleWriteNotifyStream(Driver):

What do you think about this name (same for the client BleWriteNotifyStreamClient )

A bit lenghty...

but would allow room for creating more types of ble drivers (for other purposes) over time in this package.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

True, I have changed the name for the driver and client to include WriteNotify.
Personally, I would prefer to have a generic BLE stream driver, which simply exposes all characteristics as stream. This driver could be used by other drivers as base to build things like this console. But because I am not sure if jumpstarter supports multistage drivers (driver depends on driver), I went with this impl yet.

Comment thread packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py
from jumpstarter.client import DriverClient


class BleConsoleExit(Exception):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would (in a later PR, or... also feel free to do it now, ..), move the pyserial console functionality here under "console.py" https://github.com/jumpstarter-dev/jumpstarter/tree/main/packages/jumpstarter/jumpstarter/streams and, then reuse it from both places pyserial, and your driver. It is the same code, we can maintain it just once :)

The console could receive a init parameter for the banner: BLE Console, vs Serial Console , vs... something else.

Happy to help with that later, no worries.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Indeed, removing the duplicate code is a good idea. I can work on this later

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am not sure how smart the AI (coderabbitai) is, but maybe it complains because the BLE driver does not have a EOF read function (has a pass).
In addition, the read blocks without a timeout via queue.get().
Hence, to receive an empty data set is not possible.

Should I remove the if and code block?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

don't worry, always take the comments from coderabbitai with a grain of salt, it is not perfect. If it's working for you keep it as it is, we can refactor code later.

async def __run(self):
async with self.ble_client.stream_async(method="connect") as stream:
try:
print(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

btw, you hace this message also on the CLI :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I know, this was on purpose, to inform the user how to exit the console.
To stay aligned with the serial console, I have removed it.

$ pip3 install --extra-index-url {{index_url}} jumpstarter-driver-ble
```

## Configuration
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You need to add a reference in:

https://github.com/jumpstarter-dev/jumpstarter/blob/main/docs/source/reference/package-apis/drivers/index.md?plain=1#L87

Also above in the that index.md under the "communication interfaces"

And also a symbolic link from docs/source/reference/package-apis/drivers/ble.md to this README.md

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@whati001 whati001 changed the title DRAFT: feat: ble driver: added basic ble stream driver and cli console feat: ble driver: added basic ble stream driver and cli console Nov 6, 2025
@whati001 whati001 changed the title feat: ble driver: added basic ble stream driver and cli console feat: ble write notify driver: added basic ble write and notify stream driver and cli console Nov 6, 2025
@mangelajo mangelajo self-requested a review November 6, 2025 13:14
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py (1)

1-1: Add test coverage for the BLE driver.

The TODO indicates missing tests. Consider adding tests for:

  • BLE connection establishment and disconnection
  • Characteristic validation (_check_ble_characteristics)
  • Stream send/receive operations
  • Error handling for missing services/characteristics

Would you like me to generate a test suite for the BLE driver, or open an issue to track this task?

packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (2)

13-18: Consider using the logging module for consistent logging.

The warning message uses print(), which bypasses the driver's logging configuration. Consider importing the logging module and using a logger instance, or pass the logger as a parameter to maintain consistent log formatting and levels.

Apply this diff:

 import asyncio
+import logging
 from contextlib import asynccontextmanager
 from dataclasses import dataclass
 from functools import partial
 def _ble_notify_handler(_sender: BleakGATTCharacteristic, data: bytearray, data_queue: asyncio.Queue):
     """Notification handler that puts received data into a queue."""
     try:
         data_queue.put_nowait(data)
     except asyncio.QueueFull:
-        print("Warning: Data queue is full, dropping message")
+        logging.getLogger(__name__).warning("Data queue is full, dropping message")

21-32: Simplify with dataclass decorator.

AsyncBleConfig is a simple data holder that would benefit from the @dataclass decorator for cleaner, more maintainable code.

Apply this diff:

+@dataclass
 class AsyncBleConfig():
-    def __init__(
-        self,
-        address: str,
-        service_uuid: str,
-        write_char_uuid: str,
-        notify_char_uuid: str,
-    ):
-        self.address = address
-        self.service_uuid = service_uuid
-        self.write_char_uuid = write_char_uuid
-        self.notify_char_uuid = notify_char_uuid
+    address: str
+    service_uuid: str
+    write_char_uuid: str
+    notify_char_uuid: str
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e3d05e and f6cb9dd.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • docs/source/reference/package-apis/drivers/ble.md (1 hunks)
  • docs/source/reference/package-apis/drivers/index.md (2 hunks)
  • packages/jumpstarter-all/pyproject.toml (1 hunks)
  • packages/jumpstarter-driver-ble/README.md (1 hunks)
  • packages/jumpstarter-driver-ble/examples/exporter.yaml (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (1 hunks)
  • packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver_test.py (1 hunks)
  • packages/jumpstarter-driver-ble/pyproject.toml (1 hunks)
  • pyproject.toml (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/source/reference/package-apis/drivers/ble.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/jumpstarter-driver-ble/README.md
  • packages/jumpstarter-driver-ble/examples/exporter.yaml
  • packages/jumpstarter-all/pyproject.toml
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-05T13:45:58.271Z
Learnt from: mangelajo
Repo: jumpstarter-dev/jumpstarter PR: 735
File: packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py:15-15
Timestamp: 2025-11-05T13:45:58.271Z
Learning: In packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py, pexpect is intentionally used as a transitive dependency through the jumpstarter-driver-pyserial package. The flashers package does not declare pexpect as a direct dependency because the pyserial driver package is intended to control the pexpect version.

Applied to files:

  • packages/jumpstarter-driver-ble/pyproject.toml
🧬 Code graph analysis (3)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (2)
packages/jumpstarter/jumpstarter/driver/base.py (1)
  • Driver (56-273)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (2)
  • info (21-23)
  • info (53-55)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (4)
packages/jumpstarter-driver-network/jumpstarter_driver_network/adapters/pexpect.py (1)
  • PexpectAdapter (11-19)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
  • BleConsole (16-64)
  • run (20-22)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
packages/jumpstarter/jumpstarter/client/decorators.py (2)
  • driver_click_group (13-54)
  • command (90-102)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (2)
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (3)
  • client (74-75)
  • receive (44-45)
  • send (41-42)
packages/jumpstarter/jumpstarter/client/base.py (2)
  • DriverClient (19-105)
  • call (42-52)
🪛 LanguageTool
docs/source/reference/package-apis/drivers/index.md

[uncategorized] ~33-~33: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ... (jumpstarter-driver-ble) - Bluetooth Low Energy communication * CAN (`jum...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: pytest-matrix (macos-15, 3.11)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.11)
  • GitHub Check: pytest-matrix (macos-15, 3.13)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.12)
  • GitHub Check: pytest-matrix (ubuntu-24.04, 3.13)
  • GitHub Check: e2e
🔇 Additional comments (6)
docs/source/reference/package-apis/drivers/index.md (1)

33-33: LGTM! Documentation addition is consistent and properly integrated.

The BLE driver entry is correctly added to the Communication Drivers section (line 33) and the toctree (line 88) in alphabetical order, following the same pattern as existing drivers. The changes integrate seamlessly with the existing documentation structure.

Note: The static analysis tool flagged "Bluetooth Low Energy" as requiring hyphenation, but this is the proper technical name and not being used as a compound adjective, so this is a false positive.

Also applies to: 88-88

pyproject.toml (1)

10-10: LGTM!

The workspace member addition is correctly placed in alphabetical order and follows the established pattern for driver packages.

packages/jumpstarter-driver-ble/pyproject.toml (1)

1-37: LGTM!

The package configuration follows repository conventions:

  • External dependencies (anyio, bleak) have appropriate version constraints
  • Internal jumpstarter dependencies are correctly unversioned per repository pattern
  • Build and test configuration aligns with other driver packages
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/driver.py (1)

55-147: LGTM!

The BLE driver implementation is well-structured:

  • Queue is correctly created per connection (line 110), avoiding stale data issues
  • Service and characteristic validation properly raises errors for missing UUIDs
  • Error handling appropriately catches and logs BleakError
  • Stream lifecycle management follows proper async context manager patterns
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/client.py (1)

1-65: LGTM!

The client implementation correctly:

  • Uses PexpectAdapter for pexpect session management
  • Provides both programmatic (open(), pexpect()) and CLI (cli()) interfaces
  • Properly integrates with the BleConsole for interactive sessions
packages/jumpstarter-driver-ble/jumpstarter_driver_ble/console.py (1)

1-64: LGTM!

The console implementation follows the established pattern from the serial console driver:

  • Terminal raw mode handling with proper restoration
  • Bidirectional I/O bridging using FileReadStream and FileWriteStream
  • Triple Ctrl-B exit mechanism
  • Clean exception handling with BleConsoleExit

Copy link
Copy Markdown
Member

@mangelajo mangelajo left a comment

Choose a reason for hiding this comment

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

This looks awesome! thanks @whati001 !!! :D

from jumpstarter.client import DriverClient


class BleConsoleExit(Exception):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

don't worry, always take the comments from coderabbitai with a grain of salt, it is not perfect. If it's working for you keep it as it is, we can refactor code later.

notify_queue: asyncio.Queue

async def send(self, data: bytes):
await self.client.write_gatt_char(self.config.write_char_uuid, data)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

just a question for later improvement,

Is there a limit in the number of bytes you can send via gatt?

May be above MTU may need chunking, or just refusing to send it (I guess we would get that via an exception anyway) :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

bleak does the chunking for us.

$ pip3 install --extra-index-url {{index_url}} jumpstarter-driver-ble
```

## Configuration
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@mangelajo mangelajo enabled auto-merge November 6, 2025 13:30
@mangelajo
Copy link
Copy Markdown
Member

Only some tests are missing.. we can add that later, but let's not forget :)

Is there any standard-ish ble device I can test this with?

@mangelajo mangelajo merged commit 72b3efa into jumpstarter-dev:main Nov 6, 2025
18 checks passed
@mangelajo mangelajo added this to the 0.8.0 milestone Nov 6, 2025
@whati001
Copy link
Copy Markdown
Contributor Author

whati001 commented Nov 7, 2025

Only some tests are missing.. we can add that later, but let's not forget :)

Is there any standard-ish ble device I can test this with?

True, I will check bleak if there is a way to emulate a ble device.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

No open projects

Development

Successfully merging this pull request may close these issues.

2 participants