jonas-schievink / rubble

(going to be a) BLE stack for embedded Rust
BSD Zero Clause License
398 stars 56 forks source link

Implement (G)ATT on top of L2CAP #29

Open jonas-schievink opened 5 years ago

jonas-schievink commented 5 years ago

GATT is not it's own protocol, but means Generic Attribute Profile (even though the spec sometimes calls it Generic Attribute Protocol - I'm not making this shit up!). We have to implement whatever parts of ATT are needed by GATT.

jonas-schievink commented 5 years ago

A hard-coded primary service is now enumerated correctly. Next steps:

jonas-schievink commented 5 years ago

Removing milestone since service enumeration with one hard-coded service now works.

fmckeogh commented 5 years ago

I've been using NimBLE for a few days, and think implementing a similar pattern for notifications would be a good idea, I'm going to start work on this over the next couple days and see where I get.

NimBLE ble_gap.h

NimBLE ble_gatt.h

tl8roy commented 4 years ago

As promised may moons ago, here is an example layout that I think will work (hopefully this job is still relevant). I am not 100% certain of the actual parameters required, nor how they fit into the existing code. Hopefully you can make adjustments as required.

It also doesn't take into account Server vs Client, but I think Client is a subset of the traits and probably not much different.


struct Profile {}

trait GATTProfile {
    fn get_uuid() -> UUID;
    fn set_uuid(uuid: UUID);
    fn get_profile_type() -> ProfileType;
    fn get_profile_type(profie_type: ProfileType);
    fn add_services(attr: GATTService);
    fn get_services(uuid: UUID) -> & GATTService;
    fn get_services(service_type: Option<ServiceType>) -> Vec<& GATTService>;
}

#[GATTService("UUID","ServiceType")]
struct Service {}

trait GATTService {
    fn get_uuid() -> UUID;
    fn set_uuid(uuid: UUID);
    fn get_service_type() -> ServiceType;
    fn get_service_type(profie_type: ServiceType);
    fn add_characteristic(attr: GATTCharacteristic);
    fn get_characteristic(uuid: UUID) -> & GATTCharacteristic;
    fn get_characteristic() -> Vec<& GATTCharacteristic>;
}

#[GATTCharacteristic("UUID","Descriptor")]
struct Characteristic {
    value: [u8]
}

trait GATTCharacteristic {
    fn get_uuid() -> UUID;
    fn set_uuid(uuid: UUID);
    fn get_descriptor() -> String;
    fn set_descriptor(descriptor: String);
    fn get_value() -> [u8];
    fn set_value(value: [u8]);   
    fn notify(func: Option<FnOnce(value: [u8])>); 
    fn indicate(func: Option<FnOnce(value: [u8])>); 
}```

I can't say that I can help with the implementation, but I am happy to review any decisions.
jonas-schievink commented 4 years ago

@tl8roy Sorry, I'm not entirely sure what to make of that – we can't allocate memory, for example, and if the attributes are supposed to auto-implement the traits then they would need quite a bit more info to do so. For example the service needs to know all characteristics that are present at compile time to avoid allocations, but macros cannot access implemented traits or any values really.

The reason why I arrived at the approach described in https://github.com/jonas-schievink/rubble/issues/72 is that I don't think it will be very easy (if at all possible) to make procedural macros do the job in all cases here.

tl8roy commented 4 years ago

Your completely correct. I should have reread everything before posting. Let me have a think and see if I can abuse macros/types some more.