Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GATT server support for Rust #454

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

marshallpierce
Copy link
Contributor

With a whole lot of support to get there:

  • Device::add_service(), with some complexity around how to represent the mutable Python Service being used under the covers
  • AttributePermission, CharacteristicProperty, and other supporting GATT-related stuff
  • Ability to handle the different data types used by various Python Characteristic subclasses
  • Support for running tests in the context of a background rootcanal process
  • TryToPy and TryFromPy traits to help make the lifecycle more clear for structs that have a Python equivalent, but are also meaningful standalone
  • Add example with a minimal battery service
  • Rearrange known service IDs to make it possible to refer to a service UUID in a meaningful way
  • Make Address hold its own state to avoid propagating PyResult so much, and adopt the new TryToPy and TryFromPy traits
  • AdvertisingDataValue trait so users don't have to remember as many ADU encoding details at a call site

@marshallpierce marshallpierce force-pushed the mp/gatt-server branch 2 times, most recently from 2a6aedd to e823482 Compare March 25, 2024 23:31
Copy link
Contributor Author

@marshallpierce marshallpierce left a comment

Choose a reason for hiding this comment

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

Not sure how much motivation I have to fix the build with Python 3.8 and 3.9 given that 3.10 is almost 3 years old...

attribute: Attribute,
value: Optional[bytes] = None,
force: bool = False,
) -> None:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added type annotations based on poking around at the callers, but since of course the type info isn't pervasive I'm not sure these are right

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is good. We want to eventually have everything type, incrementally.


# Dev tools
file-header = { version = "0.1.2", optional = true }
globset = { version = "0.4.13", optional = true }

# CLI
anyhow = { version = "1.0.71", optional = true }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No longer optional due to wanting a "don't care" error for callbacks, etc, that wasn't the Python-specific PyError


let mut builder = AdvertisementDataBuilder::new();
builder.append(CommonDataType::CompleteLocalName, "Bumble Battery")?;
builder.append(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is fairly manual, but given the use cases for Bumble that might want to do somewhat exciting things vis-a-vis spec compliance, I'm not sure it makes sense to make a more OS-style API that doesn't require you to assemble the advertisement and prevents you from doing things wrong...?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Having a set of reusable AdvertismementData pre-set builders would be nice, for users who what just basic common stuff. That's something I've considered adding to the Python lib but haven't found the time to yet.

///
/// This is a stop-gap until rootcanal's build is improved to the point that a rootcanal-sys crate,
/// and using rootcanal as a library rather than a separate process, becomes feasible.
pub(crate) async fn run_with_rootcanal<O, F>(closure: F) -> anyhow::Result<()>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@hchataing forgive me for these rootcanal crimes. Let me know if there is a less bad way to do this while I try to get the rootcanal build working on more systems.

/// Execute `closure` with a simple directory lock held.
///
/// Assumes that directory creation is atomic, which it is on most OS's.
async fn with_dir_lock<T>(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needed since tests run concurrently, and I don't want to sacrifice that as integration-level ones are rather slow

use pyo3::types::PyBytes;
use pyo3::{intern, PyAny, PyResult, Python};

impl TryToPy for AttributeUuid {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not quite sure how it'll work out, but I'm experimenting with having Try[To|From]Py in the wrapper module distinct from the relatively pure Rust structs in crate::internal

Copy link
Collaborator

Choose a reason for hiding this comment

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

+1 this makes sense to me. If it causes confusion later, we can move things around.


// Needed for Python 3.8-3.9, in which the Semaphore object, when constructed, calls
// `get_event_loop`.
wrap_python_async(py, device_ctor)?
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Had to sprinkle this on Device and Peer invocations that end up instantiating an asyncio.Semaphore

@marshallpierce marshallpierce marked this pull request as ready for review March 26, 2024 14:00
rust/examples/battery_service.rs Outdated Show resolved Hide resolved
})
}

/// Returns the path to a rootcanal binary, downloading it from GitHub if necessary.

Choose a reason for hiding this comment

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

It seems a little odd to me that this is downloading artifacts at runtime, would it make sense to move this step into a build.rs and setting this up at build time?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't enjoy the current approach, but I don't see a great way of doing it with build.rs either that would work nicely with doing dirty tracking. I hope either way it's short lived, though -- with n additional hours I think I can get rootcanal building as a library, at which point a rootcanal-sys crate becomes reasonable, and this whole spawn-a-process thing can go away.

rust/src/internal/core.rs Show resolved Hide resolved
rust/src/wrapper/assigned_numbers/services.rs Show resolved Hide resolved
use pyo3::types::PyBytes;
use pyo3::{intern, PyAny, PyResult, Python};

impl TryToPy for AttributeUuid {
Copy link
Collaborator

Choose a reason for hiding this comment

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

+1 this makes sense to me. If it causes confusion later, we can move things around.

rust/examples/battery_service.rs Show resolved Hide resolved
rust/examples/battery_service.rs Outdated Show resolved Hide resolved
rust/examples/battery_service.rs Show resolved Hide resolved
rust/src/internal/core.rs Show resolved Hide resolved
rust/src/wrapper/hci.rs Outdated Show resolved Hide resolved
rust/src/wrapper/hci.rs Outdated Show resolved Hide resolved
rust/src/wrapper/mod.rs Outdated Show resolved Hide resolved
rust/pytests/rootcanal.rs Show resolved Hide resolved
rust/pytests/rootcanal.rs Show resolved Hide resolved
@whitevegagabriel
Copy link
Collaborator

Also, although we've not quite kept up-to-date with the Changelog up to this point, could we at least make note of these changes in the Changelog now, so they're there for the next release?

marshallpierce and others added 2 commits April 5, 2024 09:35
With a whole lot of support to get there:

- Device::add_service(), with some complexity around how to represent the mutable Python `Service` being used under the covers
- AttributePermission, CharacteristicProperty, and other supporting GATT-related stuff
- Ability to handle the different data types used by various Python `Characteristic` subclasses
- Support for running tests in the context of a background `rootcanal` process
- `TryToPy` and `TryFromPy` traits to help make the lifecycle more clear for structs that have a Python equivalent, but are also meaningful standalone
- Add example with a minimal battery service
- Rearrange known service IDs to make it possible to refer to a service UUID in a meaningful way
- Make Address hold its own state to avoid propagating PyResult so much, and adopt the new TryToPy and TryFromPy traits
- `AdvertisingDataValue` trait so users don't have to remember as many ADU encoding details at a call site
- Sprinkle in more wrap_python_async to work on Python 3.8 and 3.9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants