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

Add support for writing to descriptors #453

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

ened
Copy link
Contributor

@ened ened commented Nov 17, 2021

Extends the plugins to support writing to specific descriptors.
I have copied the approach from QualifiedCharacteristic to become a QualifiedDescriptor.

There are quite a few generated files (tests, protobuf) which are a bit tedious to handle. Appreciate if someone could point me to a developer docs page.

iOS implementation is still TBD.

@remonh87
Copy link
Contributor

thanks! so far it looks good

Copy link
Collaborator

@werediver werediver left a comment

Choose a reason for hiding this comment

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

Looks pretty good. Good job, @ened 👍

There is at least one "TODO" mark out there, so I consider this a work-in-progress for now.

)
}
}
}.first(CharOperationFailed(deviceId, "Writechar timed-out"))
Copy link
Collaborator

Choose a reason for hiding this comment

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

@remonh87 CharOperation* types are used multiple types in this method (and possibly in other places), while it's not exactly about characteristics, but rather about descriptors (which, admittedly, are associated with characteristics). Do you think it's fine or do you think a new set of types should be introduced?

I don't have a strong opinion on this, mainly because I'm not that familiar with the Android part. Just drawing your attention.

throw Failure.descriptorNotFound(qualifiedDescriptor)
}

guard let response = characteristic.service?.peripheral?.writeValue(value, for: descriptor)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@ened Is writing to a descriptor inherently a "without response" (without acknowledgement) operation? If so, then using -WithoutResponse is reasonable, but please clarify.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Writing to descriptors works via https://developer.apple.com/documentation/corebluetooth/cbperipheral/1519107-writevalue . There is no response per se, but the docs at https://developer.apple.com/documentation/corebluetooth/cbperipheraldelegate/1519062-peripheral state that a delegate method will be called once the write is completed. If it fails, the error is given as well. Do you think the plugin should implement that delegate and return a value only once that is done?

WithoutResponse still applies based on that logic, but I am wondering what the plugin should do.

Copy link
Collaborator

@werediver werediver Nov 29, 2021

Choose a reason for hiding this comment

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

Do you think the plugin should implement that delegate and return a value only once that is done?

If waiting for the events delivered to the delegate adds anything, it might be a potential improvement, but as of now, I'd say, this method should be implemented similar to

As long as they match, it should be enough.

@@ -70,6 +70,16 @@ message WriteCharacteristicInfo {
GenericFailure failure = 3;
}

message WriteDescriptorRequest {
DescriptorAddress descriptor = 1;
bytes value = 2;
Copy link
Collaborator

Choose a reason for hiding this comment

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

@ened Do descriptors always have 16-bit IDs and cannot have longer or shorter IDs?

source: hosted
path: "../reactive_ble_platform_interface"
relative: true
source: path
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure this should be committed.

CC @remonh87

@ened
Copy link
Contributor Author

ened commented Nov 23, 2021

@werediver @remonh87 944e2a9 is not strictly part of this MR, but was needed for compatibility when including this package on iOS. Should it be separate?

Copy link
Collaborator

@werediver werediver left a comment

Choose a reason for hiding this comment

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

Some comments regarding 944e2a9.

@werediver
Copy link
Collaborator

Some comments regarding 944e2a9.

I suppose it's fine to include those changes in this pull request, but please go through all the comments already left by me.

Aside from those comments, the commit history of this pull request is dirty: contains merge commits (better rebase your feature branches), doesn't seem well structured and includes commit messages mentioning work-in-progress (WIP). If you are not going to clean the history up at some point (through interactive rebase), the pull request should be squashed before merging (can be done at the moment of merging through GitHub interface). It's okay, just don't be surprised.

@ened
Copy link
Contributor Author

ened commented Nov 23, 2021

Some comments regarding 944e2a9.

I suppose it's fine to include those changes in this pull request, but please go through all the comments already left by me.

OK & yes will do.

Aside from those comments, the commit history of this pull request is dirty: contains merge commits (better rebase your feature branches), doesn't seem well structured and includes commit messages mentioning work-in-progress (WIP). If you are not going to clean the history up at some point (through interactive rebase), the pull request should be squashed before merging (can be done at the moment of merging through GitHub interface). It's okay, just don't be surprised.

Oh yes, I was fully expecting to squash all the commits before removing the Draft label.

Thank you for the reviews.

@remonh87
Copy link
Contributor

I can test the project setup on the m1 machine (I have one) if needed.

completion(.success(result))
} else {
do {
try central.discoverDescriptors(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@werediver @remonh87 would like your opinion if this is the correct approach. I think there is no need to discover all characteristics from the get go. It may also imply a API change to declare what to discover.

The code currently discovers on-demand but may change the library behavior from what the use expects?

Copy link
Collaborator

@werediver werediver Nov 29, 2021

Choose a reason for hiding this comment

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

Characteristic discovery works differently on iOS and Android. On iOS a client can specify a list of services and characteristics to discover when connecting to a device (the client is only allowed to interact with the discovered characteristics later on), while on Android no discovery is triggered beforehand (I think it's happening on the fly then, but not entirely sure; CC @remonh87).

In terms of design, a simple solution is to extend the existing discovery mechanism to also discover descriptors, as specified when connecting. That would be consistent and quite efficient.

Discovering descriptors one by one is definitely not as efficient (the more descriptors a client uses, the larger the overhead is). This is also not consistent with how the library works with characteristics (on iOS).

Copy link
Contributor Author

@ened ened Nov 29, 2021

Choose a reason for hiding this comment

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

@werediver got you regarding the differences - and will think how to extend the initial discovery. It does mean the existing iOS discovery code will get another layer (currently counts how many services are left to discover, this will be extended with the number of characteristics to explore).

@werediver
Copy link
Collaborator

werediver commented Nov 29, 2021

Reminding of my earlier question:

@ened Do descriptors always have 16-bit IDs and cannot have longer or shorter IDs?

CBDescriptor.id has type CBUUID and that thing can contain IDs of different length, typically 128, 32, or 16 bits, while strictly 16 bits is reserved in Protobuf structure.

I'd suggest to find a suitable example in existing Protobuf structures and reuse it.

  • Rework or clarify Protobuf descriptor ID format

@ened
Copy link
Contributor Author

ened commented Nov 29, 2021

Reminding of my earlier question:

@ened Do descriptors always have 16-bit IDs and cannot have longer or shorter IDs?

CBDescriptor.id has type CBUUID and that thing can contain IDs of different length, typically 128, 32, or 16 bits, while strictly 16 bits is reserved in Protobuf structure.

I'd suggest to find a suitable example in existing Protobuf structures and reuse it.

  • Rework or clarify Protobuf descriptor ID format

At the moment, the code defines DescriptorAddress like this:

message DescriptorAddress {
    string deviceId = 1;
    Uuid serviceUuid = 2;
    Uuid characteristicUuid = 3;
    Uuid descriptorUuid = 4;
}

and Uuid is defined as:

message Uuid {
    bytes data = 1;
}

So that looks good?

@werediver
Copy link
Collaborator

werediver commented Nov 29, 2021

At the moment, the code defines DescriptorAddress like this:

message DescriptorAddress {
    string deviceId = 1;
    Uuid serviceUuid = 2;
    Uuid characteristicUuid = 3;
    Uuid descriptorUuid = 4;
}

and Uuid is defined as:

message Uuid {
    bytes data = 1;
}

So that looks good?

Oh, sure, it looks good. I misread a piece of protobuf specification, sorry.

}

guard let peripheral = central.activePeripherals[descriptor.peripheralID],
let service = peripheral.services?.first(where: { service in

Choose a reason for hiding this comment

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

how will .first work with devices that provide several instances of the same service (UUID)? Same for several instances of the same characteristic UUID.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Devices can provide multiple services with the same UUID? I have to admit that's a part of the spec I do not know about!

Technically this May be a more fundamental problem in this package then, looking as QualifiedCharacteristic for example, a full lookup is done using the UUIDs but not via index?

Copy link
Collaborator

Choose a reason for hiding this comment

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

In case anyone wants to continue with this in the future. Have a look at #776 where the problem of multiple characteristics of services with the same id has been addressed. Something similar would be needed here.

@albydarned
Copy link

I need to be able to read a characteristic's user descriptor... Does this PR just include writes?

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.

6 participants