ios ble 蓝牙锁开发用例

    技术2025-09-23  59

    开发流程:

    1. 建立中心管家 2. 扫描外部设备 3. 获取扫描的 外部设备,获取外部设备 , 连接外部设备  4.  连接外设 成功,获取  发现服务  5.  发现服务 uuid, 发现 服务下 特征值  6.  读取 特征值  ,订阅 ble->app 通道   获取app->ble 特征值  7.  接收 读取的特征值  8. 锁入网,传递psw2 给app  9. p1+p2 加密 systemId 鉴权   10. 锁回复 psw3   11.发送  开锁 确认帧 

     代码实现: 

    // // ViewController.m // mutipeerConnectivityTest // // Created by 邓安置 on 2020/6/11. // Copyright © 2020 邓安置. All rights reserved. // #import "ViewController.h" #import <CoreBluetooth/CoreBluetooth.h> #import "CBPeripheral+Extension.h" #import "KDSBleOptions.h" #import "NSData+JKEncrypt.h" #import "KDSBleAssistant.h" #import "NSTimer+KDSBlock.h" @interface ViewController ()<CBCentralManagerDelegate,CBPeripheralDelegate> @property(nonatomic,strong) CBCentralManager* mgr; @property(nonatomic,strong) CBPeripheral* peripheral; @property(nonatomic,copy,nullable) void(^myBlock)(CBPeripheral* cbP); @property (nonatomic, strong) CBCharacteristic *batteryCharacteristic; //电量 @property (nonatomic, strong) CBCharacteristic *systemIDCharacteristic; //系统ID @property (nonatomic, strong) CBCharacteristic *modelNumCharacteristic; // @property (nonatomic, strong) CBCharacteristic *seriaNumCharacteristic; // ///要写入到哪个特征 @property (nonatomic, strong) CBCharacteristic *writeCharacteristic; @property (nonatomic, strong) CBCharacteristic *functionSetCharacteristic;//功能集 @property (nonatomic, strong, nullable) CBPeripheral *connectedPeripheral; @property (nonatomic, strong) NSData *systemID; ///连接外设绑定时,收到SN后,从服务器请求回来。绑定后从绑定设备列表获得。测试没有上市的蓝牙时,服务器没有值返回,这是使用mac提取。 @property (nonatomic, copy, nullable) NSString *pwd1; ///用户入网(绑定)的时候生成,锁蓝牙返回的payload数据的1~4字节。 @property (nonatomic, strong, nullable) NSData *pwd2; ///鉴权成功时,锁蓝牙返回的payload数据的1~4字节。 @property (nonatomic, strong, nullable) NSData *pwd3; /**传输序号,每次传输+1,到了255置为50 (为了区分心跳包 设置范围为50-255)*/ @property (nonatomic, assign) int tsn; ///心跳包tsn 1 - 49 @property (nonatomic, assign) NSInteger heartbeatTsn; @property (nonatomic, weak) NSTimer *heartbeatTimer; @end @implementation ViewController - (IBAction)searchShow:(id)sender { // 1. 建立中心管家 // nil: 在主线程中扫描 CBCentralManager *centralManager= [[CBCentralManager alloc] initWithDelegate:self queue:nil]; _mgr = centralManager; NSLog(@"%@",@"开始扫描"); // } - (void)viewDidLoad { [super viewDidLoad]; self.myBlock= ^(CBPeripheral* cbP){ NSLog(@"block回调--->%@",cbP.advDataLocalName); }; self.pwd1=@"62375ea279e77356aa0a77f9"; } -(void)centralManagerDidUpdateState:(CBCentralManager *)central{ NSLog(@"%ld",central.state); // 开机状态 5 if(central.state== CBManagerStatePoweredOn ){ // 2. 扫描外部设备 CBPeripheral表示外部设备 [self.mgr scanForPeripheralsWithServices:nil options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }]; } __weak typeof(self) weakSelf = self; dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_ASSIGN_CURRENT, ^{ if (weakSelf.mgr.isScanning) { [weakSelf stopScan]; } }); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ block(); }); } - (void)stopScan { NSLog(@"%@",@"停止扫描"); [self.mgr stopScan]; } -(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{ /** ===peripheral名:Kaadas DoorLock ===广播名:KDSF0F8F2BFB618 ===设备uuid:22F116CE-D746-16A4-8408-BE1476E11B0C ===peripheral:<CBPeripheral: 0x2822b4fa0, identifier = 22F116CE-D746-16A4-8408-BE1476E11B0C, name = Kaadas DoorLock, state = disconnected> ===设备广播数据:{ kCBAdvDataIsConnectable = 1; kCBAdvDataLocalName = KDSF0F8F2BFB618; kCBAdvDataRxPrimaryPHY = 0; kCBAdvDataRxSecondaryPHY = 0; kCBAdvDataServiceUUIDs = ( FFD0, FFF0 ); kCBAdvDataTimestamp = "614075187.3347189"; kCBAdvDataTxPowerLevel = 0; } */ // NSLog(@"\n\n===peripheral名:%@ \n===广播名:%@\n ===设备uuid:%@\n ===peripheral:%@\n ===设备广播数据:%@\n\n",peripheral.name,advertisementData[@"kCBAdvDataLocalName"],peripheral.identifier.UUIDString,peripheral,advertisementData); //解析广播包中的kCBAdvDataLocalName 区分凯迪仕的设备 NSString *key = CBAdvertisementDataLocalNameKey; // 3. 获取扫描的 外部设备,获取外部设备 , 连接外部设备 if (peripheral==nil || peripheral.name==nil || ![advertisementData[key] containsString:@"KDSA434F1CC143A"]) return; [self stopScan]; NSLog(@"peripheral==%@",advertisementData[key]); peripheral.advDataLocalName = advertisementData[key]; self.myBlock(peripheral); self.peripheral = peripheral; // 连接外部设备 [self.mgr connectPeripheral:peripheral options:nil]; } // 4. 连接外设 成功,获取 发现服务 -(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{ // 通过连接的外部设置保存扫描到的 NSLog(@"连接成功:%@",peripheral.advDataLocalName); _connectedPeripheral = peripheral; NSInteger allLenth =peripheral.advDataLocalName.length; if (allLenth >= 12) { NSString *orgStr = [peripheral.advDataLocalName substringFromIndex:allLenth-12]; NSData *password1 = [orgStr dataUsingEncoding:NSASCIIStringEncoding]; } peripheral.delegate =self; // 扫描服务 根据服务 扫描特征值 [self.peripheral discoverServices:nil]; } // 5. 发现服务 uuid, 发现 服务下 特征值 -(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ NSArray* services= peripheral.services; NSLog(@"设备服务值:%@",services); for (CBService *sevice in peripheral.services) { /** 设备服务值:( "<CBService: 0x2803c6800, isPrimary = YES, UUID = Device Information>", "<CBService: 0x2803c5840, isPrimary = YES, UUID = FFF0>", "<CBService: 0x2803c66c0, isPrimary = YES, UUID = FFE0>", "<CBService: 0x2803c6740, isPrimary = YES, UUID = FFE5>", "<CBService: 0x2803c6640, isPrimary = YES, UUID = FFB0>", "<CBService: 0x2803c60c0, isPrimary = YES, UUID = F000FFD0-0451-4000-B000-000000000000>" ) */ // NSLog(@"%@",sevice.UUID.UUIDString); if ([sevice.UUID.UUIDString isEqualToString:OADResetServiceUUID] ||[sevice.UUID.UUIDString isEqualToString:DFUResetServiceUUID]) { //存在FFD0或1802服务,OAD复位服务 //存在FFD0或1802服务,OAD复位服务 peripheral.isNewDevice = YES; peripheral.bleVersion = 2; NSLog(@"--{Kaadas}--bleVersion:%d",peripheral.bleVersion); } [peripheral discoverCharacteristics:nil forService:sevice];//发现服务下面所有的特征 } } // 6. 读取 特征值 ,订阅 ble->app 通道 获取app->ble 特征值,发送 心跳包 // 发现特征值就会调用 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(nullable NSError *)error { if (error){ NSLog(@"获取服务:%@的特征失败: %@", service.UUID, error); return; } UInt64 uuid = strtoul(service.UUID.UUIDString.UTF8String, 0, 16); KDSBleService serviceType = (KDSBleService)uuid; NSLog(@"读的服务-service===%@",service.characteristics); switch (serviceType) { case KDSBleServiceModule: for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"读的服务-模块信息 (电量 pwd3):severUUId:%@====charUUId:%@",service.UUID.UUIDString,characteristic.UUID.UUIDString); // [peripheral setNotifyValue:YES forCharacteristic:characteristic]; if ([characteristic.UUID.UUIDString isEqualToString:batteryDUUID]) { [peripheral readValueForCharacteristic:characteristic]; //电量特征值 _batteryCharacteristic = characteristic; } } break; case KDSBleServiceDevice: for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"读的服务-BLE设备信息参数:severUUId:%@====charUUId:%@",service.UUID.UUIDString,characteristic.UUID.UUIDString); //监听characteristic值变化 // [peripheral setNotifyValue:YES forCharacteristic:characteristic]; if ([characteristic.UUID.UUIDString isEqualToString:systemIDUUID]) { _systemIDCharacteristic = characteristic; [peripheral readValueForCharacteristic:characteristic]; } else if ([characteristic.UUID.UUIDString isEqualToString:modelNumUUID]) { _modelNumCharacteristic = characteristic; [peripheral readValueForCharacteristic:characteristic]; } else if ([characteristic.UUID.UUIDString isEqualToString:seriaLNumUUID]) { _seriaNumCharacteristic = characteristic; [peripheral readValueForCharacteristic:characteristic]; } } break; case KDSBleServiceLock: break; case KDSBleServiceApp2BleTunnel: // app->蓝牙 0xFFE5 ,下只有一个特征值,app->ble for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"读的服务-APP-->BLE数据通道severUUId:%@====charUUId:%@",service.UUID.UUIDString,characteristic.UUID.UUIDString); //主动去读取一次外围设备的消息 [peripheral readValueForCharacteristic:characteristic]; // [peripheral setNotifyValue:YES forCharacteristic:characteristic]; self.writeCharacteristic = characteristic; } if(self.connectedPeripheral.isNewDevice){ [self createHeartbeatTimer]; } break; case KDSBleServiceBle2AppTunnel: //0xFFE0 for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"读的服务-BLE-->APP数据通道:severUUId:%@====charUUId:%@",service.UUID.UUIDString,characteristic.UUID.UUIDString); [peripheral setNotifyValue:YES forCharacteristic:characteristic]; // ffe0 下功能集 ffe1 if ([characteristic.UUID.UUIDString isEqualToString:kFFE1]) { peripheral.bleVersion = 3; NSLog(@"--{Kaadas}--bleVersion:%d",peripheral.bleVersion); _functionSetCharacteristic = characteristic; [peripheral readValueForCharacteristic:characteristic]; } } break; case KDSApp2BleDisNetworkTunnel: break; case KDSBle2AppDisNetworkTunnel: break; } } // 7. 接收 读取的特征值 , #pragma mark 接收特征的数据 - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ if (error) return; NSString *uuid = characteristic.UUID.UUIDString; NSLog(@"读取所有特征值的时候蓝牙UUID的值:%@",uuid); if ([uuid isEqualToString:batteryDUUID]) {//电量信息 NSData *batteryData = characteristic.value; u_int8_t tt; [batteryData getBytes:&tt length:sizeof(tt)]; int elct = tt;//0-100 peripheral.power = elct; NSLog(@"电量值是=%d",elct); } else if ([uuid isEqualToString:seriaLNumUUID]) {//SN peripheral.serialNumber = characteristic.value.jk_UTF8String; NSLog(@"sn是=%@",peripheral.serialNumber); } else if ([uuid isEqualToString:systemIDUUID]){//System ID //获取systemId _systemID = characteristic.value; NSString *systemIDStr = [KDSBleAssistant convertDataToHexStr:_systemID]; if (!systemIDStr || strtol(systemIDStr.UTF8String, NULL, 16) == 0) { _systemID = [KDSBleAssistant extractSystemIDFromAdvName:peripheral.advDataLocalName]; } NSLog(@"systemID是=%@",_systemID); } else if ([uuid isEqualToString:modelNumUUID]) // modulenum { self.connectedPeripheral.lockModelNumber = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding]; NSLog(@"modulenum是=%@",self.connectedPeripheral.lockModelNumber); } else if ([uuid isEqualToString:bleToAppDUUID]){//模块向App发送数据通道 if (characteristic.value.length == 16) { //没用,都是20个字节 NSData *pwd1Data = [characteristic.value subdataWithRange:NSMakeRange(0, 12)]; NSData *pwd = [KDSBleAssistant convertHexStrToData:self.pwd1]; NSString* pwdS= [KDSBleAssistant convertDataToHexStr:pwd]; NSLog(@"%@",pwdS); return; } if (characteristic.value.length < 20) {return;/**数据丢包了 */ } //新设备数据的处理 NSData *data = characteristic.value; NSLog(@"--{Kaadas}--蓝牙--处理模块发来的数据==%@",data); const unsigned char* bytes = (const unsigned char*)data.bytes; u_int8_t control = bytes[0]; u_int8_t check1 = bytes[2]; NSData *decryptData = data; if (control != 0) {//心跳包不加密 //解密数据 decryptData = [self getAes256_decryptDataWithOriginData:data]; NSLog(@"解密以后的数据:%@",[KDSBleAssistant convertDataToHexStr:decryptData]); } u_int8_t check2 = [KDSBleAssistant sumOfDataThroughoutBytes:[decryptData subdataWithRange:NSMakeRange(4, 16)]]; if (check1 == check2) { // KDSLog(@"校验正确=%@",decryptData) NSLog(@"处理校验正确之后的数据------%@",decryptData); const unsigned char *bytes = (const unsigned char *)decryptData.bytes; unsigned char transferSerialNumber = bytes[1]; //tsn unsigned char cmd = bytes[3]; unsigned char status = bytes[4]; if (cmd==0 && transferSerialNumber<50) return;//心跳包确认帧。 NSString *receipt = @(transferSerialNumber).stringValue; //如果是app主动发送命令,那么队列里会有记录,根据tsn和命令值执行相应的命令就行。 //执行app主动发送命令队列中不存在的上报命令 KDSBleTunnelOrder order = (KDSBleTunnelOrder)cmd; switch (order) { case KDSBleTunnelOrderEncrypt: // 8. 锁入网,传递psw2 给app if (status == 1)//收到锁发送过来的入网(绑定)指令,可以在返回数据中提取pwd2 { self.pwd2 = [decryptData subdataWithRange:NSMakeRange(5, 4)]; // 9. p1+p2 加密 systemId 鉴权 [self sendResponseInOrOutNet:(int)transferSerialNumber]; NSLog(@"~~~~~收到入网命令self.pwd2=%@",self.pwd2); [self authenticationWithPwd1:self.pwd1 pwd2:self.pwd2]; } else if (status == 2)//收到pwd3,绑定成功。 { // 10. 锁回复 psw3 self.pwd3 = [decryptData subdataWithRange:NSMakeRange(5, 4)]; NSLog(@"~~~~~收到self.pwd3,绑定成功=%@",self.pwd3); [self sendResponseInOrOutNet:(int)transferSerialNumber]; } break; case KDSBleTunnelOrderLockOperate: NSLog(@"%@",@"锁操作上报"); break; }; }else{ NSLog(@"--{Kaadas}--校验出错--check1=%d--check2=%d",check1,check2); } } else if ([uuid isEqualToString:kFFE1])//功能集 { NSString *FunctionSetKey = [NSString stringWithFormat:@"0x%@",[KDSBleAssistant convertDataToHexStr:characteristic.value]]; NSLog(@"--{Kaadas}--功能集=锁上=%@",FunctionSetKey); } } #pragma mark - 获取解密数据 ///解密数据,绑定/重置时只能使用密码1+4字节都为0的NSData解密(即密码2必须为nil);鉴权时只能使用密码1+密码2解密(即密码3必须为nil);鉴权成功获取密码3后只能使用密码1+密码3解密;否则解密的数据都不正确。 - (NSData *)getAes256_decryptDataWithOriginData:(NSData *)data{ NSMutableData *keyData = [NSMutableData data]; NSData *key1Data = [KDSBleAssistant convertHexStrToData:_pwd1]; //12个字节 [keyData appendData:key1Data]; if (self.pwd2 == nil) {//解密pwd2 4个字节 Byte behindByte[] = {0x00,0x00,0x00,0x00}; NSData *behindData= [NSData dataWithBytes:behindByte length:sizeof(behindByte)]; [keyData appendData:behindData]; NSLog(@"keyData:%@",keyData); }else if(self.pwd2 && self.pwd3 == nil){//解密pwd3 [keyData appendData:self.pwd2]; NSLog(@"keyData2:%@",keyData); } else if(self.pwd2 && self.pwd3){//建立通道以后 解密数据 [keyData appendData:self.pwd3]; // KDSLog(@"keyData3:%@",keyData); } // KDSLog(@"收到的原始数据:%@",data); NSData *resultData = [[data subdataWithRange:NSMakeRange(4, 16)] aes256_decryptData:keyData]; NSMutableData *decryptData = [NSMutableData data]; [decryptData appendData:[data subdataWithRange:NSMakeRange(0, 4)]]; [decryptData appendData:resultData]; // KDSLog(@"--{Kaadas}--蓝牙--收到的解密后数据%@",decryptData); return decryptData.copy; } #pragma mark - 发送收到 入网/退网 确认帧 - (void)sendResponseInOrOutNet:(int)tsn{ if (_connectedPeripheral.isNewDevice) { [self sendConfirmDataToBleDevice:tsn]; return; } } #pragma mark - 新模块发送确认帧 - (void)sendConfirmDataToBleDevice:(NSInteger)tsn{ if (tsn != 0) { // [self pauseTimer]; Byte conformByte[] = {0x00,tsn,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,}; NSData *conformData = [[NSData alloc] initWithBytes:conformByte length:sizeof(conformByte)]; if (self.writeCharacteristic && self.connectedPeripheral ) {///设备已经连接且要写入特征 // KDSLog(@"发送了确认帧:%@ %@",conformData,getcurretenDate); [self.connectedPeripheral writeValue:conformData forCharacteristic:self.writeCharacteristic type:0]; } return; } } - (NSString *)authenticationWithPwd1:(NSString *)pwd1 pwd2:(id)pwd2 { Byte payload[16] = {0}; for (int i = 0; i < self.systemID.length; ++i) { // 把NSData 转化为 Byte payload[i] = ((Byte *)self.systemID.bytes)[i]; } int sum = [KDSBleAssistant sumOfDataThroughoutBytes:[[NSData alloc] initWithBytes:payload length:16]]; self.tsn++; unsigned char* bytes = (unsigned char*)malloc(20); bytes[0] = 1; bytes[1] = self.tsn; bytes[2] = sum; bytes[3] = KDSBleTunnelOrderAuth; memcpy(bytes + 4, payload, 16); NSData* sendData= [[NSData alloc] initWithBytesNoCopy:bytes length:20]; NSMutableData *key = [NSMutableData dataWithData:[KDSBleAssistant convertHexStrToData:self.pwd1]]; //12个字节 [key appendData:self.pwd2]; //4个字节 NSData *encryptData = [[sendData subdataWithRange:NSMakeRange(4, 16)] aes256_encryptData:key]; NSLog(@"--{Kaadas}--encryptData=key=%@",key); NSLog(@"--{Kaadas}--encryptData====%@",encryptData); [key setData:[sendData subdataWithRange:NSMakeRange(0, 4)]]; [key appendData:encryptData]; [self.connectedPeripheral writeValue:key forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithResponse]; return nil; } #pragma mark - 发送心跳包 - (void)createHeartbeatTimer{ if (self.heartbeatTimer==nil) { //重复每3秒发送一次心跳包 __weak typeof(self) weakSelf = self; NSTimer *timer = [NSTimer kdsScheduledTimerWithTimeInterval:3 repeats:YES block:^(NSTimer * _Nonnull timer) { self.heartbeatTsn ++; Byte bytePayload[16] = {0xFF, 0}; NSData *payloadData = [[NSData alloc] initWithBytes:bytePayload length:sizeof(bytePayload)]; Byte byteHeader[] = {0x00,self.heartbeatTsn,0xff,0xAA}; NSData *headerData = [[NSData alloc] initWithBytes:byteHeader length:sizeof(byteHeader)]; NSMutableData *senderData = [NSMutableData data]; [senderData appendData:headerData]; [senderData appendData:payloadData]; NSLog(@"发送心跳:%@",[KDSBleAssistant convertDataToHexStr:senderData]); [self.connectedPeripheral writeValue:senderData forCharacteristic:self.writeCharacteristic type:0]; }]; self.heartbeatTimer = timer; [timer fire]; } } // 如果发送开锁命令,暂停3s发送心跳包 - (void)pauseTimer{ self.heartbeatTimer.fireDate = [NSDate dateWithTimeIntervalSinceNow:3]; } - (IBAction)openLock:(id)sender { [self operateLockWithPwd:@"147258" actionType:KDSBleLockControlActionUnlock keyType:KDSBleLockControlKeyPIN completion:^(KDSBleError error, CBPeripheral * _Nullable peripheral) { }]; } // 11.发送 开锁 确认帧 - (NSString *)operateLockWithPwd:(NSString *)pwd actionType:(KDSBleLockControl)action keyType:(KDSBleLockControl)key completion:(nullable void(^)(KDSBleError error, CBPeripheral * __nullable peripheral))completion{ Byte payload[16] = {(char)action, (char)key, 0, 0}; payload[3] = pwd.length;//密码长度 //第4个字节开始是密码,每一个字节保存一位密码。 for (int i = 0; i< pwd.length; i++) { payload[i + 4] = pwd.UTF8String[i]; } int sum = [KDSBleAssistant sumOfDataThroughoutBytes:[[NSData alloc] initWithBytes:payload length:16]]; self.tsn++; unsigned char* bytes = (unsigned char*)malloc(20); bytes[0] = 1; bytes[1] = self.tsn; bytes[2] = sum; bytes[3] = KDSBleTunnelOrderControl; memcpy(bytes + 4, payload, 16); NSData* sendData= [[NSData alloc] initWithBytesNoCopy:bytes length:20]; NSMutableData *key13 = [NSMutableData dataWithData:[KDSBleAssistant convertHexStrToData:self.pwd1]]; if (self.pwd3)//鉴权后+密码3加密 { [key13 appendData:self.pwd3]; } NSData *encryptData = [[sendData subdataWithRange:NSMakeRange(4, 16)] aes256_encryptData:key13]; [key13 setData:[sendData subdataWithRange:NSMakeRange(0, 4)]]; [key13 appendData:encryptData]; [self.connectedPeripheral writeValue:key13 forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithResponse]; return nil; } @end

    源码下载 : https://download.csdn.net/download/dreams_deng/12576424

    Processed: 0.012, SQL: 9