part of flutter_blue_plus;
final Guid cccdUuid = Guid("00002902-0000-1000-8000-00805f9b34fb");
class BluetoothCharacteristic {
final DeviceIdentifier remoteId;
final Guid serviceUuid;
final Guid? secondaryServiceUuid;
final Guid characteristicUuid;
BluetoothCharacteristic({
required this.remoteId,
required this.serviceUuid,
this.secondaryServiceUuid,
required this.characteristicUuid,
});
BluetoothCharacteristic.fromProto(BmBluetoothCharacteristic p)
: remoteId = p.remoteId,
serviceUuid = p.serviceUuid,
secondaryServiceUuid = p.secondaryServiceUuid != null ? p.secondaryServiceUuid! : null,
characteristicUuid = p.characteristicUuid;
Guid get uuid => characteristicUuid;
BluetoothDevice get device => BluetoothDevice(remoteId: remoteId);
CharacteristicProperties get properties {
return _bmchr != null ? CharacteristicProperties.fromProto(_bmchr!.properties) : CharacteristicProperties();
}
List<BluetoothDescriptor> get descriptors {
return _bmchr != null ? _bmchr!.descriptors.map((d) => BluetoothDescriptor.fromProto(d)).toList() : [];
}
List<int> get lastValue {
String key = "$serviceUuid:$characteristicUuid";
return FlutterBluePlus._lastChrs[remoteId]?[key] ?? [];
}
Stream<List<int>> get lastValueStream => FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnCharacteristicReceived" || m.method == "OnCharacteristicWritten")
.map((m) => m.arguments)
.map((args) => BmCharacteristicData.fromMap(args))
.where((p) => p.remoteId == remoteId)
.where((p) => p.serviceUuid == serviceUuid)
.where((p) => p.characteristicUuid == characteristicUuid)
.where((p) => p.success == true)
.map((c) => c.value)
.newStreamWithInitialValue(lastValue);
Stream<List<int>> get onValueReceived => FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnCharacteristicReceived")
.map((m) => m.arguments)
.map((args) => BmCharacteristicData.fromMap(args))
.where((p) => p.remoteId == remoteId)
.where((p) => p.serviceUuid == serviceUuid)
.where((p) => p.characteristicUuid == characteristicUuid)
.where((p) => p.success == true)
.map((c) => c.value);
bool get isNotifying {
var cccd = descriptors._firstWhereOrNull((d) => d.descriptorUuid == cccdUuid);
if (cccd == null) {
return false;
}
var hasNotify = cccd.lastValue.isNotEmpty && (cccd.lastValue[0] & 0x01) > 0;
var hasIndicate = cccd.lastValue.isNotEmpty && (cccd.lastValue[0] & 0x02) > 0;
return hasNotify || hasIndicate;
}
Future<List<int>> read({int timeout = 15}) async {
if (device.isDisconnected) {
throw FlutterBluePlusException(
ErrorPlatform.fbp, "readCharacteristic", FbpErrorCode.deviceIsDisconnected.index, "device is not connected");
}
_Mutex mtx = _MutexFactory.getMutexForKey("global");
await mtx.take();
List<int> responseValue = [];
try {
var request = BmReadCharacteristicRequest(
remoteId: remoteId,
characteristicUuid: characteristicUuid,
serviceUuid: serviceUuid,
secondaryServiceUuid: null,
);
var responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnCharacteristicReceived")
.map((m) => m.arguments)
.map((args) => BmCharacteristicData.fromMap(args))
.where((p) => p.remoteId == request.remoteId)
.where((p) => p.serviceUuid == request.serviceUuid)
.where((p) => p.characteristicUuid == request.characteristicUuid);
Future<BmCharacteristicData> futureResponse = responseStream.first;
await FlutterBluePlus._invokeMethod('readCharacteristic', request.toMap());
BmCharacteristicData response = await futureResponse
.fbpEnsureAdapterIsOn("readCharacteristic")
.fbpEnsureDeviceIsConnected(device, "readCharacteristic")
.fbpTimeout(timeout, "readCharacteristic");
if (!response.success) {
throw FlutterBluePlusException(_nativeError, "readCharacteristic", response.errorCode, response.errorString);
}
responseValue = response.value;
} finally {
mtx.give();
}
return responseValue;
}
Future<void> write(List<int> value,
{bool withoutResponse = false, bool allowLongWrite = false, int timeout = 15}) async {
if (withoutResponse && allowLongWrite) {
throw ArgumentError("cannot longWrite withoutResponse, not allowed on iOS or Android");
}
if (device.isDisconnected) {
throw FlutterBluePlusException(
ErrorPlatform.fbp, "writeCharacteristic", FbpErrorCode.deviceIsDisconnected.index, "device is not connected");
}
_Mutex mtx = _MutexFactory.getMutexForKey("global");
await mtx.take();
try {
final writeType = withoutResponse ? BmWriteType.withoutResponse : BmWriteType.withResponse;
var request = BmWriteCharacteristicRequest(
remoteId: remoteId,
characteristicUuid: characteristicUuid,
serviceUuid: serviceUuid,
secondaryServiceUuid: null,
writeType: writeType,
allowLongWrite: allowLongWrite,
value: value,
);
var responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnCharacteristicWritten")
.map((m) => m.arguments)
.map((args) => BmCharacteristicData.fromMap(args))
.where((p) => p.remoteId == request.remoteId)
.where((p) => p.serviceUuid == request.serviceUuid)
.where((p) => p.characteristicUuid == request.characteristicUuid);
Future<BmCharacteristicData> futureResponse = responseStream.first;
await FlutterBluePlus._invokeMethod('writeCharacteristic', request.toMap());
BmCharacteristicData response = await futureResponse
.fbpEnsureAdapterIsOn("writeCharacteristic")
.fbpEnsureDeviceIsConnected(device, "writeCharacteristic")
.fbpTimeout(timeout, "writeCharacteristic");
if (!response.success) {
throw FlutterBluePlusException(_nativeError, "writeCharacteristic", response.errorCode, response.errorString);
}
return Future.value();
} finally {
mtx.give();
}
}
Future<bool> setNotifyValue(bool notify, {int timeout = 15, bool forceIndications = false}) async {
if (device.isDisconnected) {
throw FlutterBluePlusException(
ErrorPlatform.fbp, "setNotifyValue", FbpErrorCode.deviceIsDisconnected.index, "device is not connected");
}
if (Platform.isMacOS || Platform.isIOS) {
assert(forceIndications == false, "iOS & macOS do not support forcing indications");
}
_Mutex mtx = _MutexFactory.getMutexForKey("global");
await mtx.take();
try {
var request = BmSetNotifyValueRequest(
remoteId: remoteId,
serviceUuid: serviceUuid,
secondaryServiceUuid: null,
characteristicUuid: characteristicUuid,
forceIndications: forceIndications,
enable: notify,
);
Stream<BmDescriptorData> responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnDescriptorWritten")
.map((m) => m.arguments)
.map((args) => BmDescriptorData.fromMap(args))
.where((p) => p.remoteId == request.remoteId)
.where((p) => p.serviceUuid == request.serviceUuid)
.where((p) => p.characteristicUuid == request.characteristicUuid)
.where((p) => p.descriptorUuid == cccdUuid);
Future<BmDescriptorData> futureResponse = responseStream.first;
bool hasCCCD = await FlutterBluePlus._invokeMethod('setNotifyValue', request.toMap());
if (hasCCCD) {
BmDescriptorData response = await futureResponse
.fbpEnsureAdapterIsOn("setNotifyValue")
.fbpEnsureDeviceIsConnected(device, "setNotifyValue")
.fbpTimeout(timeout, "setNotifyValue");
if (!response.success) {
throw FlutterBluePlusException(_nativeError, "setNotifyValue", response.errorCode, response.errorString);
}
}
} finally {
mtx.give();
}
return true;
}
BmBluetoothCharacteristic? get _bmchr {
if (FlutterBluePlus._knownServices[remoteId] != null) {
for (var s in FlutterBluePlus._knownServices[remoteId]!.services) {
if (s.serviceUuid == serviceUuid) {
for (var c in s.characteristics) {
if (c.characteristicUuid == uuid) {
return c;
}
}
}
}
}
return null;
}
@override
String toString() {
return 'BluetoothCharacteristic{'
'remoteId: $remoteId, '
'serviceUuid: $serviceUuid, '
'secondaryServiceUuid: $secondaryServiceUuid, '
'characteristicUuid: $characteristicUuid, '
'descriptors: $descriptors, '
'properties: $properties, '
'value: $lastValue'
'}';
}
@Deprecated('Use remoteId instead')
DeviceIdentifier get deviceId => remoteId;
@Deprecated('Use lastValueStream instead')
Stream<List<int>> get value => lastValueStream;
@Deprecated('Use onValueReceived instead')
Stream<List<int>> get onValueChangedStream => onValueReceived;
}
class CharacteristicProperties {
final bool broadcast;
final bool read;
final bool writeWithoutResponse;
final bool write;
final bool notify;
final bool indicate;
final bool authenticatedSignedWrites;
final bool extendedProperties;
final bool notifyEncryptionRequired;
final bool indicateEncryptionRequired;
const CharacteristicProperties(
{this.broadcast = false,
this.read = false,
this.writeWithoutResponse = false,
this.write = false,
this.notify = false,
this.indicate = false,
this.authenticatedSignedWrites = false,
this.extendedProperties = false,
this.notifyEncryptionRequired = false,
this.indicateEncryptionRequired = false});
CharacteristicProperties.fromProto(BmCharacteristicProperties p)
: broadcast = p.broadcast,
read = p.read,
writeWithoutResponse = p.writeWithoutResponse,
write = p.write,
notify = p.notify,
indicate = p.indicate,
authenticatedSignedWrites = p.authenticatedSignedWrites,
extendedProperties = p.extendedProperties,
notifyEncryptionRequired = p.notifyEncryptionRequired,
indicateEncryptionRequired = p.indicateEncryptionRequired;
@override
String toString() {
return 'CharacteristicProperties{'
'broadcast: $broadcast, '
'read: $read, '
'writeWithoutResponse: $writeWithoutResponse, '
'write: $write, '
'notify: $notify, '
'indicate: $indicate, '
'authenticatedSignedWrites: $authenticatedSignedWrites, '
'extendedProperties: $extendedProperties, '
'notifyEncryptionRequired: $notifyEncryptionRequired, '
'indicateEncryptionRequired: $indicateEncryptionRequired'
'}';
}
}