Skip to content

Conversation

@ikifar2012
Copy link
Owner

@ikifar2012 ikifar2012 commented Aug 14, 2025

This pull request updates the version of the neosmartblue.py library and improves the receive_data method in the device.py file to provide more robust handling and parsing of Bluetooth notification data. The most important changes are grouped below:

Version Update:

  • Bumped the package version from 0.1.3 to 0.1.4 in pyproject.toml to reflect new changes.

Bluetooth Notification Handling Improvements:

  • Updated the receive_data method in device.py to use BleakGATTCharacteristic for the notification handler's sender argument, and improved the notification handler to handle and log errors gracefully, check data length before parsing, and print more informative debug messages. [1] [2]
  • Changed the return documentation and behavior of receive_data to clarify that it returns a parsed status object (from parse_status_data) instead of raw bytes.

Summary by CodeRabbit

  • Refactor
    • Device responses are now parsed into structured status objects instead of raw bytes for easier consumption.
  • Bug Fixes
    • Bluetooth notification handling is more robust: short/malformed payloads, timeouts and parsing errors are handled gracefully and logged.
  • Chores
    • Package version bumped to 0.1.4.

@coderabbitai
Copy link

coderabbitai bot commented Aug 14, 2025

Walkthrough

Bumps package version to 0.1.4 and refactors BlueLinkDevice.receive_data to add typing, return parsed status dicts (or None), perform payload length checks, log sender UUIDs, and add exception handling while still using start_notify/_TX_UUID.

Changes

Cohort / File(s) Change Summary
Version metadata
pyproject.toml
Increment project version from "0.1.3" to "0.1.4" in [project].
Device notification parsing and typing
src/neosmartblue/py/device.py
Update BlueLinkDevice.receive_data signature to async def receive_data(self, timeout: float = 5.0) -> Optional[Dict[str, Any]]; add module logger and typing imports; replace prints with logging; introduce data_event: asyncio.Event() and data: Optional[Dict[str, Any]]; update nested notification_handler(sender: BleakGATTCharacteristic, received_data: bytearray) to convert to bytes, log sender UUID, require payload length >= 9, slice bytes[4:9], parse via parse_status_data, store parsed status and set event, catch and log exceptions; use start_notify(_TX_UUID, notification_handler) and await data_event with timeout, stop notifications in finally, and return parsed dict or None on timeout/no notification.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant BlueLinkDevice
    participant BleakClient
    participant Device

    Caller->>BlueLinkDevice: receive_data(timeout)
    BlueLinkDevice->>BleakClient: start_notify(_TX_UUID, notification_handler)
    Device-->>BleakClient: Notification(bytearray)
    BleakClient-->>BlueLinkDevice: notification_handler(sender, data)
    BlueLinkDevice->>BlueLinkDevice: convert to bytes, check len>=9
    BlueLinkDevice->>BlueLinkDevice: status = parse_status_data(bytes[4:9])
    BlueLinkDevice->>BlueLinkDevice: set data_event with parsed status
    BlueLinkDevice-->>Caller: return parsed status dict (or None on timeout)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

I twitch my nose at a new small score,
Version hopped up to 0.1.4. 🐇
Five bytes plucked, parsed with care,
I log each UUID with flair.
A gentle catch — onward we go!

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pin-bleak-1.1.0

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e74d1f6 and e19d0a0.

📒 Files selected for processing (2)
  • pyproject.toml (1 hunks)
  • src/neosmartblue/py/device.py (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/neosmartblue/py/device.py (2)
src/neosmartblue/py/parse_status.py (1)
  • parse_status_data (1-50)
src/neosmartblue/py/scan.py (2)
  • detection_callback (18-42)
  • scan_for_devices (6-48)
🔇 Additional comments (2)
pyproject.toml (1)

3-3: Version bump aligned with API change.

0.1.4 makes sense given the behavioral change in receive_data. No issues spotted with the bump itself.

src/neosmartblue/py/device.py (1)

3-3: Bleak import is acceptable — no change required

Verified: pyproject.toml pins "bleak >=1.0.1" and PyPI shows latest 1.1.0, so importing BleakGATTCharacteristic from bleak.backends.characteristic is consistent with the project's Bleak v1.x dependency.

  • Location to note:
    • src/neosmartblue/py/device.py — from bleak.backends.characteristic import BleakGATTCharacteristic (used in notification_handler signature around lines ~105–109)

Short guidance: keep as-is given the dependency constraint. If you intend to support older bleak versions (<1.0) or allow looser dependency ranges, adopt the TYPE_CHECKING + Any fallback pattern suggested in the original comment.

Copy link

@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

♻️ Duplicate comments (3)
src/neosmartblue/py/device.py (3)

95-103: Return type + docstring clarification: LGTM; aligns with API behavior change

Nice job annotating the return type and clarifying the docstring to reflect the parsed status dict (or None) behavior. This addresses the previously raised concern about the breaking change.


131-139: Wrap start_notify in the try/finally and log timeout explicitly

Minor structure tweak: placing start_notify inside the try pairs the setup/teardown more cleanly and allows a consistent timeout log.

-        await self.client.start_notify(_TX_UUID, notification_handler)
-        try:
+        try:
+            await self.client.start_notify(_TX_UUID, notification_handler)
             try:
                 await asyncio.wait_for(data_event.wait(), timeout=timeout)
             except asyncio.TimeoutError:
-                # No data within timeout; return None
-                pass
+                logger.debug("No notification received within %.1fs", timeout)
             return data

2-3: Consistent logging across the class (replace prints)

Follow-up from earlier feedback: consider replacing print calls in connect, disconnect, and send_command with logger calls for consistency with receive_data and for better library ergonomics.

🧹 Nitpick comments (5)
src/neosmartblue/py/device.py (5)

2-3: Adopt a module-level logger for consistency and efficiency

You already import logging; prefer defining a module-level logger once instead of creating it per-call inside receive_data.

Apply within this function (see next comment for removal), and add at module top (outside selected range):

# top-level, once per module
logger = logging.getLogger(__name__)

104-107: Avoid per-call getLogger; use the module-level logger

Create the logger at module level and remove the per-call instantiation.

-        logger = logging.getLogger(__name__)
         data: Optional[Dict[str, Any]] = None
         data_event = asyncio.Event()

111-127: Reduce log noise; prefer debug level for high-frequency notifications and use clearer naming

Notifications can be chatty; log at debug to avoid flooding info-level logs. Also, rename “printable” to “payload” for clarity.

-                printable = bytes(received_data)
-                logger.info(
+                payload = bytes(received_data)
+                logger.debug(
                     "Notification from characteristic %s: %s",
                     getattr(sender, 'uuid', sender),
-                    printable.hex().upper(),
+                    payload.hex().upper(),
                 )
-                if len(printable) >= 9:
-                    status_payload = printable[4:9]
+                if len(payload) >= 9:
+                    status_payload = payload[4:9]
                     status = parse_status_data(status_payload)
                     data = status
-                    logger.info("Parsed status: %s", status)
+                    logger.debug("Parsed status: %s", status)
                     data_event.set()
                 else:
-                    logger.info(
+                    logger.debug(
                         "Received data too short to parse status payload (len=%d)",
-                        len(printable),
+                        len(payload),
                     )

140-143: Nit: include characteristic in stop-notify failure log

Helps correlate failures in multi-characteristic scenarios.

-            except Exception:  # pragma: no cover - defensive cleanup
-                logger.exception("Failed to stop notifications")
+            except Exception:  # pragma: no cover - defensive cleanup
+                logger.exception("Failed to stop notifications for %s", _TX_UUID)

5-5: Optional: guard BleakGATTCharacteristic import to avoid runtime dependency on internal path

bleak.backends.characteristic can shift across Bleak versions. Since it’s only used for type annotation, consider guarding with TYPE_CHECKING and a forward reference to avoid runtime import errors if Bleak internals change.

Example (outside selected range):

from typing import TYPE_CHECKING
if TYPE_CHECKING:
    from bleak.backends.characteristic import BleakGATTCharacteristic

# then annotate as:
def notification_handler(sender: "BleakGATTCharacteristic", received_data: bytearray):
    ...
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 90c60fe and afae6f5.

📒 Files selected for processing (1)
  • src/neosmartblue/py/device.py (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/neosmartblue/py/device.py (2)
src/neosmartblue/py/parse_status.py (1)
  • parse_status_data (1-50)
src/neosmartblue/py/scan.py (2)
  • detection_callback (18-42)
  • scan_for_devices (6-48)
🔇 Additional comments (2)
src/neosmartblue/py/device.py (2)

108-108: More precise callback signature: good

Typing the notification handler’s sender as BleakGATTCharacteristic matches Bleak’s callback signature and improves readability.


128-129: Robust exception logging inside handler: LGTM

Catching and logging exceptions with logger.exception ensures tracebacks are preserved without crashing the callback.

@ikifar2012 ikifar2012 merged commit 4de89c8 into main Aug 15, 2025
2 checks passed
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.

2 participants