// // JLBleManager.m // JL_OTA // // Created by 凌煊峰 on 2021/10/11. // #import "JLBleManager.h" #define SENDBYSINGLE 0 //1:通过信号检测发送 0:通过直接塞数据发送 NSString *kFLT_BLE_FOUND = @"FLT_BLE_FOUND"; //发现设备 NSString *kFLT_BLE_PAIRED = @"FLT_BLE_PAIRED"; //BLE已配对 NSString *kFLT_BLE_CONNECTED = @"FLT_BLE_CONNECTED"; //BLE已连接 NSString *kFLT_BLE_DISCONNECTED = @"FLT_BLE_DISCONNECTED"; //BLE断开连接 NSString *FLT_BLE_SERVICE = @"AE00"; //服务号 NSString *FLT_BLE_RCSP_W = @"AE01"; //命令“写”通道 NSString *FLT_BLE_RCSP_R = @"AE02"; //命令“读”通道 @interface JLBleManager() @property (strong, nonatomic) CBCentralManager *bleManager; @property (strong, nonatomic) JLHashHandler *pairHash; @property (assign, nonatomic) BOOL pairStatus; @property (strong, nonatomic) NSMutableArray *blePeripheralArr; @property (strong, nonatomic) CBPeripheral *bleCurrentPeripheral; @property (strong, nonatomic) CBService * mService; @property (strong, nonatomic) CBCharacteristic *mRcspWrite; @property (strong, nonatomic) CBCharacteristic *mRcspRead; @property (strong, nonatomic) NSString *selectedOtaFilePath; @property (strong, nonatomic) NSString *connectByUUID; @property (strong, nonatomic) GET_DEVICE_CALLBACK getCallback; @property (strong, nonatomic) NSString *lastAddress; @property (strong, nonatomic) NSString *lastFilePath; @end @implementation JLBleManager - (instancetype)init { self = [super init]; if (self) { _isPaired = NO; _pairStatus = NO; _bleManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil]; /*--- JLSDK ADD ---*/ _otaManager = [JL_OTAManager getOTAManager]; [JL_OTAManager logSDKVersion]; [JLHashHandler sdkVersion]; [_otaManager logSendData:false]; _otaManager.delegate = self; self.pairHash = [[JLHashHandler alloc] init]; self.pairHash.delegate = self; _connectByUUID = nil; } return self; } - (void)setIsPaired:(BOOL)isPaired { _isPaired = isPaired; } #pragma mark - 扫描设备相关 #pragma mark 开始扫描 - (void)startScanBLE:(NSString * _Nullable)address filePath:(NSString * _Nullable)filePath { kJLLog(JLLOG_DEBUG, @"BLE ---> startScanBLE."); self.lastAddress = address; self.lastFilePath = filePath; _blePeripheralArr = [NSMutableArray new]; if (_bleManager) { if (_bleManager.state == CBManagerStatePoweredOn) { [_bleManager scanForPeripheralsWithServices:nil options:nil]; } else { __weak typeof(self) weakSelf = self; dispatch_after(0.5, dispatch_get_main_queue(), ^{ if (weakSelf.bleManager.state == CBManagerStatePoweredOn) { [weakSelf.bleManager scanForPeripheralsWithServices:nil options:nil]; } }); } } } #pragma mark 停止扫描 - (void)stopScanBLE { if (_bleManager) [_bleManager stopScan]; } #pragma mark - 蓝牙设备连接相关 #pragma mark 断开当前蓝牙设备连接 - (void)disconnectBLE { if (_bleCurrentPeripheral) { kJLLog(JLLOG_DEBUG, @"BLE --->To disconnectBLE."); [_bleManager cancelPeripheralConnection:_bleCurrentPeripheral]; self.isConnected = false; self.currentEntity = nil; } } #pragma mark 连接蓝牙设备 - (void)connectBLE:(CBPeripheral*)peripheral { if(_bleCurrentPeripheral){ [_bleManager cancelPeripheralConnection:_bleCurrentPeripheral]; } _bleCurrentPeripheral = peripheral; [_bleCurrentPeripheral setDelegate:self]; [_bleManager connectPeripheral:_bleCurrentPeripheral options:@{CBConnectPeripheralOptionNotifyOnDisconnectionKey:@(YES)}]; [_bleManager stopScan]; kJLLog(JLLOG_DEBUG, @"BLE Connecting... Name ---> %@", peripheral.name); } - (void)connectPeripheralWithUUID:(NSString*)uuid { _connectByUUID = uuid; [self startScanBLE:self.lastAddress filePath:self.lastFilePath]; } -(void)findHid:(NSString *)uuid{ NSUUID *uuidNs = [[NSUUID alloc] initWithUUIDString:uuid]; NSArray *array = [self.bleManager retrievePeripheralsWithIdentifiers:@[uuidNs]]; for (CBPeripheral *cbp in array) { if([cbp.identifier.UUIDString isEqualToString:uuid]){ kJLLog(JLLOG_DEBUG, @"reconnect:%@",cbp); [self connectBLE:cbp]; break; } } } -(void)connectAction{ if(self.connectByUUID == nil) return; NSArray *uuidArr = @[[[NSUUID alloc] initWithUUIDString:self.connectByUUID]]; NSArray *phArr = [_bleManager retrievePeripheralsWithIdentifiers:uuidArr];//serviceUUID就是你首次连接配对的蓝牙 if (phArr.count == 0) { return; } CBPeripheral* peripheral = phArr[0]; if (phArr.firstObject && [phArr.firstObject state] != CBPeripheralStateConnected && [phArr.firstObject state] != CBPeripheralStateConnecting) { NSString *ble_name = peripheral.name; NSString *ble_uuid = peripheral.identifier.UUIDString; kJLLog(JLLOG_DEBUG, @"FLT Connecting(Last)... Name ---> %@ UUID:%@",ble_name,ble_uuid); _bleCurrentPeripheral = peripheral; [_bleCurrentPeripheral setDelegate:self]; [_bleManager connectPeripheral:_bleCurrentPeripheral options:@{CBConnectPeripheralOptionNotifyOnDisconnectionKey:@(YES)}]; _connectByUUID = nil; } } #pragma mark - CBCentralManagerDelegate #pragma mark 蓝牙初始化 Callback - (void)centralManagerDidUpdateState:(CBCentralManager *)central { _mBleManagerState = central.state; if (_mBleManagerState != CBManagerStatePoweredOn) { self.mBlePeripheral = nil; self.blePeripheralArr = [NSMutableArray array]; } } #pragma mark 发现设备 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { NSString *ble_name = advertisementData[@"kCBAdvDataLocalName"]; NSData *ble_AD = advertisementData[@"kCBAdvDataManufacturerData"]; if (ble_name.length == 0) return; kJLLog(JLLOG_DEBUG, @"发现 ----> NAME:%@ RSSI:%@ AD:%@", ble_name,RSSI,ble_AD); if (ble_AD.length >= 29) { const uint8_t *bytes = ble_AD.bytes; NSMutableString *macStr = [NSMutableString string]; for (int i = 7; i <= 12; i++) { uint8_t macByte = bytes[i] ^ 0x5A; [macStr appendFormat:@"%02X", macByte]; if (i < 12) [macStr appendString:@":"]; } [self addPeripheral:peripheral Info:macStr]; } [[NSNotificationCenter defaultCenter] postNotificationName:kFLT_BLE_FOUND object:_blePeripheralArr userInfo:nil]; // ota升级过程,回连使用 if ([JLAdvParse otaBleMacAddress:self.lastBleMacAddress isEqualToCBAdvDataManufacturerData:ble_AD]) { [self connectBLE:peripheral]; } } - (void)addPeripheral:(CBPeripheral*)peripheral Info:(NSString*)macStr { kJLLog(JLLOG_DEBUG, @"BLE addPeripheral ---> Device %@", peripheral.name); if (self.lastAddress && [macStr isEqualToString:self.lastAddress]) { [self stopScanBLE]; [self connectBLE:peripheral]; } } #pragma mark 设备连接回调 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { kJLLog(JLLOG_DEBUG, @"BLE Connected ---> Device %@", peripheral.name); for (JLBleEntity *entity in self.blePeripheralArr) { if([entity.mPeripheral.identifier.UUIDString isEqualToString:peripheral.identifier.UUIDString]){ self.currentEntity = entity; break; } } self.isConnected = YES; _otaManager.mBLE_NAME = peripheral.name; _otaManager.mBLE_UUID = peripheral.identifier.UUIDString; [DFNotice post:kFLT_BLE_CONNECTED Object:peripheral]; // 连接成功后,查找服务 [peripheral discoverServices:nil]; } - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error { kJLLog(JLLOG_DEBUG, @"Err:BLE Connect FAIL ---> Device:%@ Error:%@",peripheral.name,[error description]); } #pragma mark 设备断开连接 - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(nullable NSError *)error { kJLLog(JLLOG_DEBUG, @"BLE Disconnect ---> Device %@ error:%d", peripheral.name, (int)error.code); [_otaManager noteEntityDisconnected]; self.isConnected = NO; self.pairStatus = NO; /*--- UI刷新,设备断开 ---*/ [[NSNotificationCenter defaultCenter] postNotificationName:kFLT_BLE_DISCONNECTED object:peripheral]; } #pragma mark - CBPeripheralDelegate #pragma mark 设备服务回调 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error { if (error) { kJLLog(JLLOG_DEBUG, @"Err: Discovered services fail."); return; } _mBlePeripheral = peripheral; for (CBService *service in peripheral.services) { //如果我们知道要查询的特性的CBUUID,可以在参数一中传入CBUUID数组。 //if ([service.UUID.UUIDString isEqual:FLT_BLE_SERVICE]) { kJLLog(JLLOG_DEBUG, @"BLE Service ---> %@", service.UUID.UUIDString); [peripheral discoverCharacteristics:nil forService:service]; //break; //} } } #pragma mark 设备特征回调 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error { if (error) { kJLLog(JLLOG_DEBUG, @"Err: Discovered Characteristics fail."); return; } if ([service.UUID.UUIDString isEqual:FLT_BLE_SERVICE]) { for (CBCharacteristic *character in service.characteristics) { /*--- RCSP ---*/ if ([character.UUID.UUIDString isEqual:FLT_BLE_RCSP_W]) { kJLLog(JLLOG_DEBUG, @"BLE Get Rcsp(Write) Channel ---> %@",character.UUID.UUIDString); self.mRcspWrite = character; } if ([character.UUID.UUIDString isEqual:FLT_BLE_RCSP_R]) { kJLLog(JLLOG_DEBUG, @"BLE Get Rcsp(Read) Channel ---> %@",character.UUID.UUIDString); self.mRcspRead = character; [peripheral setNotifyValue:YES forCharacteristic:character]; if(self.mRcspRead.properties == CBCharacteristicPropertyRead){ [peripheral readValueForCharacteristic:character]; kJLLog(JLLOG_DEBUG, @"BLE Rcsp(Read) Read Value For Characteristic."); } } } } } #pragma mark 更新通知特征的状态 - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error { if (error) { kJLLog(JLLOG_DEBUG, @"Err: Update NotificationState For Characteristic fail."); return; } if ([characteristic.service.UUID.UUIDString isEqual:FLT_BLE_SERVICE] && [characteristic.UUID.UUIDString isEqual:FLT_BLE_RCSP_R] && characteristic.isNotifying == YES) { __weak typeof(self) weakSelf = self; self.bleMtu = [peripheral maximumWriteValueLengthForType:CBCharacteristicWriteWithoutResponse]; kJLLog(JLLOG_DEBUG, @"BLE ---> MTU:%lu",(unsigned long)self.bleMtu); if (self.isPaired == YES) { [_pairHash hashResetPair]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //设备认证 [self->_pairHash bluetoothPairingKey:self.pairKey Result:^(BOOL ret) { if(ret){ weakSelf.lastUUID = peripheral.identifier.UUIDString; weakSelf.lastBleMacAddress = nil; [self postBlePairedNotificationWithPeripheral:peripheral]; [weakSelf.otaManager noteEntityConnected]; weakSelf.pairStatus = YES; }else{ kJLLog(JLLOG_DEBUG, @"JL_Assist Err: bluetooth pairing fail."); [weakSelf.bleManager cancelPeripheralConnection:peripheral]; } }]; }); }else{ self.lastUUID = peripheral.identifier.UUIDString; self.lastBleMacAddress = nil; [self postBlePairedNotificationWithPeripheral:peripheral]; [self.otaManager noteEntityConnected]; } } self.isConnected = YES; } - (void)postBlePairedNotificationWithPeripheral:(CBPeripheral *)peripheral { [[NSNotificationCenter defaultCenter] postNotificationName:kFLT_BLE_PAIRED object:peripheral]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self getDeviceInfo:^(BOOL needForcedUpgrade) { kJLLog(JLLOG_DEBUG, @"getDeviceInfo:%d",__LINE__); if (needForcedUpgrade) { kJLLog(JLLOG_DEBUG, @"设备需要强制升级,请到升级界面选择ota升级文件进行升级!"); } [self otaFuncWithFilePath:self.lastFilePath]; }]; }); } #pragma mark 设备返回的数据 GET - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { if (error) { kJLLog(JLLOG_DEBUG, @"Err: receive data fail."); return; } if(_isPaired == YES && _pairStatus == NO){ //收到设备的认证交互数据 [_pairHash inputPairData:characteristic.value]; }else{ //收到的设备数据,正常通讯数据 [_otaManager cmdOtaDataReceive:characteristic.value]; } } - (void)peripheralIsReadyToSendWriteWithoutResponse:(CBPeripheral *)peripheral{ } #pragma mark - 杰理蓝牙库OTA流程相关业务 /** * 获取已连接的蓝牙设备信息,这里如果上次设备升级没有成功,会要求执行otaFuncWithFilePath:强制升级 */ - (void)getDeviceInfo:(GET_DEVICE_CALLBACK _Nonnull)callback { /*--- 获取设备信息 ---*/ _getCallback = callback; [_otaManager cmdTargetFeature]; } /** * ota升级 * @param otaFilePath ota升级文件路径 */ - (void)otaFuncWithFilePath:(NSString *)otaFilePath { kJLLog(JLLOG_DEBUG, @"current otaFilePath ---> %@", otaFilePath); if (self.flutterEventSink) { self.flutterEventSink(@{ @"event": @"onStartOTA", @"type": @0, @"progress": @0.0, @"code": @8, @"message": @"开始升级" }); } self.selectedOtaFilePath = otaFilePath; NSData *otaData = [[NSData alloc] initWithContentsOfFile:otaFilePath]; [_otaManager cmdOTAData:otaData Result:^(JL_OTAResult result, float progress) { for (id objc in self.delegates) { if([objc respondsToSelector:@selector(otaProgressWithOtaResult:withProgress:)]){ [objc otaProgressWithOtaResult:result withProgress:progress]; } } }]; } - (void)otaFuncCancel:(CANCEL_CALLBACK _Nonnull)result{ [_otaManager cmdOTACancelResult:^(uint8_t status, uint8_t sn, NSData * _Nullable data) { result(status); }]; } //MARK: - ota manager delegate callback -(void)otaCancel{ //TODO: 取消OTA升级回调 } -(void)otaUpgradeResult:(JL_OTAResult)result Progress:(float)progress{ //TODO: 设备升级过程回调,包括进度状态 kJLLog(JLLOG_DEBUG, @"otaUpgradeResult ---> %u", result); if (result == JL_OTAResultPreparing) { if (self.flutterEventSink) { self.flutterEventSink(@{ @"event": @"onProgress", @"type": @0, @"progress": @(progress * 100), @"code": @(result), @"message": @"开始升级" }); } } else if (result == JL_OTAResultUpgrading) { if (self.flutterEventSink) { self.flutterEventSink(@{ @"event": @"onProgress", @"type": @1, @"progress": @(progress * 100), @"code": @(result), @"message": @"开始升级" }); } } else if (result == JL_OTAResultPrepared) { } else if (result == JL_OTAResultReconnect) { } else if (result == JL_OTAResultReconnectWithMacAddr) { } else if (result == JL_OTAResultSuccess) { if (self.flutterEventSink) { self.flutterEventSink(@{ @"event": @"onStopOTA", @"type": @0, @"progress": @0.0, @"code": @(result), @"message": @"升级成功" }); } } else if (result == JL_OTAResultReboot) { } else if (result == JL_OTAResultDisconnect) { } else { if (self.flutterEventSink) { self.flutterEventSink(@{ @"event": @"onError", @"type": @0, @"progress": @0.0, @"code": @(result), @"message": @"升级失败" }); } } } -(void)otaDataSend:(NSData *)data{ [self writeDataByCbp:data]; } -(void)otaFeatureResult:(JL_OTAManager *)manager{ kJLLog(JLLOG_DEBUG, @"getDeviceInfo:%d",__LINE__); if (manager.otaStatus == JL_OtaStatusForce) { kJLLog(JLLOG_DEBUG, @"---> 进入强制升级."); if (self.selectedOtaFilePath) { [self otaFuncWithFilePath:self.selectedOtaFilePath]; } else { _getCallback(true); } return; } else { if (manager.otaHeadset == JL_OtaHeadsetYES) { kJLLog(JLLOG_DEBUG, @"---> 进入强制升级: OTA另一只耳机."); if (self.selectedOtaFilePath) { [self otaFuncWithFilePath:self.selectedOtaFilePath]; } else { _getCallback(true); } return; } } kJLLog(JLLOG_DEBUG, @"---> 设备正常使用..."); dispatch_async(dispatch_get_main_queue(), ^{ /*--- 获取公共信息 ---*/ [self->_otaManager cmdSystemFunction]; self->_getCallback(false); }); } //MARK: - Hash pair delegate callback -(void)hashOnPairOutputData:(NSData *)data{ [self writeDataByCbp:data]; } //MARK: - data send manager /// 需要分包发送 /// - Parameter data: 数据 -(void)writeDataByCbp:(NSData *)data{ // kJLLog(JLLOG_DEBUG, @"%s:data:%@",__func__,data); if (_mBlePeripheral && self.mRcspWrite) { if (data.length > 0 ) { NSInteger len = data.length; while (len>0) { if (len <= _bleMtu) { NSData *mtuData = [data subdataWithRange:NSMakeRange(0, data.length)]; [self selectSendAction:mtuData]; len -= data.length; }else{ NSData *mtuData = [data subdataWithRange:NSMakeRange(0, _bleMtu)]; [self selectSendAction:mtuData]; len -= _bleMtu; data = [data subdataWithRange:NSMakeRange(_bleMtu, len)]; } } } }else{ //需要先赋值写特征的内容 kJLLog(JLLOG_DEBUG, @"need to init"); } } -(void)selectSendAction:(NSData *)data{ [_mBlePeripheral writeValue:data forCharacteristic:self.mRcspWrite type:CBCharacteristicWriteWithoutResponse]; } //MARK: - 通过信号阀发送 - (void)singleDidSendData:(NSData *)data{ [_mBlePeripheral writeValue:data forCharacteristic:self.mRcspWrite type:CBCharacteristicWriteWithoutResponse]; } - (void)releaseAllResources { // 停止扫描 [self stopScanBLE]; // 断开蓝牙连接 [self disconnectBLE]; // 释放 delegate self.bleManager.delegate = nil; self.pairHash.delegate = nil; self.otaManager.delegate = nil; // 清空 eventSink self.flutterEventSink = nil; // 清空回调 self.getCallback = nil; // 清空当前设备和属性 self.bleCurrentPeripheral = nil; self.mService = nil; self.mRcspWrite = nil; self.mRcspRead = nil; self.currentEntity = nil; self.blePeripheralArr = nil; self.selectedOtaFilePath = nil; self.connectByUUID = nil; self.lastAddress = nil; self.lastFilePath = nil; } @end