// // BLEServiceHandler.m // OneCable // // Created by KaRam Kim on 2017. 5. 18.. // Copyright © 2017년 ntels. All rights reserved. // #import "BLEServiceHandler.h" #import #import "Definitions.h" #import "JDJSONModel.h" #import "JDObject.h" @interface BLEServiceHandler() { CBCentralManager *_manager; NSMutableArray *_devices; BOOL _scanning; NSTimer *_scanTimer; BTLEDeivceModel *_connectedDevice; //service characteristic CBService *lastService; } @end @implementation BLEServiceHandler + (id)sharedManager { static BLEServiceHandler *sharedBLEServiceHandler = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedBLEServiceHandler = [[self alloc] init]; }); return sharedBLEServiceHandler; } - (id) init { self = [super init]; if (self) { _manager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()]; // *peripheralManager = [[CBPeripheralManager alloc]initWithDelegate:nil queue:nil options:@{CBPeripheralManagerOptionShowPowerAlertKey:@NO}] _devices = [NSMutableArray array]; // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startScan) name:kBLEConnect object:nil]; // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startScan) name:kBLEDisConnect object:nil]; } return self; } - (void)connect:(BTLEDeivceModel *)info { [_manager connectPeripheral:info.peripheralRef options:nil]; // if (info.peripheralRef.state != CBPeripheralStateDisconnected) { // [_manager cancelPeripheralConnection:info.peripheralRef]; // } else { // [_manager connectPeripheral:info.peripheralRef options:nil]; // } } - (void)disConnect { _conDevice = nil; } -(void) openBluetoothSettings{ [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"App-Prefs:root=Bluetooth"]]; } - (BOOL) checkBLEStatus { if (_manager.state == CBCentralManagerStatePoweredOff) { [[JDFacade facade] confirmTitle:@"Notice" message:@"블루투스가 비활성화 되어 있습니다. 활성화 해주세요." btnOKLabel:@"OK" btnCancelLabel:@"Cancel" completion:^(CustomAlertView *alertView, NSInteger buttonIndex) { if (buttonIndex == 1) { [self openBluetoothSettings]; } }]; return NO; } return YES; } - (void) startScan { if (![self checkBLEStatus]) return; _devices = [NSMutableArray array]; if (_scanTimer) [_scanTimer invalidate]; _scanning = YES; [_manager stopScan]; [_manager scanForPeripheralsWithServices:nil options:nil]; _scanTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(stopScan:) userInfo:@"Y" repeats:NO]; } - (void)stopScan:(NSString*)timeout { NSLog(@"STOP SCAN"); [_manager stopScan] ; _scanning = NO ; if( _delegate && [_delegate respondsToSelector:@selector(BLEEndScan:)] ) { [_delegate BLEEndScan:_devices] ; } } -(NSMutableArray *)getDeviceList { return _devices; } -(CBCharacteristic *)getChrInfo:(NSString *)name { CBCharacteristic *result = nil; for (CBService *serviceInfo in [_conDevice getServiceList]) { for (CBCharacteristic *chrInfo in serviceInfo.characteristics) { if ([[self getStrUUID:chrInfo] isEquestToIgnoreCase:[self getServiceUUID:name]]) { //NSLog(@"\nname : %@, chrInfo : %@", name, chrInfo.UUID); result = chrInfo; break; } } } return result; } -(NSString *)getChrName:(CBCharacteristic *)info { NSString *result = nil; for (NSString *key in [[self getGatewayCharDict] allKeysForObject:[[self getStrUUID:info] lowercaseString]]) { result = key; } return result; } -(NSString *)getStrUUID:(CBCharacteristic *)info { NSString *uuid =[NSString stringWithFormat:@"%@", info.UUID]; return uuid; } - (BTLEDeivceModel *)addPeripheral:(CBPeripheral*)peripheral { BTLEDeivceModel *device = [[BTLEDeivceModel alloc] init]; device.peripheralRef = peripheral; device.manager = _manager; peripheral.delegate = self; if (![_devices containsObject:device]) { [_devices addObject:device]; } // if( _delegate && [_delegate respondsToSelector:@selector(BLEUpldateDevice:)] ) { // [_delegate BLEUpldateDevice:device] ; // } return device; } - (NSString*)hexStringValue:(CBCharacteristic *)input { if (!input.value) return @"-"; NSString *raw = [NSString stringWithFormat:@"0x%@", input.value]; raw = [raw stringByReplacingOccurrencesOfString:@"<" withString:@""]; raw = [raw stringByReplacingOccurrencesOfString:@">" withString:@""]; return raw; } - (NSString*)asciiStringValue:(CBCharacteristic *)input { if (!input.value) return @"-"; NSString *ascii = [[NSString alloc] initWithData:input.value encoding:NSASCIIStringEncoding]; return ascii; } -(void)sendData:(CBCharacteristic *)chr str:(NSString *)str { NSData *data = [NSData data]; if ([str isDigit]) { int dataToWrite = [str intValue]; data = [NSData dataWithBytes:&dataToWrite length:1]; } else { data = [str dataUsingEncoding:NSASCIIStringEncoding]; } if (chr.properties & CBCharacteristicPropertyWriteWithoutResponse) [chr.service.peripheral writeValue:data forCharacteristic:chr type:CBCharacteristicWriteWithoutResponse]; else if (chr.properties & CBCharacteristicPropertyWrite) [chr.service.peripheral writeValue:data forCharacteristic:chr type:CBCharacteristicWriteWithResponse]; } #pragma mark - WiFi Settings //WiFi Setting관련 메뉴 -(void)scanWiFiList { [self sendData:[self getChrInfo:kBLEChrStWiFiScan] str:@"1"]; } -(NSString *)getWLanList:(BLEWlanListType)type { // TODO : WLanList 가져오기 NSString *result = @""; switch (type) { case BLEWlanListType1: [self readAndNotifyCharacteristicUUID:kBLEChrRdWiFiList1 isNotify:NO]; //result = [self asciiStringValue:[self getChrInfo:kBLEChrRdWiFiList1]]; break; case BLEWlanListType2: [self readAndNotifyCharacteristicUUID:kBLEChrRdWiFiList2 isNotify:NO]; //result = [self asciiStringValue:[self getChrInfo:kBLEChrRdWiFiList2]]; break; case BLEWlanListType3: [self readAndNotifyCharacteristicUUID:kBLEChrRdWiFiList3 isNotify:NO]; //result = [self asciiStringValue:[self getChrInfo:kBLEChrRdWiFiList3]]; break; default: break; } return result; } -(void)setWiFiSSID:(NSString *)ssid { [self sendData:[self getChrInfo:kBLEChrStSSIDArg] str:ssid]; } -(void)setWiFiPwd:(NSString *)pwd { [self sendData:[self getChrInfo:kBLEChrStPWDArg] str:pwd]; } -(void)enableDHCP { [self sendData:[self getChrInfo:kBLEChrStDHCPArg] str:@"1"]; } -(void)applyWiFiSettingInfo { [self sendData:[self getChrInfo:kBLEChrStSetApply] str:@"1"]; // todo : Connection정보가 notify로 들어오면, delegate를 통해서 알리기 } -(void)readAndNotifyCharacteristicUUID:(NSString *)uuid isNotify:(BOOL)isNotify { for (CBService *service in _conDevice.peripheralRef.services) { for (CBCharacteristic *characteristic in service.characteristics) { if ([characteristic.UUID.UUIDString isEquestToIgnoreCase:[self getGatewayDicUUIDForKey:uuid]]) { if (isNotify) [_conDevice.peripheralRef setNotifyValue:YES forCharacteristic:characteristic]; else [_conDevice.peripheralRef readValueForCharacteristic:characteristic]; break; } } } } - (void)readConnectionWiFiInfo { [self readAndNotifyCharacteristicUUID:kBLEChrRdSSID isNotify:NO]; [self readAndNotifyCharacteristicUUID:kBLEChrRdBSSID isNotify:NO]; [self readAndNotifyCharacteristicUUID:kBLEChrRnIpSet isNotify:NO]; [self readAndNotifyCharacteristicUUID:kBLEChrRnIpAddr isNotify:NO]; } - (NSString*)getStringValueForCharacteristicWithKey:(NSString*)key { return [self asciiStringValue:[self getChrInfo:key]]; } -(NSString *)getServiceName:(NSString *)uuid { NSString *result = nil; for (NSString *key in [[self getGatewayCharDict] allKeysForObject:uuid.lowercaseString]) { result = key; } return result; } -(NSString *)getServiceUUID:(NSString *)strKey { NSString *result = nil; for (NSString *key in [[self getGatewayCharDict] allKeys]) { if ([key isEquestToIgnoreCase:strKey]) { result = [[self getGatewayCharDict] objectForKey:key]; break; } } return result; } - (NSString*)getGatewayDicUUIDForKey:(NSString*)key{ return [[self getGatewayCharDict] objectForKey:key]; } -(NSDictionary *)getGatewayCharDict { NSDictionary *db = @{@"WFNPS_UUID":@"6f819d94-dddf-11e6-bf26-cec0c932ce01", @"SCAN_CHR_UUID":@"b364676d-dd76-11e6-bf26-cec0c932ce01", @"WLAN_LIST1_CHR_UUID":@"f333f87c-0787-11e7-93ae-92361f002671", @"WLAN_LIST2_CHR_UUID":@"f333fad4-0787-11e7-93ae-92361f002671", @"WLAN_LIST3_CHR_UUID":@"f333fbec-0787-11e7-93ae-92361f002671", @"SSID_ARG_CHR_UUID":@"508e6c28-0788-11e7-93ae-92361f002671", @"PASSWORD_ARG_CHR_UUID":@"b3646c08-dd76-11e6-bf26-cec0c932ce01", @"CONNECTION_CHR_UUID":@"b3646fbe-dd76-11e6-bf26-cec0c932ce01", @"SSID_CHR_UUID":@"b3647108-dd76-11e6-bf26-cec0c932ce01", @"BSSID_CHR_UUID":@"b3647234-dd76-11e6-bf26-cec0c932ce01", @"WF_IP_SET_ARG_CHR_UUID":@"8e0b49e6-088b-11e7-93ae-92361f002671", @"WF_IP_ADDR_ARG_CHR_UUID":@"8e0b4c2a-088b-11e7-93ae-92361f002671", @"WF_NET_PRE_LEN_ARG_CHR_UUID":@"8e0b4e32-088b-11e7-93ae-92361f002671", @"WF_GATEWAY_ARG_CHR_UUID":@"8e0b4ffe-088b-11e7-93ae-92361f002671", @"WF_DNS_ARG_CHR_UUID":@"8e0b50d0-088b-11e7-93ae-92361f002671", @"APPLY_CHR_UUID":@"8e0b51a2-088b-11e7-93ae-92361f002671", @"WF_IP_SET_CHR_UUID":@"8e0b5314-088b-11e7-93ae-92361f002671", @"WF_IP_ADDR_CHR_UUID":@"8e0b54a4-088b-11e7-93ae-92361f002671", @"WF_NET_PRE_LEN_CHR_UUID":@"8e0b5576-088b-11e7-93ae-92361f002671", @"WF_GATEWAY_CHR_UUID":@"8e0b568e-088b-11e7-93ae-92361f002671", @"WF_DNS_CHR_UUID":@"8e0b5878-088b-11e7-93ae-92361f002671", @"IPNPS_UUID":@"09d38ae8-dbb9-11e6-bf26-cec0c932ce01", @"IP_SET_ARG_CHR_UUID":@"09d38d68-dbb9-11e6-bf26-cec0c932ce01", @"IP_ADDR_ARG_CHR_UUID":@"09d38e62-dbb9-11e6-bf26-cec0c932ce01", @"NET_PRE_LEN_ARG_CHR_UUID":@"09d38f48-dbb9-11e6-bf26-cec0c932ce01", @"GATEWAY_ARG_CHR_UUID":@"09d39024-dbb9-11e6-bf26-cec0c932ce01", @"DNS_ARG_CHR_UUID":@"09d3960a-dbb9-11e6-bf26-cec0c932ce01", @"APPLY_IP_CHR_UUID":@"73207ac8-dd72-11e6-bf26-cec0c932ce01", @"IP_SET_CHR_UUID":@"73208018-dd72-11e6-bf26-cec0c932ce01", @"IP_ADDR_CHR_UUID":@"73208126-dd72-11e6-bf26-cec0c932ce01", @"NET_PRE_LEN_CHR_UUID":@"73208202-dd72-11e6-bf26-cec0c932ce01", @"GATEWAY_CHR_UUID":@"732082de-dd72-11e6-bf26-cec0c932ce01", @"DNS_CHR_UUID":@"732083a6-dd72-11e6-bf26-cec0c932ce01"}; return db; } #pragma mark - CBCentralManagerDelegate - (void)centralManagerDidUpdateState:(CBCentralManager *)central { NSLog(@"Central manager changed state: %ld", central.state); if( _delegate && [_delegate respondsToSelector:@selector(BLEStateChange:)] ) { [_delegate BLEStateChange:central.state == CBCentralManagerStatePoweredOn] ; } if (central.state == CBCentralManagerStatePoweredOn) { [self startScan]; } } - (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals { NSLog(@"%ld periphirals retrieved", [peripherals count]); } - (void)centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals { for (CBPeripheral *peripheral in peripherals) { NSLog(@"1Periphiral discovered: %@, %@", peripheral.name, peripheral.identifier); BOOL isExist = NO; for (BTLEDeivceModel *device in _devices) { if ([[device.peripheralRef.identifier UUIDString] isEqualToString:[peripheral.identifier UUIDString]]) { isExist = YES; } } if (!isExist) [self addPeripheral:peripheral]; } } - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI { //NSLog(@"2Periphiral discovered: %@, %@, signal strength: %d", peripheral.name, peripheral.identifier, RSSI.intValue); for (BTLEDeivceModel *device in _devices) { if ([[device.peripheralRef.identifier UUIDString] isEqualToString:[peripheral.identifier UUIDString]]) { return; } } if (peripheral.name != nil && peripheral.name != (id)[NSNull null]) { if ([peripheral.name rangeOfString:@"DKC"].location != NSNotFound || [peripheral.name rangeOfString:@"BlueZ"].location != NSNotFound) { BTLEDeivceModel *device = [self addPeripheral:peripheral]; if (![_devices containsObject:device]) { [_devices addObject:device]; } } } } - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { NSLog(@"Periphiral connected name : %@", peripheral.name); _isConnected = YES; [peripheral discoverServices:nil] ; } - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { NSLog(@"Periphiral disconnected: %@", peripheral.name); if( _delegate && [_delegate respondsToSelector:@selector(BLEDisConnected:)] ) { [_delegate BLEDisConnected:_conDevice] ; } _isConnected = NO; _conDevice = nil; } - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { NSLog(@"Periphiral failed to connect: %@", peripheral.name); } #pragma mark - CBPeripheralDelegate - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { NSLog(@"\nServices dicovered for peripheral %@:", peripheral.name); lastService = [peripheral.services lastObject]; for (CBService *service in peripheral.services) { [peripheral discoverCharacteristics:nil forService:service]; } } - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error { NSLog(@"\ncharacteristics dicovered for service : %@", service.UUID); if ([lastService.UUID isEqual:service.UUID]) { _conDevice = [self addPeripheral:peripheral]; lastService = nil; if( _delegate && [_delegate respondsToSelector:@selector(BLEConnected:)] ) { [_delegate BLEConnected:_conDevice] ; } } for (CBCharacteristic *characteristic in service.characteristics) { //NSLog(@"characteristic : %@", characteristic.UUID); // [self readValueSetNotifyValueForCharacteristic:characteristic peripheral:peripheral]; // [peripheral setNotifyValue:YES forCharacteristic:characteristic]; // [peripheral readValueForCharacteristic:characteristic]; } } - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { NSLog(@"Incoming: [%@], %@, %@, %@", [self getChrName:characteristic], characteristic.value, [self asciiStringValue:characteristic], [self hexStringValue:characteristic]); [self updateValueForDelegateWithCharacteristic:characteristic]; } -(void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray *)invalidatedServices { // device = peripheral; NSLog(@"peripheral : %@", peripheral); NSLog(@"didModifyServices : %@", invalidatedServices); } - (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { //_conDevice.peripheralRef = peripheral; NSLog(@"didWriteValueForCharacteristic : %@", characteristic.value) ; } //update characteristic 데이터 처리 - (void)updateValueForDelegateWithCharacteristic:(CBCharacteristic*)characteristic { //wlan list if ([self isReadWlanForCharacteristic:characteristic]) { if( _delegate && [_delegate respondsToSelector:@selector(BLEWLanUpdateWithKey:result:)] ) { [_delegate BLEWLanUpdateWithKey:[self getChrName:characteristic] result:[self asciiStringValue:characteristic]]; } } //ssid argument 읽음 else if ([[self getStrUUID:characteristic] isEquestToIgnoreCase: [self getServiceUUID:kBLEChrStSSIDArg]]) { if( _delegate && [_delegate respondsToSelector:@selector(BLEWiFiSSIDUpdate:)] ) { [_delegate BLEWiFiSSIDUpdate:[self asciiStringValue:characteristic]]; } } //dhcp 읽음 else if ([[self getStrUUID:characteristic] isEquestToIgnoreCase: [self getServiceUUID:kBLEChrStDHCPArg]]) { if( _delegate && [_delegate respondsToSelector:@selector(BLEWiFiDHCPUpdate:)] ) { [_delegate BLEWiFiDHCPUpdate:nil]; } } //wifi connection else if ([[self getStrUUID:characteristic] isEquestToIgnoreCase: [self getServiceUUID:kBLEChrRdConInfo]]) { if( _delegate && [_delegate respondsToSelector:@selector(BLEWiFiConnectionUpdate:)] ) { [_delegate BLEWiFiConnectionUpdate:characteristic]; } } //ssid else if ([self isReadConInfoForCharacteristic:characteristic]) { if( _delegate && [_delegate respondsToSelector:@selector(BLEWiFiConnectionInfoUpdateWithKey:result:)] ) { [_delegate BLEWiFiConnectionInfoUpdateWithKey:[self getChrName:characteristic] result:[self asciiStringValue:characteristic]]; } } } //wlan list read 인지 판단 - (BOOL)isReadWlanForCharacteristic:(CBCharacteristic*)characteristic { NSString *uuid = characteristic.UUID.UUIDString; BOOL rdWiFi = [uuid isEquestToIgnoreCase:[self getGatewayDicUUIDForKey:kBLEChrRdWiFiList1]] || [uuid isEquestToIgnoreCase:[self getGatewayDicUUIDForKey:kBLEChrRdWiFiList2]] || [uuid isEquestToIgnoreCase:[self getGatewayDicUUIDForKey:kBLEChrRdWiFiList3]]; return rdWiFi; } //wlan list read 인지 판단 - (BOOL)isReadConInfoForCharacteristic:(CBCharacteristic*)characteristic { NSString *uuid = characteristic.UUID.UUIDString; // static NSString *kBLEChrRdSSID = @"SSID_CHR_UUID"; // static NSString *kBLEChrRdBSSID = @"BSSID_CHR_UUID"; // static NSString *kBLEChrRnIpSet = @"WF_IP_SET_CHR_UUID"; // static NSString *kBLEChrRnIpAddr = @"WF_IP_ADDR_CHR_UUID"; BOOL conInfo = [uuid isEquestToIgnoreCase:[self getGatewayDicUUIDForKey:kBLEChrRdSSID]] || [uuid isEquestToIgnoreCase:[self getGatewayDicUUIDForKey:kBLEChrRdBSSID]] || [uuid isEquestToIgnoreCase:[self getGatewayDicUUIDForKey:kBLEChrRnIpSet]] || [uuid isEquestToIgnoreCase:[self getGatewayDicUUIDForKey:kBLEChrRnIpAddr]] ; return conInfo; } @end