变量
Dart
static bool _initialized = false; //初始化标志
/// native platform channel - 与原生平台通信的通道
static final MethodChannel _methodChannel = const MethodChannel('flutter_blue_plus/methods');
/// a broadcast stream version of the MethodChannel
// ignore: close_sinks
//接收原平台主动发来的事件
static final StreamController<MethodCall> _methodStream = StreamController.broadcast();
// always keep track of these device variables
//设备状态缓存
static final Map<DeviceIdentifier, BmConnectionStateResponse> _connectionStates = {};
//服务列表
static final Map<DeviceIdentifier, BmDiscoverServicesResult> _knownServices = {};
//配对状态
static final Map<DeviceIdentifier, BmBondStateResponse> _bondStates = {};
//保存MTU的值
static final Map<DeviceIdentifier, BmMtuChangedResponse> _mtuValues = {};
//保存设备名称
static final Map<DeviceIdentifier, String> _platformNames = {}; //系统名称
static final Map<DeviceIdentifier, String> _advNames = {}; //广播名称
//最后的读写特征
static final Map<DeviceIdentifier, Map<String, List<int>>> _lastChrs = {};
static final Map<DeviceIdentifier, Map<String, List<int>>> _lastDescs = {};
//订阅管理
static final Map<DeviceIdentifier, List<StreamSubscription>> _deviceSubscriptions = {};
static final Map<DeviceIdentifier, List<StreamSubscription>> _delayedSubscriptions = {};
static final List<StreamSubscription> _scanSubscriptions = [];
//自动连接集合
static final Set<DeviceIdentifier> _autoConnect = {};
//扫描相关
/// stream used for the isScanning public api
static final _isScanning = _StreamControllerReEmit<bool>(initialValue: false);
/// stream used for the scanResults public api
static final _scanResults = _StreamControllerReEmit<List<ScanResult>>(initialValue: []);
/// buffers the scan results
static _BufferStream<BmScanResponse>? _scanBuffer;
/// the subscription to the merged scan results stream
static StreamSubscription<BmScanResponse?>? _scanSubscription;
/// timeout for scanning that can be cancelled by stopScan
static Timer? _scanTimeout;
/// the last known adapter state - 适配器状态
static BmAdapterStateEnum? _adapterStateNow;
/// FlutterBluePlus log level - 日志配置
static LogLevel _logLevel = LogLevel.debug;
static bool _logColor = true;
get方法
Dart
//获取当前日志级别
static LogLevel get logLevel => _logLevel;
/// Checks whether the hardware supports Bluetooth - 硬件是否支持蓝牙
static Future<bool> get isSupported async => await _invokeMethod('isSupported');
/// The current adapter state - 当前蓝牙适配器状态
static BluetoothAdapterState get adapterStateNow =>
_adapterStateNow != null ? _bmToAdapterState(_adapterStateNow!) : BluetoothAdapterState.unknown;
/// Return the friendly Bluetooth name of the local Bluetooth adapter - 适配器名称
static Future<String> get adapterName async => await _invokeMethod('getAdapterName');
/// returns whether we are scanning as a stream - 扫描状态
static Stream<bool> get isScanning => _isScanning.stream;
/// are we scanning right now? - 当前时刻是否正在扫描
static bool get isScanningNow => _isScanning.latestValue;
/// the most recent scan results - 最近一次扫描的结果列表
static List<ScanResult> get lastScanResults => _scanResults.latestValue;
/// a stream of scan results - 作为流返回扫描结果
//重新监听时会重新发送之前的结果
/// - if you re-listen to the stream it re-emits the previous results
//包含从扫描开始以来的所有结果
/// - the list contains all the results since the scan started
/// - the returned stream is never closed.返回的流永远不会关闭
static Stream<List<ScanResult>> get scanResults => _scanResults.stream;
/// This is the same as scanResults, except:
/// - it *does not* re-emit previous results after scanning stops.
static Stream<List<ScanResult>> get onScanResults {
if (isScanningNow) {
return _scanResults.stream;//正常返回扫描结果流
} else {
// skip previous results & push empty list
//跳过之前的缓存结果,先发送空列表[],然后才发送新结果
return _scanResults.stream.skip(1).newStreamWithInitialValue([]);
}
}
疑问点
Dart
1.蓝牙适配器是什么,作用是什么?
答:蓝牙适配器是指设备上负责管理蓝牙功能的硬件模块及其对应的软件接口。
核心作用:管理蓝牙开关状态,设备识别与命名,扫描周围设备,管理连接。
访问蓝牙设备事件流
Dart
/// Get access to all device event streams
//蓝牙设备事件流的统一访问入口
static final BluetoothEvents events = BluetoothEvents();
开启蓝牙的警告框
Dart
//当蓝牙不可用时,系统是否自动弹出体系用户开启蓝牙的警告框
/// Set configurable options - 只能在IOS和MacOS系统上使用
/// - [showPowerAlert] Whether to show the power alert (iOS & MacOS only). i.e. CBCentralManagerOptionShowPowerAlertKey
/// To set this option you must call this method before any other method in this package.
/// See: https://developer.apple.com/documentation/corebluetooth/cbcentralmanageroptionshowpoweralertkey
/// This option has no effect on Android.
static Future<void> setOptions({
bool showPowerAlert = true,
}) async {
await _invokeMethod('setOptions', {"show_power_alert": showPowerAlert});
}
打开蓝牙
Dart
/// Turn on Bluetooth (Android only),只安卓设备有效
static Future<void> turnOn({int timeout = 60}) async {
//设置响应监听
var responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnTurnOnResponse")
.map((m) => m.arguments)
.map((args) => BmTurnOnResponse.fromMap(args));
// Start listening now, before invokeMethod, to ensure we don't miss the response
//提前开始监听
Future<BmTurnOnResponse> futureResponse = responseStream.first;
// invoke - 调用原生方法
bool changed = await _invokeMethod('turnOn');
// only wait if bluetooth was off 只在蓝牙原本关闭时执行
if (changed) {
// wait for response - 等待用户选择
BmTurnOnResponse response = await futureResponse.fbpTimeout(timeout, "turnOn");
// check response 检查授权结果
if (response.userAccepted == false) {
throw FlutterBluePlusException(ErrorPlatform.fbp, "turnOn", FbpErrorCode.userRejected.index, "user rejected");
}
// wait for adapter to turn on - 等待蓝牙完全开启
await adapterState.where((s) => s == BluetoothAdapterState.on).first.fbpTimeout(timeout, "turnOn");
}
}
交互时序图
Dart
用户代码 Flutter层 原生层(Android) 系统对话框
| | | |
|---turnOn()---------->| | |
| |---监听响应(提前)-------->| |
| | | |
| |---_invokeMethod-------->| |
| | ('turnOn') | |
| | |---显示对话框---------->|
| | | |
| | |<--用户点击"允许"-------|
| |<--OnTurnOnResponse------| |
| | (userAccepted=true) | |
| | | |
| |<--adapterState=on-------| |
|<--(完成)-------------| | |
异步生成器
Dart
/// Gets the current state of the Bluetooth module
//提供蓝牙适配器状态的持续监控流
static Stream<BluetoothAdapterState> get adapterState async* {
// get current state if needed 获取初始状态
if (_adapterStateNow == null) {
var result = await _invokeMethod('getAdapterState');
var value = BmBluetoothAdapterState.fromMap(result).adapterState;
// update _adapterStateNow if it is still null after the await
//双重检查,防止并发问题
if (_adapterStateNow == null) {
_adapterStateNow = value;
}
}
//创建状态变化监听流
yield* FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnAdapterStateChanged")
.map((m) => m.arguments)
.map((args) => BmBluetoothAdapterState.fromMap(args))
.map((s) => _bmToAdapterState(s.adapterState))
.newStreamWithInitialValue(_bmToAdapterState(_adapterStateNow!));
}
连接的蓝牙设备列表
Dart
/// Retrieve a list of devices currently connected to your app
//获取当前应用已经连接的蓝牙设备列表(当前软件连接的BLE设备)
static List<BluetoothDevice> get connectedDevices {
var copy = Map.from(_connectionStates); //复制连接状态映射
//删除未连接的设备
copy.removeWhere((key, value) => value.connectionState == BmConnectionStateEnum.disconnected);
//转换未设备对象列表
return copy.values.map((v) => BluetoothDevice(remoteId: v.remoteId)).toList();
}
系统中所有已经连接的蓝牙设备
Dart
/// Retrieve a list of devices currently connected to the system
/// - The list includes devices connected to by *any* app
/// - You must still call device.connect() to connect them to *your app*
//返回系统中所有已连接的蓝牙设备列表,不仅限于当前应用连接的设备
//整个手机连接的BLE设备
static Future<List<BluetoothDevice>> get systemDevices async {
var result = await _invokeMethod('getSystemDevices');//调用原生方法
var r = BmDevicesList.fromMap(result); //解析结果
for (BmBluetoothDevice device in r.devices) { //缓存设备名称
if (device.platformName != null) {
_platformNames[device.remoteId] = device.platformName!;
}
}
return r.devices.map((d) => BluetoothDevice.fromProto(d)).toList(); //转换为设备对象
}
获取已配对的蓝牙设备列表
Dart
/// Retrieve a list of bonded devices (Android only)
//获取已配对的蓝牙设备列表(连接BLE的设备+连接经典蓝牙的设备)
static Future<List<BluetoothDevice>> get bondedDevices async {
var result = await _invokeMethod('getBondedDevices');//调用原生方法
var r = BmDevicesList.fromMap(result); //解析结果
for (BmBluetoothDevice device in r.devices) { //缓存设备名称
if (device.platformName != null) {
_platformNames[device.remoteId] = device.platformName!;
}
}
return r.devices.map((d) => BluetoothDevice.fromProto(d)).toList(); //返回设备列表
}
扫描方法
Dart
/// Start a scan, and return a stream of results 开始扫描,并返回结果流
/// Note: scan filters use an "or" behavior. i.e. if you set `withServices` & `withNames` we
//扫描过滤器使用'或'行为
/// return all the advertisments that match any of the specified services *or* any of the specified names.
/// - [withServices] filter by advertised services
/// - [withRemoteIds] filter for known remoteIds (iOS: 128-bit guid, android: 48-bit mac address)
/// - [withNames] filter by advertised names (exact match)
/// - [withKeywords] filter by advertised names (matches any substring)
/// - [withMsd] filter by manfacture specific data
/// - [withServiceData] filter by service data
/// - [timeout] calls stopScan after a specified duration
/// - [removeIfGone] if true, remove devices after they've stopped advertising for X duration
/// - [continuousUpdates] If `true`, we continually update 'lastSeen' & 'rssi' by processing
//如果为 `true`,我们通过处理重复的广播数据来持续更新 'lastSeen' 和 'rssi'。这会消耗更多电量。通常不建议使用此选项。
/// duplicate advertisements. This takes more power. You typically should not use this option.
/// - [continuousDivisor] Useful to help performance. If divisor is 3, then two-thirds of advertisements are
/// ignored, and one-third are processed. This reduces main-thread usage caused by the platform channel.
/// The scan counting is per-device so you always get the 1st advertisement from each device.
/// If divisor is 1, all advertisements are returned. This argument only matters for `continuousUpdates` mode.
/// - [oneByOne] if `true`, we will stream every advertistment one by one, possibly including duplicates.
/// If `false`, we deduplicate the advertisements, and return a list of devices.
/// - [androidScanMode] choose the android scan mode to use when scanning
/// - [androidUsesFineLocation] request `ACCESS_FINE_LOCATION` permission at runtime
static Future<void> startScan({
//过滤参数
List<Guid> withServices = const [], //按服务UUID过滤
List<String> withRemoteIds = const [], //按设备ID过滤(IOS:GUID,Android:MAC地址)
List<String> withNames = const [], //按设备名称过滤(精准匹配)
List<String> withKeywords = const [],//按关键词过滤(子串匹配)
List<MsdFilter> withMsd = const [], //按服务商特定数据过滤
List<ServiceDataFilter> withServiceData = const [], //按服务数据过滤
//行为参数
Duration? timeout, //自动停止扫描的时长
Duration? removeIfGone,//设备停止广播X时长后移除
bool continuousUpdates = false,//是否持续更新重复广告
int continuousDivisor = 1,//性能优化:每N条广告处理1条
bool oneByOne = false,//true:逐个返回设备;false:批量返回列表
//Android专用参数
AndroidScanMode androidScanMode = AndroidScanMode.lowLatency,//扫描模式
bool androidUsesFineLocation = false, //是否需要精确定位权限
}) async {
// check args - 参数合法性检查
//如果设置了 removeIfGone(不为 null),则必须同时设置 continuousUpdates = true
assert(removeIfGone == null || continuousUpdates, "removeIfGone requires continuousUpdates");
//不能同时使用 removeIfGone 和 oneByOne = true
assert(removeIfGone == null || !oneByOne, "removeIfGone is not compatible with oneByOne");
//continuousDivisor 必须是大于等于 1 的整数
assert(continuousDivisor >= 1, "divisor must be >= 1");
// check filters - 检查是否设置了除withKeywords以外的其他过滤器
bool hasOtherFilter = withServices.isNotEmpty ||
withRemoteIds.isNotEmpty ||
withNames.isNotEmpty ||
withMsd.isNotEmpty ||
withServiceData.isNotEmpty;
// Note: `withKeywords` is not compatible with other filters on android
//注意:`withKeywords` 在 Android 上与其他过滤器不兼容
// because it is implemented in custom fbp code, not android code
// 因为它是在自定义的 fbp 代码中实现的,而不是在 Android 原生代码中
assert(!(Platform.isAndroid && withKeywords.isNotEmpty && hasOtherFilter),
"withKeywords is not compatible with other filters on Android");
// only allow a single task to call
// startScan or stopScan at a time
_Mutex mtx = _MutexFactory.getMutexForKey("scan"); //互斥锁控制
await mtx.take();
try {
// already scanning?
if (_isScanning.latestValue == true) {
// stop existing scan
await _stopScan();
} else {
// push to stream
_isScanning.add(true);
}
//创造扫描配置对象
var settings = BmScanSettings(
withServices: withServices,
withRemoteIds: withRemoteIds,
withNames: withNames,
withKeywords: withKeywords,
withMsd: withMsd.map((d) => d._bm).toList(),
withServiceData: withServiceData.map((d) => d._bm).toList(),
continuousUpdates: continuousUpdates,
continuousDivisor: continuousDivisor,
androidScanMode: androidScanMode.value,
androidUsesFineLocation: androidUsesFineLocation);
//创建响应流监听
Stream<BmScanResponse> responseStream = FlutterBluePlus._methodStream.stream
.where((m) => m.method == "OnScanResponse") //只接收扫描响应
.map((m) => m.arguments)
.map((args) => BmScanResponse.fromMap(args));
// Start listening now, before invokeMethod, so we do not miss any results
_scanBuffer = _BufferStream.listen(responseStream);//开始监听
// invoke platform method - 调用原生扫描
await _invokeMethod('startScan', settings.toMap()).onError((e, s) => _stopScan(invokePlatform: false));
// check every 250ms for gone devices? - 处理设备过期
late Stream<BmScanResponse?> outputStream = removeIfGone != null
? _mergeStreams([_scanBuffer!.stream, Stream.periodic(Duration(milliseconds: 250))])
: _scanBuffer!.stream;
// start by pushing an empty array
_scanResults.add([]);
List<ScanResult> output = [];
// listen & push to `scanResults` stream - 处理扫描结果
_scanSubscription = outputStream.listen((BmScanResponse? response) {
if (response == null) {
// if null, this is just a periodic update to remove old results
//定期检查:移除过期设备
if (output._removeWhere((elm) => DateTime.now().difference(elm.timeStamp) > removeIfGone!)) {
_scanResults.add(List.from(output)); // push to stream
}
} else {
// failure? 错误处理
if (response.success == false) {
var e = FlutterBluePlusException(_nativeError, "scan", response.errorCode, response.errorString);
_scanResults.addError(e);//向流中添加错误
_stopScan(invokePlatform: false);//停止扫描,不调用原生层
}
// iterate through advertisements
for (BmScanAdvertisement bm in response.advertisements) {//遍历广播数据
// cache platform name - 缓存设备名称
if (bm.platformName != null) {
_platformNames[bm.remoteId] = bm.platformName!;
}
// cache advertised name - 缓存广播名称
if (bm.advName != null) {
_advNames[bm.remoteId] = bm.advName!;
}
// convert - 转换为ScanResult的对象
ScanResult sr = ScanResult.fromProto(bm);
if (oneByOne) {
// push single item
_scanResults.add([sr]); //每次只推送一个设备的列表
} else {
// add result to output
output.addOrUpdate(sr);//更新设备列表
}
}
// push entire list 批量推送
if (!oneByOne) {
_scanResults.add(List.from(output));
}
}
});
// Start timer *after* stream is being listened to, to make sure the
// timeout does not fire before _scanSubscription is set
if (timeout != null) {
_scanTimeout = Timer(timeout, stopScan); //设置扫描超时定时器
}
} finally {
mtx.give(); //解锁
}
}
停止扫描
Dart
/// Stops a scan for Bluetooth Low Energy devices
//给外部调用的API,负责安全的停止扫描
static Future<void> stopScan() async {
_Mutex mtx = _MutexFactory.getMutexForKey("scan"); //互斥锁
await mtx.take();
try {
if(isScanningNow) { //只有在扫描中才停止
await _stopScan();
} else if (_logLevel.index >= LogLevel.info.index) {
print("[FBP] stopScan: already stopped");
}
} finally {
mtx.give(); //释放锁
}
}
/// for internal use
//内部实现的停止扫描方法
static Future<void> _stopScan({bool invokePlatform = true}) async {
_scanBuffer?.close(); //关闭缓冲区
_scanSubscription?.cancel();//取消扫描订阅
_scanTimeout?.cancel(); //取消超时定时器
_isScanning.add(false); //更新状态为未扫描
for (var subscription in _scanSubscriptions) {
subscription.cancel();//取消所有其他订阅
}
if (invokePlatform) {
await _invokeMethod('stopScan');//通知原生层停止
}
}
清理步骤
Dart
┌─────────────────────────────────────────────────────────┐
│ _stopScan() 清理流程 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. _scanBuffer?.close() │
│ └─ 关闭结果缓冲区,停止接收新数据 │
│ │
│ 2. _scanSubscription?.cancel() │
│ └─ 取消扫描结果的订阅,停止处理 │
│ │
│ 3. _scanTimeout?.cancel() │
│ └─ 取消超时定时器,防止超时后重复停止 │
│ │
│ 4. _isScanning.add(false) │
│ └─ 更新状态,通知所有监听者扫描已停止 │
│ │
│ 5. 遍历取消 _scanSubscriptions │
│ └─ 清理所有其他相关订阅(如各设备的连接状态) │
│ │
│ 6. _invokeMethod('stopScan') │
│ └─ 通知原生层停止扫描(可选) │
│ │
└─────────────────────────────────────────────────────────┘
管理流订阅的生命周期
Dart
/// Register a subscription to be canceled when scanning is complete.
/// This function simplifies cleanup, to prevent creating duplicate stream subscriptions.
/// - this is an optional convenience function
/// - prevents accidentally creating duplicate subscriptions before each scan
//注册一个订阅,在扫描完成时自动取消,这个函数简化了清理工作,防止创建重复的流订阅
//这是一个可选的便捷函数,防止在每次扫描前意外创建重复订阅
static void cancelWhenScanComplete(StreamSubscription subscription) {
FlutterBluePlus._scanSubscriptions.add(subscription);
}
设置FlutterBluePlus插件的日志级别
Dart
/// Sets the internal FlutterBlue log level
//设置FlutterBluePlus内部的日志级别
static Future<void> setLogLevel(LogLevel level, {color = true}) async {
_logLevel = level; //保存日志级别到内存
_logColor = color; //保存是否使用彩色日志
await _invokeMethod('setLogLevel', level.index);//通知原生层
}
获取蓝牙物理层(PHY)支持信息
Dart
/// Request Bluetooth PHY support 请求蓝牙PHY支持信息
static Future<PhySupport> getPhySupport() async {
// check android 过滤出安卓设备
if (Platform.isAndroid == false) {
throw FlutterBluePlusException(
ErrorPlatform.fbp, "getPhySupport", FbpErrorCode.androidOnly.index, "android-only");
}
//调用原生方法并转换结果
return await _invokeMethod('getPhySupport').then((args) => PhySupport.fromMap(args));
初始化方法
Dart
static Future<dynamic> _initFlutterBluePlus() async {
if (_initialized) {
return;
}
_initialized = true;
// set platform method handler 设置平台方法调用处理器
_methodChannel.setMethodCallHandler(_methodCallHandler);
// hot restart 处理热重启情况
if ((await _methodChannel.invokeMethod('flutterHotRestart')) != 0) {
await Future.delayed(Duration(milliseconds: 50));
while ((await _methodChannel.invokeMethod('connectedCount')) != 0) {
await Future.delayed(Duration(milliseconds: 50));
}
}
}
方法调用处理器
Dart
static Future<dynamic> _methodCallHandler(MethodCall call) async {
// log result
if (logLevel == LogLevel.verbose) { //日志级别设置为详细模式
String func = '[[ ${call.method} ]]';
String result = call.arguments.toString();
func = _logColor ? _black(func) : func;
result = _logColor ? _brown(result) : result;
print("[FBP] $func result: $result");
}
// android only
if (call.method == "OnDetachedFromEngine") {
_stopScan(invokePlatform: false);
}
// keep track of adapter states
if (call.method == "OnAdapterStateChanged") {
BmBluetoothAdapterState r = BmBluetoothAdapterState.fromMap(call.arguments);
_adapterStateNow = r.adapterState;
if (isScanningNow && r.adapterState != BmAdapterStateEnum.on) {
_stopScan(invokePlatform: false);
}
if (r.adapterState == BmAdapterStateEnum.on) {
for (DeviceIdentifier d in _autoConnect) {
BluetoothDevice(remoteId: d).connect(autoConnect: true, mtu: null).onError((e, s) {
if (logLevel != LogLevel.none) {
print("[FBP] [AutoConnect] connection failed: $e");
}
});
}
}
}
// keep track of connection states
if (call.method == "OnConnectionStateChanged") {
var r = BmConnectionStateResponse.fromMap(call.arguments);
_connectionStates[r.remoteId] = r;
if (r.connectionState == BmConnectionStateEnum.disconnected) {
// push to mtu stream, if needed
if (_mtuValues.containsKey(r.remoteId)) {
var resp = BmMtuChangedResponse(remoteId: r.remoteId, mtu: 23);
_methodStream.add(MethodCall("OnMtuChanged", resp.toMap()));
}
// clear mtu
_mtuValues.remove(r.remoteId);
// clear lastDescs (resets 'isNotifying')
_lastDescs.remove(r.remoteId);
// clear lastChrs (api consistency)
_lastChrs.remove(r.remoteId);
// cancel & delete subscriptions
_deviceSubscriptions[r.remoteId]?.forEach((s) => s.cancel());
_deviceSubscriptions.remove(r.remoteId);
// Note: to make FBP easier to use, we do not clear `knownServices`,
// otherwise `servicesList` would be more annoying to use. We also
// do not clear `bondState`, for faster performance.
// autoconnect
if (Platform.isAndroid == false) {
if (_autoConnect.contains(r.remoteId)) {
if (_adapterStateNow == BmAdapterStateEnum.on) {
var d = BluetoothDevice(remoteId: r.remoteId);
d.connect(autoConnect: true, mtu: null).onError((e, s) {
if (logLevel != LogLevel.none) {
print("[FBP] [AutoConnect] connection failed: $e");
}
});
}
}
}
}
}
// keep track of device name
if (call.method == "OnNameChanged") {
var device = BmNameChanged.fromMap(call.arguments);
if (Platform.isMacOS || Platform.isIOS) {
// iOS & macOS internally use the name changed callback for the platform name
_platformNames[device.remoteId] = device.name;
}
}
// keep track of services resets
if (call.method == "OnServicesReset") {
var r = BmBluetoothDevice.fromMap(call.arguments);
_knownServices.remove(r.remoteId);
}
// keep track of bond state
if (call.method == "OnBondStateChanged") {
var r = BmBondStateResponse.fromMap(call.arguments);
_bondStates[r.remoteId] = r;
}
// keep track of services
if (call.method == "OnDiscoveredServices") {
var r = BmDiscoverServicesResult.fromMap(call.arguments);
if (r.success == true) {
_knownServices[r.remoteId] = r;
}
}
// keep track of mtu values
if (call.method == "OnMtuChanged") {
var r = BmMtuChangedResponse.fromMap(call.arguments);
if (r.success == true) {
_mtuValues[r.remoteId] = r;
}
}
// keep track of characteristic values
if (call.method == "OnCharacteristicReceived" || call.method == "OnCharacteristicWritten") {
var r = BmCharacteristicData.fromMap(call.arguments);
if (r.success == true) {
_lastChrs[r.remoteId] ??= {};
_lastChrs[r.remoteId]!["${r.serviceUuid}:${r.characteristicUuid}"] = r.value;
}
}
// keep track of descriptor values
if (call.method == "OnDescriptorRead" || call.method == "OnDescriptorWritten") {
var r = BmDescriptorData.fromMap(call.arguments);
if (r.success == true) {
_lastDescs[r.remoteId] ??= {};
_lastDescs[r.remoteId]!["${r.serviceUuid}:${r.characteristicUuid}:${r.descriptorUuid}"] = r.value;
}
}
_methodStream.add(call);
// cancel delayed subscriptions
if (call.method == "OnConnectionStateChanged") {
if (_delayedSubscriptions.isNotEmpty) {
var r = BmConnectionStateResponse.fromMap(call.arguments);
if (r.connectionState == BmConnectionStateEnum.disconnected) {
var remoteId = r.remoteId;
// use delayed to update the stream before we cancel it
Future.delayed(Duration.zero).then((_) {
_delayedSubscriptions[remoteId]?.forEach((s) => s.cancel()); // cancel
_delayedSubscriptions.remove(remoteId); // delete
});
}
}
}
}