565 lines
20 KiB
Dart
Executable File
565 lines
20 KiB
Dart
Executable File
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:star_lock/app_settings/app_settings.dart';
|
|
|
|
import 'io_tool/io_model.dart';
|
|
import 'io_tool/io_tool.dart';
|
|
import 'io_tool/manager_event_bus.dart';
|
|
import 'reciver_data.dart';
|
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
|
|
|
//连接状态回调
|
|
typedef ConnectStateCallBack = Function(
|
|
BluetoothConnectionState connectionState);
|
|
typedef ScanDevicesCallBack = Function(List<ScanResult>);
|
|
|
|
class BlueManage {
|
|
|
|
factory BlueManage() => shareManager()!;
|
|
|
|
BlueManage._init() {
|
|
_initBlue();
|
|
}
|
|
final List<ScanResult> scanDevices = <ScanResult>[];
|
|
|
|
// 用来写入的服务id
|
|
final Guid _serviceIdConnect = Guid('fff0');
|
|
|
|
// 用来写入的服务id
|
|
final Guid _serviceIdWrite = Guid('0000FFF0-0000-1000-8000-00805F9B34FB');
|
|
|
|
// 用来订阅的特征id
|
|
final Guid _characteristicIdSubscription = Guid('fff1');
|
|
|
|
// 用来写入的特征id
|
|
final Guid _characteristicIdWrite = Guid('fff2');
|
|
|
|
// 监听发送事件
|
|
StreamSubscription<EventSendModel>? _sendStreamSubscription;
|
|
|
|
StreamSubscription<BluetoothConnectionState>? _connectionStateSubscription;
|
|
|
|
StreamSubscription<int>? _mtuSubscription;
|
|
int? _mtuSize = 20;
|
|
|
|
// 当前连接设备的名字
|
|
String connectDeviceName = '';
|
|
|
|
// 当前连接设备的mac地址
|
|
String connectDeviceMacAddress = '';
|
|
|
|
// 当前连接的设备
|
|
BluetoothDevice? bluetoothConnectDevice;
|
|
|
|
// 当前扫描到结果要连接设备
|
|
ScanResult? scanResult;
|
|
|
|
// 监听蓝牙连接状态
|
|
BluetoothConnectionState? bluetoothConnectionState =
|
|
BluetoothConnectionState.disconnected;
|
|
|
|
BluetoothAdapterState? _adapterState = BluetoothAdapterState.unknown;
|
|
StreamSubscription<BluetoothAdapterState>? _adapterStateStateSubscription;
|
|
|
|
// 听上报来的数据,参数来自前面扫描到的结果
|
|
List<int> allData = <int>[];
|
|
|
|
// 保存上一次的数据,用来判断是否收到重复的数据
|
|
List<int> lastTimeData = <int>[];
|
|
int? dataLen;
|
|
|
|
static BlueManage? _manager;
|
|
|
|
static BlueManage? shareManager() {
|
|
_manager ??= BlueManage._init();
|
|
// _manager!._initBlue();
|
|
return _manager;
|
|
}
|
|
|
|
BlueManage? get manager => shareManager();
|
|
|
|
void _initBlue() {
|
|
FlutterBluePlus.setLogLevel(LogLevel.error, color: true);
|
|
_initSendStreamSubscription();
|
|
_initAdapterStateStateSubscription();
|
|
}
|
|
|
|
void _initGetMtuSubscription() {
|
|
_mtuSubscription ??= bluetoothConnectDevice!.mtu.listen((int value) {
|
|
_mtuSize = value - 3;
|
|
AppLog.log('_mtuSizeValue:$value mtuSize:$_mtuSize');
|
|
});
|
|
}
|
|
|
|
void _initAdapterStateStateSubscription() {
|
|
_adapterStateStateSubscription ??=
|
|
FlutterBluePlus.adapterState.listen((BluetoothAdapterState state) {
|
|
_adapterState = state;
|
|
});
|
|
}
|
|
|
|
void _initListenConnectionState() {
|
|
_connectionStateSubscription?.cancel();
|
|
_connectionStateSubscription = null;
|
|
|
|
_connectionStateSubscription = bluetoothConnectDevice!.connectionState
|
|
.listen((BluetoothConnectionState state) async {
|
|
bluetoothConnectionState = state;
|
|
// AppLog.log("蓝牙连接状态:$state");
|
|
});
|
|
}
|
|
|
|
void _initSendStreamSubscription() {
|
|
_sendStreamSubscription ??= EventBusManager()
|
|
.eventBus!
|
|
.on<EventSendModel>()
|
|
.listen((EventSendModel model) {
|
|
if (model.sendChannel == DataChannel.ble) {
|
|
FlutterBluePlus.isSupported.then((bool isAvailable) async {
|
|
if (isAvailable) {
|
|
if (_adapterState == BluetoothAdapterState.on) {
|
|
// 蓝牙已开启,可以进行蓝牙操作
|
|
writeCharacteristicWithResponse(model.data);
|
|
} else {
|
|
try {} catch (e) {
|
|
AppLog.log('蓝牙打开失败');
|
|
}
|
|
}
|
|
} else {
|
|
AppLog.log('写入数据 蓝牙不可用,不能进行蓝牙操作');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 开始指定设备名称的扫描蓝牙设备
|
|
Future<void> startScanSingle(String deviceName, int timeout,
|
|
ScanDevicesCallBack scanDevicesCallBack) async {
|
|
final DateTime start = DateTime.now();
|
|
FlutterBluePlus.isSupported.then((bool isAvailable) async {
|
|
if (isAvailable) {
|
|
if (_adapterState == BluetoothAdapterState.on) {
|
|
try {
|
|
//android 扫描比较慢,取样只要 8 分之一
|
|
final int divisor = Platform.isAndroid ? 3 : 1;
|
|
FlutterBluePlus.startScan(
|
|
continuousDivisor: divisor,
|
|
continuousUpdates: true,
|
|
// withServiceData:[ServiceDataFilter()],
|
|
withKeywords: <String>[deviceName],
|
|
timeout: Duration(seconds: timeout));
|
|
final Completer<dynamic> completer = Completer<dynamic>();
|
|
final StreamSubscription<List<ScanResult>> subscription =
|
|
FlutterBluePlus.scanResults.listen((List<ScanResult> results) {
|
|
final bool isExit = results.any((ScanResult element) =>
|
|
(element.device.platformName == deviceName) ||
|
|
(element.advertisementData.advName == deviceName));
|
|
final int milliseconds = DateTime.now().millisecondsSinceEpoch -
|
|
start.millisecondsSinceEpoch;
|
|
AppLog.log(
|
|
'扫描到的设备数:${results.length} 是否查找到 $isExit 以查找$milliseconds毫秒');
|
|
if (isExit) {
|
|
for (final ScanResult scanResult in results) {
|
|
if (((scanResult.advertisementData.serviceUuids.isNotEmpty
|
|
? scanResult.advertisementData.serviceUuids[0]
|
|
: '')
|
|
.toString()
|
|
.contains('758824')) &&
|
|
(scanResult.rssi >= -100)) {
|
|
// 查询id相同的元素
|
|
final int knownDeviceIndex = scanDevices.indexWhere(
|
|
(ScanResult d) =>
|
|
(d.device.platformName ==
|
|
scanResult.device.platformName) ||
|
|
(d.advertisementData.advName ==
|
|
scanResult.advertisementData.advName));
|
|
// 不存在的时候返回-1
|
|
if (knownDeviceIndex >= 0) {
|
|
scanDevices[knownDeviceIndex] = scanResult;
|
|
} else {
|
|
scanDevices.add(scanResult);
|
|
}
|
|
}
|
|
}
|
|
|
|
completer.complete();
|
|
}
|
|
}, onError: (e) {
|
|
AppLog.log(
|
|
'扫描失败:$e',
|
|
);
|
|
});
|
|
FlutterBluePlus.cancelWhenScanComplete(subscription);
|
|
await completer.future;
|
|
scanDevicesCallBack(scanDevices);
|
|
subscription.cancel();
|
|
} catch (e) {
|
|
AppLog.log('扫描失败');
|
|
}
|
|
} else {
|
|
try {
|
|
if (Platform.isAndroid) {
|
|
await FlutterBluePlus.turnOn();
|
|
}
|
|
} catch (e) {
|
|
AppLog.log('蓝牙打开失败');
|
|
}
|
|
}
|
|
} else {
|
|
AppLog.log('开始扫描 蓝牙不可用,不能进行蓝牙操作');
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 开始扫描蓝牙设备
|
|
Future<void> startScan(int timeout, ScanDevicesCallBack scanDevicesCallBack,
|
|
{List<Guid>? idList}) async {
|
|
FlutterBluePlus.isSupported.then((bool isAvailable) async {
|
|
if (isAvailable) {
|
|
if (_adapterState == BluetoothAdapterState.on) {
|
|
try {
|
|
FlutterBluePlus.startScan(timeout: Duration(seconds: timeout));
|
|
final StreamSubscription<List<ScanResult>> subscription =
|
|
FlutterBluePlus.scanResults.listen((List<ScanResult> results) {
|
|
scanDevices.clear();
|
|
for (final ScanResult scanResult in results) {
|
|
// 判断名字为空的直接剔除
|
|
if (((scanResult.advertisementData.serviceUuids.isNotEmpty
|
|
? scanResult.advertisementData.serviceUuids[0]
|
|
: '')
|
|
.toString()
|
|
.contains('758824')) &&
|
|
(scanResult.rssi >= -100)) {
|
|
// 查询id相同的元素
|
|
final int knownDeviceIndex = scanDevices.indexWhere(
|
|
(ScanResult d) =>
|
|
(d.device.platformName ==
|
|
scanResult.device.platformName) ||
|
|
(d.advertisementData.advName ==
|
|
scanResult.advertisementData.advName));
|
|
// 不存在的时候返回-1
|
|
if (knownDeviceIndex >= 0) {
|
|
scanDevices[knownDeviceIndex] = scanResult;
|
|
} else {
|
|
scanDevices.add(scanResult);
|
|
}
|
|
}
|
|
}
|
|
scanDevicesCallBack(scanDevices);
|
|
}, onError: (e) {
|
|
AppLog.log(
|
|
'扫描失败:$e',
|
|
);
|
|
});
|
|
|
|
FlutterBluePlus.cancelWhenScanComplete(subscription);
|
|
} catch (e) {
|
|
AppLog.log('扫描失败');
|
|
}
|
|
} else {
|
|
try {
|
|
if (Platform.isAndroid) {
|
|
await FlutterBluePlus.turnOn();
|
|
}
|
|
} catch (e) {
|
|
AppLog.log('蓝牙打开失败');
|
|
}
|
|
}
|
|
} else {
|
|
AppLog.log('开始扫描 蓝牙不可用,不能进行蓝牙操作');
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 调用发送数据 List senderData,
|
|
Future<void> blueSendData(
|
|
String deviceName, ConnectStateCallBack stateCallBack,
|
|
{bool isAddEquipment = false}) async {
|
|
FlutterBluePlus.isSupported.then((bool isAvailable) async {
|
|
if (isAvailable) {
|
|
AppLog.log(
|
|
'蓝牙状态 系统蓝牙状态:$_adapterState 蓝牙连接状态:$bluetoothConnectionState');
|
|
if (_adapterState == BluetoothAdapterState.on) {
|
|
// 蓝牙已开启,可以进行蓝牙操作
|
|
if (bluetoothConnectionState != BluetoothConnectionState.connected) {
|
|
_connect(deviceName, (BluetoothConnectionState state) {
|
|
stateCallBack(bluetoothConnectionState!);
|
|
}, isAddEquipment: isAddEquipment);
|
|
} else {
|
|
stateCallBack(bluetoothConnectionState!);
|
|
}
|
|
} else {
|
|
try {
|
|
stateCallBack(BluetoothConnectionState.disconnected);
|
|
openBlue();
|
|
} catch (e) {
|
|
AppLog.log('蓝牙打开失败');
|
|
}
|
|
}
|
|
} else {
|
|
AppLog.log('开始扫描 蓝牙不可用,不能进行蓝牙操作');
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 连接
|
|
Future<void> _connect(
|
|
String deviceName, ConnectStateCallBack connectStateCallBack,
|
|
{bool isAddEquipment = false}) async {
|
|
connectDeviceName = deviceName;
|
|
final List<ScanResult> devicesList = scanDevices;
|
|
|
|
final bool isExistDevice = isExistScanDevices(connectDeviceName);
|
|
|
|
if (isAddEquipment == false && isExistDevice == false) {
|
|
//取消缓存直接使用,存在配对场景设备信息会更变
|
|
startScanSingle(deviceName, 10, (List<ScanResult> scanDevices) {
|
|
_connectDevice(scanDevices, deviceName, connectStateCallBack,
|
|
isAddEquipment: isAddEquipment);
|
|
});
|
|
} else {
|
|
_connectDevice(devicesList, deviceName, connectStateCallBack,
|
|
isAddEquipment: isAddEquipment);
|
|
}
|
|
}
|
|
|
|
//查找缓存里面是否有设备
|
|
bool isExistScanDevices(String connectDeviceName) {
|
|
final bool isExistDevice = scanDevices.any((ScanResult element) =>
|
|
element.device.platformName == connectDeviceName ||
|
|
element.advertisementData.advName == connectDeviceName);
|
|
return isExistDevice;
|
|
}
|
|
|
|
Future<void> _connectDevice(List<ScanResult> devicesList, String deviceName,
|
|
ConnectStateCallBack connectStateCallBack,
|
|
{bool isAddEquipment = false}) async {
|
|
// 判断数组列表里面是否有这个设备
|
|
// AppLog.log("devicesList:$devicesList");
|
|
final int knownDeviceIndex = devicesList.indexWhere((ScanResult d) =>
|
|
(d.device.platformName == deviceName) ||
|
|
(d.advertisementData.advName == deviceName));
|
|
|
|
ScanResult? scanResult; //使用局部变量防止出现缓存
|
|
if (knownDeviceIndex >= 0) {
|
|
// 存在的时候赋值
|
|
connectDeviceMacAddress =
|
|
devicesList[knownDeviceIndex].advertisementData.advName.isNotEmpty
|
|
? devicesList[knownDeviceIndex].advertisementData.advName
|
|
: devicesList[knownDeviceIndex].device.platformName;
|
|
|
|
bluetoothConnectDevice = devicesList[knownDeviceIndex].device;
|
|
// AppLog.log('bluetoothConnectDevice: $bluetoothConnectDevice');
|
|
scanResult = devicesList[knownDeviceIndex];
|
|
|
|
_initGetMtuSubscription();
|
|
_initListenConnectionState();
|
|
}
|
|
if (scanResult == null || connectDeviceMacAddress.isEmpty) {
|
|
return;
|
|
}
|
|
// AppLog.log("调用了停止扫描的方法");
|
|
await stopScan();
|
|
|
|
if ((scanResult.advertisementData.serviceUuids[0].toString()[31] == '0') &&
|
|
isAddEquipment == false) {
|
|
connectStateCallBack(BluetoothConnectionState.disconnected);
|
|
EasyLoading.showToast('该锁已被重置'.tr, duration: 2000.milliseconds);
|
|
return;
|
|
}
|
|
|
|
// 重连三次
|
|
const int maxAttempts = 3;
|
|
int attempt = 0;
|
|
while (attempt < maxAttempts) {
|
|
try {
|
|
await bluetoothConnectDevice!.connect(timeout: 5.seconds);
|
|
break; // If the connection is successful, break the loop
|
|
} catch (e) {
|
|
AppLog.log('连接失败 重连了: $e');
|
|
attempt++; // Increase the attempt count
|
|
if (attempt < maxAttempts) {
|
|
AppLog.log('重新尝试连接...');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (attempt >= maxAttempts) {
|
|
AppLog.log('$maxAttempts次后尝试连接失败');
|
|
connectStateCallBack(BluetoothConnectionState.disconnected);
|
|
}
|
|
|
|
if (bluetoothConnectionState == BluetoothConnectionState.connected) {
|
|
try {
|
|
bluetoothConnectDevice!
|
|
.discoverServices()
|
|
.then((List<BluetoothService> services) {
|
|
for (final BluetoothService service in services) {
|
|
if (service.uuid == _serviceIdConnect) {
|
|
for (final BluetoothCharacteristic characteristic
|
|
in service.characteristics) {
|
|
if (characteristic.characteristicUuid ==
|
|
_characteristicIdSubscription) {
|
|
_subScribeToCharacteristic(characteristic);
|
|
bluetoothConnectionState = BluetoothConnectionState.connected;
|
|
connectStateCallBack(bluetoothConnectionState!);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} on Exception catch (e) {
|
|
bluetoothConnectionState = BluetoothConnectionState.disconnected;
|
|
connectStateCallBack(bluetoothConnectionState!);
|
|
AppLog.log(
|
|
'发现设备时失败 e:$e bluetoothConnectionState:$bluetoothConnectionState');
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Future<void> _subScribeToCharacteristic(
|
|
BluetoothCharacteristic characteristic) async {
|
|
final StreamSubscription<List<int>> subscription =
|
|
characteristic.onValueReceived.listen((List<int> data) {
|
|
AppLog.log('订阅获取的数据: $data ');
|
|
if (data == lastTimeData || data.isEmpty) {
|
|
return;
|
|
} else {
|
|
lastTimeData = data;
|
|
}
|
|
final bool dataHeadCorrect = isDataHeadCorrect(data);
|
|
final bool allDataHeadCorrect = isDataHeadCorrect(allData);
|
|
if (dataHeadCorrect && allDataHeadCorrect) {
|
|
//缓存数据和新数据都有包头,直接放弃缓存数据
|
|
allData = <int>[];
|
|
}
|
|
if (dataHeadCorrect) {
|
|
// 当包有头时
|
|
// 判断是否需要分包
|
|
dataLen = data[8] * 256 + data[9]; // 高16位用来指示后面数据块内容的长度
|
|
if (dataLen! + 14 > data.length) {
|
|
// 当前包的长度小于实际的包时 分包添加 不解析
|
|
allData.addAll(data);
|
|
} else {
|
|
// 当前包的长度小于实际的包时 不分包 解析
|
|
allData.addAll(data);
|
|
CommandReciverManager.appDataReceive(allData);
|
|
// 发送完解析初始化数组
|
|
allData = <int>[];
|
|
}
|
|
} else if (allDataHeadCorrect) {
|
|
// 当包没有头时 是分包的包 直接添加
|
|
allData.addAll(data);
|
|
if (((dataLen ?? 0) + 14) <= allData.length) {
|
|
// 当长度小于等于当前包的数据时 直接解析数据
|
|
CommandReciverManager.appDataReceive(allData);
|
|
// 发送完解析初始化数组
|
|
allData = <int>[];
|
|
}
|
|
}
|
|
});
|
|
bluetoothConnectDevice!.cancelWhenDisconnected(subscription);
|
|
await characteristic.setNotifyValue(true);
|
|
}
|
|
|
|
//判断数据头是否正确
|
|
bool isDataHeadCorrect(List<int> data) {
|
|
if (data.length < 4) {
|
|
return false;
|
|
}
|
|
//239, 1, 238, 2, 是数据包头
|
|
if ((data[0] == 0xEF) &&
|
|
(data[1] == 0x01) &&
|
|
(data[2] == 0xEE) &&
|
|
(data[3] == 0x02)) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 写入
|
|
Future<void> writeCharacteristicWithResponse(List<int> value) async {
|
|
final List<BluetoothService> services =
|
|
await bluetoothConnectDevice!.discoverServices();
|
|
for (final BluetoothService service in services) {
|
|
if (service.uuid == _serviceIdConnect) {
|
|
for (final BluetoothCharacteristic characteristic
|
|
in service.characteristics) {
|
|
if (characteristic.characteristicUuid == _characteristicIdWrite) {
|
|
try {
|
|
final List<int> valueList = value;
|
|
final List subData = splitList(valueList, _mtuSize!);
|
|
// AppLog.log('writeCharacteristicWithResponse _mtuSize:$_mtuSize 得到的分割数据:$subData');
|
|
for (int i = 0; i < subData.length; i++) {
|
|
if (characteristic.properties.writeWithoutResponse) {
|
|
// 使用WRITE_NO_RESPONSE属性写入值
|
|
await characteristic.write(subData[i], withoutResponse: true);
|
|
} else if (characteristic.properties.write) {
|
|
// 使用WRITE属性写入值
|
|
await characteristic.write(subData[i]);
|
|
} else {
|
|
// 特性不支持写入
|
|
throw Exception(
|
|
'This characteristic does not support writing.');
|
|
}
|
|
}
|
|
} on Exception catch (e, s) {
|
|
AppLog.log('APP写入失败: $e $s');
|
|
rethrow;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 停止扫描蓝牙设备
|
|
Future<void> stopScan() async {
|
|
try {
|
|
await FlutterBluePlus.stopScan();
|
|
} catch (e) {
|
|
AppLog.log('停止扫描失败');
|
|
}
|
|
}
|
|
|
|
// 断开连接
|
|
Future<void> disconnect() async {
|
|
try {
|
|
connectDeviceMacAddress = '';
|
|
if (bluetoothConnectionState == BluetoothConnectionState.connected) {
|
|
//加快蓝牙断连
|
|
await bluetoothConnectDevice!.disconnect(timeout: 3);
|
|
AppLog.log('断开连接成功');
|
|
}
|
|
} on Exception catch (e, _) {
|
|
AppLog.log('断开连接失败: $e');
|
|
} finally {
|
|
bluetoothConnectionState = BluetoothConnectionState.disconnected;
|
|
}
|
|
}
|
|
|
|
Future<void> openBlue() async {
|
|
if (Platform.isAndroid) {
|
|
await FlutterBluePlus.turnOn();
|
|
}
|
|
if (Platform.isIOS) {
|
|
EasyLoading.showToast('请开启蓝牙'.tr, duration: 2000.milliseconds);
|
|
}
|
|
}
|
|
|
|
void disposed() {
|
|
_sendStreamSubscription?.cancel();
|
|
_mtuSubscription!.cancel();
|
|
_adapterStateStateSubscription!.cancel();
|
|
_connectionStateSubscription!.cancel();
|
|
}
|
|
}
|