StanfordSpezi / SpeziBluetooth

Connect and communicate with Bluetooth devices.
https://swiftpackageindex.com/StanfordSpezi/SpeziBluetooth/documentation/spezibluetooth
MIT License
11 stars 2 forks source link

Support Descriptor Interactions #11

Open Supereg opened 6 months ago

Supereg commented 6 months ago

Problem

Currently, it is only possible to read all Characteristic Descriptors upon discovery. However, it is not possible to read/write descriptors within the lifetime of a connected device.

Solution

Provide access to read/write descriptors within the BluetoothPeripheral actor and find a respective translation for the DSL language (e.g., a @Characteristic equivalent?).

Additional context

No response

Code of Conduct

Supereg commented 1 month ago

Maybe we could implement Descriptor support as another property wrapper usable in BluetoothService types with a syntax similar to the code snippet below.

public struct DeviceInformationService: BluetoothService, Sendable {
    public static let id: BTUUID = "180A"

    @Characteristic(id: "2A29")
    public var manufacturerName: String?

    @Descriptor(id: "2901", for: \Self.$manufacturerName)
    public var manufacturerNameDescription: String?

    // ...

    public init() {}
}

The property wrapper @Descriptor works similar to the @Characteristic with additionally requiring a KeyPath to the CharacteristicAccessor binding of the characteristic it refers to. This design would however require a runtime check that the KeyPath refers to a characteristic declared on the same Service type (in this case DeviceInformationService) as we can't express this relationship through the Swift type system.

The code snippet below roughly sketches the public interface of the Descriptor property wrapper. Note that we would likely want shorthand initializers for the common descriptor UUIDs (see CBDescriptor).

@propertyWrapper
public struct Descriptor<Value>: Sendable {
    public var wrappedValue: Value? {
        fatalError("Needs implementation")
    }

    public var projectedValue: DescriptorAccessor<Value> {
        fatalError("Needs implementation") // read and write accesses for the descriptor
    }

    public init<S: BluetoothService, V>(wrappedValue: Value? = nil, id: BTUUID, for characteristic: KeyPath<S, CharacteristicAccessor<V>>) {
        fatalError("Needs implementation")
    }
}