Closed ekuleshov closed 5 months ago
This is by design.
I think developers should not care about how to response the read and write request because the sendRespone
method is not easy to use, look at the Android and iOS document, the requestId
, status
and offset
parameters made this method too complicated to be called, and the method is different on each platforms.
So I decide to send response by the plugin itself. if you want to change the characteristic's value, just call the writeCharacteristic
method, this method will change the characteristc's value no matter it's subscribed or not, that means if you call readCharacteristic in the central side, you will receive the last value set by the writeCharacteristic method or the initial value if you didn't call this method, in this way, developers don't need to call the sendRespone method any more.
The characteristcRead event is just to tell developers that the characteristic is read by centrals, you can ignore this event if you don't care about this event.
In short, you just need to call writeCharacteristic method if you want to change the characteristic's value, central can receive that value by read or notify.
=========== EDIT
if you want just change characteristic's value and don't want to notify other centrals, call the writeCharacteristic
without the central
parameter, if you want to notify others, you need to call this method with the central
parameter one or more times(you can only notify one central each time).
@yanshouwang I understand and appreciate the simplicity.
However there is an extra cost and additional complexity adds up for changing those char values from different data sources.
Take a battery service as an example. Instead of simply pulling the current battery level at the time battery char is read - we have to subscribe to battery level change and call writeCharacteristic()
with updated values even if the battery value char will be never be read.
And when you have a few of such services - maintaining subscriptions to their value updates and updating chars up from gets costly.
So, allowing to provide a value
callback on the GattCharacteristic
and GattDescriptor
would allow the app developer to provide custom data sources for all those values.
If I add the sendResponse method, instead of listen custom data source, you need to listen the characteristicRead
stream, and I really don't recommand developers to make this themself.
But if you really want to call the writeCharacteristic
just when the characteristic is read, as the stream can't block the response, I think maybe I can provide a PeripheralManagerInterceptor
mixin example to intercept the read event and do the writeCharacteristic at that time.
==== Edit Another way is make the characteristicRead stream to a callback so developers can intercept the read response, need to consider how to implement this with minimum change.
...if you really want to call the
writeCharacteristic
...
That is not what I'm suggesting. Right now chars and descriptors are created like this, even if there are no values available:
GattCharacteristic(
uuid: _charUuid,
properties: [ GattCharacteristicProperty.read, GattCharacteristicProperty.notify ],
value: Uint8List.fromList([]), // <--- char value
descriptors: [
GattDescriptor(uuid: _charUuid, value: Uint8List.fromList([])) // <--- descriptor value
],
);
You could add an optional value provider parameters, so it would allow to hook up any custom values
GattCharacteristic(
uuid: _charUuid,
properties: [ GattCharacteristicProperty.read, GattCharacteristicProperty.notify ],
provider: () async { // <--- char value provider
...this (potentially async) code will be called to get value when char is read
},
...
Another way is make the characteristicRead stream to a callback so developers can intercept the read response, need to consider how to implement this with minimum change.
That may work too. Internally you already transforming/truncating value before passing it to central.
Currently we have to listen like this
PeripheralManager.instance.characteristicRead.listen(_onCharRead);
Perhaps you could add another method that would return the same read stream but allow to provide custom values:
PeripheralManager.instance.characteristicReadMapped((GattCharacteristicReadEventArgs args) {
return <mapped value>;
}).listen(_onCharRead);
@ekuleshov You can do this by override the GattCharacteristic
class.
Just add bluetooth_low_energy_platform_interface
dependency, and extend the MyGattCharacteristic
class and override the value property, then you can create your own GattCharacteristic.
@yanshouwang but isn't MyGattCharacteristic being instantiated by a various platform-specific implementations?
Also, having to depend in bluetooth_low_energy_platform_interface
is not ideal for a regular plugin consumer app and going to be fragile and likely break when platform interface changes.
@yanshouwang but isn't MyGattCharacteristic being instantiated by a various platform-specific implementations?
Also, having to depend in
bluetooth_low_energy_platform_interface
is not ideal for a regular plugin consumer app and going to be fragile and likely break when platform interface changes.
The MyGattCharacteristic
doesn't have platform-specific implementations when used with PeripheralManager, When you create the GattCharacteristic, the factory constructor returns a new MyGattCharacteristic
instance, so you can safely extends this class on the peripheral side.
Anyone can depend the platform_interface plugin or the platform plugin without concern, you can even provide you own PeripheralManager implementation in this way. The api is stable(no breaking changes) until the main version changed.
Take a battery service as an example. Instead of simply pulling the current battery level at the time battery char is read - we have to subscribe to battery level change and call
writeCharacteristic()
with updated values even if the battery value char will be never be read.And when you have a few of such services - maintaining subscriptions to their value updates and updating chars up from gets costly.
There is another thing to be noticed, if you read the battery level just when the central read, it will spend extra time to read the value from the battery plugin as this is an async function, so it's better to store the battery value directly in the characteristic itself, so the PeripheralManager can get the battery level and send response immediately.
Anyway, I can expose the MyGattCharacteristic
from the platform_interface so you can extend it without depend on the platform_interface, I hide this class just because I don't want the characteristic's value to be modified directly, it should be read and write by the PeripheralManager class.
I also don't really want to modify values. I would prefer to be able to specify a data provider.
I'm avare of the overhead of pulling char values. Though some use cases are harder to implement another way. E.g. think of a service that returns a counter how many times it been read, or a service that returns a random number for every call.
I don't want to make breaking changes for this, In the current version, the read/write value is just stored in the characteristic itself as intended, what you want will break current API.
Also you can look at Apple's document about the descriptor value, there even doesn't have a descriptor read or write callback, the read and write descriptor response is handled by system, we don't even kown when the descriptor is read or write, we just maintain the descriptor's value when it's changed.
I don't want to make breaking changes for this, In the current version, the read/write value is just stored in the characteristic itself as intended, what you want will break current API.
U understand about the breaking changes, though making a required property optional and adding additional optional properties is not a breaking change.
Also I see it mentioned there that you can call respond
and provide a value in response to didReceiveRead
call on CBPeripheralManagerDelegate
.
Here are a few examples using that delegate API:
I don't want to make breaking changes for this, In the current version, the read/write value is just stored in the characteristic itself as intended, what you want will break current API.
U understand about the breaking changes, though making a required property optional and adding additional optional properties is not a breaking change.
Also I see it mentioned there that you can call
respond
and provide a value in response todidReceiveRead
call onCBPeripheralManagerDelegate
.Here are a few examples using that delegate API:
It's not so easy for me to do that...
Obviously we can respond characteristcs read and write requests, but what I mean is that we can't resond descriptors read and write requests on iOS platform, I want to keep the characteristic's read/write API the same as the descriptor's.
It's not just add something, It's a mechanism issue
Understood. Hope you will consider adding support for this in the future.
This issue is stale because it has been open for 30 days with no activity.
This issue was closed because it has been inactive for 14 days since being marked as stale.
New PeripheralManager
API is designed with interface-6.0.0-dev.16
The new API contains GATTCharacteristic.mutable()
and GATTCharacteristic.immutable
factory methods, characteristicReadRequested
and characteristicWriteRequested
events and corresponding respond
method.
I think the new API can resolve this issue.
The 6.0.0-dev.0 has released.
New
PeripheralManager
API is designed with interface-6.0.0-dev.16The new API contains
GATTCharacteristic.mutable()
andGATTCharacteristic.immutable
factory methods,characteristicReadRequested
andcharacteristicWriteRequested
events and correspondingrespond
method.I think the new API can resolve this issue.
I'm struggling with converting my 5.x code to the new 6.x APIs.
I have a service/char that receives commands and the app need to respond to another service/char with the dynamically created data for a received command.
I can't figure out how to send a response to a different service when processing a PeripheralManager.characteristicWriteRequested()
event.
Also the PeripheralManager.getState()
method is not listed in the 6.x migration notes.
I'm struggling with converting my 5.x code to the new 6.x APIs.
I have a service/char that receives commands and the app need to respond to another service/char with the dynamically created data for a received command.
I can't figure out how to send a response to a different service when processing a
PeripheralManager.characteristicWriteRequested()
event.Also the
PeripheralManager.getState()
method is not listed in the 6.x migration notes.
You can't respond if the service is not read or written by remote devices. You must respond to a request.
The getState method just moved to state field.
You can't respond if the service is not read or written by remote devices. You must respond to a request.
In 5.x API I simply used the PeripheralManager.writeCharacteristic()
to send/notify on a different GATT char using the central
instance from the incoming "write" request.
The getState method just moved to state field.
I know. Yet it is not in the migration notes.
You can't respond if the service is not read or written by remote devices. You must respond to a request.
In 5.x API I simply used the
PeripheralManager.writeCharacteristic()
to send/notify on a different GATT char using thecentral
instance from the incoming "write" request.The getState method just moved to state field.
I know. Yet it is not in the migration notes.
notifyCharacteristic
method to notify central device the characteristic changed when the central is notifying. And you must respond the read or write request when received a read or write request.I'll add that to the migration doc later
You can use the
notifyCharacteristic
method to notify central device the characteristic changed when the central is notifying. And you must respond the read or write request when received a read or write request.
Respond to read/write request how? The requesting device is getting UNKNOWN_GATT_ERROR 241 with 6.x peripheral.
Respond to read/write request how? The requesting device is getting UNKNOWN_GATT_ERROR 241 with 6.x peripheral.
Use the respondReadReuqestWithValue or the respondWriteRequest method. There is a sample code in the migration doc. And you can run the example to see how it works
The
PeripheralManager.characteristicRead
stream spawnsGattCharacteristicReadEventArgs
events when connected "central" device is sending a read request to a certain characteristic.But I can't figure out how to return an app-specific data in response to that read request.
There is
PeripheralManager.writeCharacteristic()
method which sends an updated characteristic value to one or more subscribed centrals, using a notification or indication. But that requires additional orchestration, e.g. have central subscribe for notifications and use some command from central (e.g. a write event) to initiate transmission.It looks like the
value
is set in theGattCharacteristic
at the timeGattService.characteristics
are created and then gatt service is registered usingPeripheralManager.instance.addService()
, but it is unclear how to changevalue
in those chars because theGattCharacteristic
has no setter forvalue
, but even then it is too late to updatevalue
in those chars at the timecharacteristicRead
event is triggered.Perhaps you could add a
value
setter to theGattCharacteristic
or better allow to specify a callback there that would allow to pull the up to date value from any custom source.