单军华
2018-04-20 2197e837490f5083d18d9fdb97265d1e0f04832a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
 
 
#import <UIKit/UIKit.h>
#import "CBMoralManager.h"
#import "CBPeripheralExt.h"
#import "ResourceHandler.h"
#import "Utilities.h"
 
/*!
 *  @class CBMoralManager
 *
 *  @discussion Class to co-ordinate all the peripheral related operations
 *
 */
 
@interface CBMoralManager () <CBCentralManagerDelegate, CBPeripheralDelegate>
{
    CBCentralManager    *centralManager;
    NSMutableArray *peripheralListArray;
    
    void (^cbCommunicationHandler)(BOOL success, NSError *error);
    BOOL isTimeOutAlert;
}
@end
 
@implementation CBMoralManager
 
@synthesize cbCharacteristicDelegate;
@synthesize myPeripheral;
@synthesize myService;
@synthesize myCharacteristic;
@synthesize myCharacteristic2;
@synthesize serviceUUIDDict;
@synthesize cbDiscoveryDelegate;
@synthesize foundPeripherals;
@synthesize foundServices;
@synthesize characteristicDescriptors;
@synthesize characteristicProperties;
@synthesize bootLoaderFilesArray;
 
 
#define k_SERVICE_UUID_PLIST_NAME @"ServiceUUIDPList"
 
#pragma mark - Singleton Methods
 
+ (id)sharedManager {
    static CBMoralManager *sharedMyManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedMyManager = [[self alloc] init];
    });
    return sharedMyManager;
}
 
- (id)init {
    if (self = [super init])
    {
        centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
        foundPeripherals = [[NSMutableArray alloc] init];
        foundServices = [[NSMutableArray alloc] init];
        peripheralListArray = [[NSMutableArray alloc] init];
        serviceUUIDDict = [NSMutableDictionary dictionaryWithDictionary:[ResourceHandler getItemsFromPropertyList:k_SERVICE_UUID_PLIST_NAME]];
        bootLoaderFilesArray = nil;
    }
    return self;
}
 
 
#pragma mark - Discovery
/*                                Discovery                                   */
/****************************************************************************/
 
/*!
 *  @method startScanning
 *
 *  @discussion  To scans for peripherals that are advertising services.
 *
 */
 
- (void) startScanning
{
    NSLog(@"-------------------->startScanning");
    
    if((NSInteger)[centralManager state] == CBCentralManagerStatePoweredOn)
    {
        [cbDiscoveryDelegate bluetoothStateUpdatedToState:YES];
        NSDictionary *options=[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],CBCentralManagerScanOptionAllowDuplicatesKey, nil];
        [centralManager scanForPeripheralsWithServices:nil options:options];
    }
    else if ([centralManager state] == CBCentralManagerStateUnsupported)
    {
        [Utilities alert:@"CySmart" Message:@"This device does not support Bluetooth Low Energy"];
    }
}
 
 
/*!
 *  @method stopScanning
 *
 *  @discussion  To stop scanning for peripherals.
 *
 */
 
- (void) stopScanning
{
    
    [centralManager stopScan];
}
 
 
/*!
 *  @method peripheralWithPeripheral:advertisementData:RSSI
 *
 *  @param peripheral           A <code>CBPeripheral</code> object.
 *  @param advertisementData    A dictionary containing any advertisement and scan response data.
 *  @param RSSI                 The current RSSI of <i>peripheral</i>, in dBm. A value of <code>127</code> is reserved and indicates the RSSI
 *                                was not available.
 *
 *  @discussion  The methods handles the peripherals are to be displayed or not in the BLE Device List.
 Each time a new peripheral dicover will invoke [discoveryDidRefresh] method.
 *
 */
 
-(void)peripheralWithPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    
    if (![peripheralListArray containsObject:peripheral])
    {
        if(peripheral.state == CBPeripheralStateConnected)
        {
        }
        else
        {
            //Printing description of newPeriPheral->mAdvertisementData:
            //{
            //    kCBAdvDataIsConnectable = 1;
            //    kCBAdvDataLocalName = MORAL;
            //    kCBAdvDataServiceData =     {
            //        "0003CBBB-0000-1000-8000-00805F9B0131" = <bbcb>;
            //   };
            //}
            
            CBPeripheralExt *newPeriPheral = [[CBPeripheralExt alloc] init];
            newPeriPheral.mPeripheral = [peripheral copy];
            newPeriPheral.mAdvertisementData = [advertisementData copy];
            newPeriPheral.mRSSI = [RSSI copy];
            [peripheralListArray addObject:peripheral];
            [foundPeripherals addObject:newPeriPheral];
            [cbDiscoveryDelegate discoveryDidRefresh];
        }
    }
    
}
 
//  - This is called with the CBPeripheral class as its main input parameter. This contains the information about a BLE peripheral.
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    [self peripheralWithPeripheral:peripheral advertisementData:advertisementData RSSI:RSSI];
}
 
#pragma mark - Connection/Disconnection
 
/*!
 *  @method cancelTimeOutAlert
 *
 *  @discussion Method to cancel timeout alert
 *
 */
-(void)cancelTimeOutAlert
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeOutMethodForConnect) object:nil];
}
 
/*!
 *  @method timeOutMethodForConnect
 *  连接超时
 *  @discussion The methods invoke to cancel the connection request Because connection attempts do not time out,
 *
 */
-(void)timeOutMethodForConnect
{
    isTimeOutAlert = YES;
    [self cancelTimeOutAlert];
    [self disconnectPeripheral:myPeripheral];
    NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
    [errorDetail setValue:@"The Connection has Timed Out unexpectedly" forKey:NSLocalizedDescriptionKey];
    NSError *error = [NSError errorWithDomain:@"myDomain" code:100 userInfo:errorDetail];
    [self refreshPeripherals];
    cbCommunicationHandler(NO,error);
}
 
/*                        Connection/Disconnection                            */
/****************************************************************************/
 
 
/*!
 *  @method connectPeripheral:CompletionBlock
 *
 *  @param peripheral    The peripheral to which the central is attempting to connect.
 *
 *  @discussion     Establishes a local connection to a peripheral.
 *
 */
- (void) connectPeripheral:(CBPeripheral*)peripheral CompletionBlock:(void (^)(BOOL success, NSError *error))completionHandler
{
    
      if((NSInteger)[centralManager state] == CBCentralManagerStatePoweredOn)
      {
          cbCommunicationHandler = completionHandler ;
         
          if ([peripheral state] == CBPeripheralStateDisconnected)
          {
              [centralManager connectPeripheral:peripheral options:nil];
              LOG_INFO(@"[%@] %@",peripheral.name,CONNECTION_REQUEST);
              //[[LoggerHandler LogManager] LogData:[NSString stringWithFormat:@"[%@] %@",peripheral.name,CONNECTION_REQUEST]];
          }
          else
          {
              [centralManager cancelPeripheralConnection:peripheral];
          }
          
          [self performSelector:@selector(timeOutMethodForConnect) withObject:nil afterDelay:DEVICE_CONNECTION_TIMEOUT];
 
      }
    
 
}
 
/*!
 *  @method disconnectPeripheral:
 *
 *  @param peripheral    The peripheral which needs to be disconnected from central.
 *
 *  @discussion     Cancels an active or pending local connection to a peripheral.
 *
 */
 
- (void) disconnectPeripheral:(CBPeripheral*)peripheral
{
    if(peripheral)
    {
        [centralManager cancelPeripheralConnection:peripheral];
    }
}
 
- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    myPeripheral =  nil;
    myPeripheral = [peripheral copy];
    myPeripheral.delegate = self ;
    [myPeripheral discoverServices:nil];
    
    LOG_INFO(@"[%@] %@",peripheral.name,CONNECTION_ESTABLISH);
    LOG_INFO(@"[%@] %@",peripheral.name,SERVICE_DISCOVERY_REQUEST);
}
 
 
- (void) centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"Attempted connection to peripheral %@ failed: %@", [peripheral name], [error localizedDescription]);
     [self cancelTimeOutAlert];
     cbCommunicationHandler(NO,error);
}
 
 
- (void) centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    [self cancelTimeOutAlert];
 
    /*  Check whether the disconnection is done by the device */
    if (error == nil && !isTimeOutAlert)
    {
        NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
        [errorDetail setValue:@"The BLE device is disconnected" forKey:NSLocalizedDescriptionKey];
        NSError *disconnectError = [NSError errorWithDomain:@"myDomain" code:100 userInfo:errorDetail];
        
        LOG_INFO(@"[%@] %@",peripheral.name,DISCONNECTION_REQUEST);
        
        cbCommunicationHandler(NO,disconnectError);
    }
    else
    {
        isTimeOutAlert = NO;
        
        // Checking whether the disconnected device has pending firmware upgrade
        if ([[CBMoralManager sharedManager] bootLoaderFilesArray] != nil && error != nil)
        {
            NSMutableDictionary *errorDict = [NSMutableDictionary dictionary];
            [errorDict setValue:[NSString stringWithFormat:@"%@%@",[error.userInfo objectForKey:NSLocalizedDescriptionKey],FIRMWARE_DISCONNECTION_ERROR] forKey:NSLocalizedDescriptionKey];
            
            NSError *disconnectionError = [NSError errorWithDomain:@"myDomain" code:100 userInfo:errorDict];
            cbCommunicationHandler(NO,disconnectionError);
        }
        else
            cbCommunicationHandler(NO,error);
    }
 
    [self redirectToRootviewcontroller];
    LOG_INFO(@"[%@] %@",peripheral.name,DISCONNECTED);
    [self clearDevices];
}
 
/*!
 *  @method redirectToRootviewcontroller
 *
 *  @discussion     Pops all the view controllers on the stack except the root view controller and updates the display. This will redirect to BLE Devices Page which list all discovered peripherals,
 *
 */
 
-(void)redirectToRootviewcontroller
{
    if(cbDiscoveryDelegate)
    {
        [[(UIViewController*)cbDiscoveryDelegate navigationController] popToRootViewControllerAnimated:YES];
    }
    else if(cbCharacteristicDelegate)
    {
        [[(UIViewController*)cbCharacteristicDelegate navigationController] popToRootViewControllerAnimated:YES];
    }
}
 
 
 
#pragma mark - Disc Services
 
/*    Represents the current state of a CBCentralManager.                     */
/****************************************************************************/
 
 
 
// CBPeripheralDelegate - Invoked when you discover the peripheral's available services.
 
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    [self cancelTimeOutAlert];
    if(error == nil)
    {
        LOG_INFO(@"[%@] %@- %@",peripheral.name,SERVICE_DISCOVERY_STATUS,SERVICE_DISCOVERED);
        BOOL isCapsenseExist = NO;
        for (CBService *service in peripheral.services)
        {
 
            if (![foundServices containsObject:service]) {
                [foundServices addObject:service];
                if([service.UUID isEqual:CAPSENSE_SERVICE_UUID])
                {
                    isCapsenseExist = YES;
                    cbCharacteristicDelegate = nil;  
                     [myPeripheral discoverCharacteristics:nil forService:service];
                }
            }
        }
        if(isCapsenseExist == NO )
            cbCommunicationHandler(YES,nil);
    }
    else
    {
        LOG_INFO(@"[%@] %@- %@%@]",peripheral.name,SERVICE_DISCOVERY_STATUS,SERVICE_DISCOVERY_ERROR,[error.userInfo objectForKey:NSLocalizedDescriptionKey]);
 
        cbCommunicationHandler(NO,error);
        
    }
 
}
 
 
 
 
/*    Represents the current state of a CBCentralManager.                     */
/****************************************************************************/
 
#pragma mark - characteristic/
 
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    
    if([cbCharacteristicDelegate isKindOfClass:[CBMoralManager class]] || cbCharacteristicDelegate == nil)
    {
        cbCommunicationHandler(YES,nil);
    }
    else
    {
        [cbCharacteristicDelegate peripheral:peripheral didDiscoverCharacteristicsForService:service error:error];
    }
    
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    if (error)
    {
        if (!characteristic.isNotifying)
        {
            [Utilities logDataWithService:[ResourceHandler getServiceNameForUUID:characteristic.service.UUID] characteristic:[ResourceHandler getCharacteristicNameForUUID:characteristic.UUID] descriptor:nil operation:[NSString stringWithFormat:@"%@- %@%@",READ_RESPONSE,READ_ERROR,[error.userInfo objectForKey:NSLocalizedDescriptionKey]]];
        }
    }
    
    if([cbCharacteristicDelegate respondsToSelector:@selector(peripheral:didUpdateValueForCharacteristic:error:)])
        [cbCharacteristicDelegate peripheral:peripheral didUpdateValueForCharacteristic:characteristic error:error];
}
 
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    if([cbCharacteristicDelegate respondsToSelector:@selector(peripheral:didWriteValueForCharacteristic:error:)])
    [cbCharacteristicDelegate peripheral:peripheral didWriteValueForCharacteristic:characteristic error:error];
    
}
 
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    if([cbCharacteristicDelegate respondsToSelector:@selector(peripheral:didDiscoverDescriptorsForCharacteristic:error:)])
    [cbCharacteristicDelegate peripheral:peripheral didDiscoverDescriptorsForCharacteristic:characteristic error:error];
}
 
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error
{
    if (error)
    {
        [Utilities logDataWithService:[ResourceHandler getServiceNameForUUID:descriptor.characteristic.service.UUID] characteristic:[ResourceHandler getCharacteristicNameForUUID:descriptor.characteristic.UUID] descriptor:[Utilities getDiscriptorNameForUUID:descriptor.UUID] operation:[NSString stringWithFormat:@"%@- %@%@",READ_RESPONSE,READ_ERROR,[error.userInfo objectForKey:NSLocalizedDescriptionKey]]];
    }
    [cbCharacteristicDelegate peripheral:peripheral didUpdateValueForDescriptor:descriptor error:error];
}
 
 
#pragma mark - BLE State
 
/*    Represents the current state of a CBCentralManager.                     */
/****************************************************************************/
 
/*!
 *  @method clearDevices
 *
 *  @discussion     Clear all listed peripherals and services,
 *
 */
- (void) clearDevices
{
    [peripheralListArray removeAllObjects];
    [foundPeripherals removeAllObjects];
    [foundServices removeAllObjects];
}
 
/*
 Invoked when the central manager’s state is updated. (required)
 
 If the state is On then app start scanning for peripherals that are advertising services.
 If the state is Off then call method [clearDevices] and redirect to Home screen.
 
 */
 
 
- (void) centralManagerDidUpdateState:(CBCentralManager *)central
{
    
    switch ((NSInteger)[centralManager state])
    {
        case CBCentralManagerStatePoweredOff:
        {
            [self clearDevices];
            /* Tell user to power ON BT for functionality, but not on first run - the Framework will alert in that instance. */
            //Show Alert
            [self redirectToRootviewcontroller];
            [cbDiscoveryDelegate bluetoothStateUpdatedToState:NO];
            break;
        }
            
        case CBCentralManagerStateUnauthorized:
        {
            /* Tell user the app is not allowed. */
            [Utilities alert:@"CySmart" Message:@"The app is not authorized  to use bluetooth low energy"];
            break;
        }
            
        case CBCentralManagerStateUnknown:
        {
            /* Bad news, let's wait for another event. */
            [Utilities alert:@"CySmart" Message:@"The state of central manager is unknown."];
            break;
        }
            
        case CBCentralManagerStatePoweredOn:
        {
            [cbDiscoveryDelegate bluetoothStateUpdatedToState:YES];
            //暂时不需要马上检索
            //[self startScanning];
            break;
        }
            
        case CBCentralManagerStateResetting:
        {
            [self clearDevices];
            break;
        }
    }
    
}
 
/*!
 *  @method refreshPeripherals
 *
 *  @discussion      Clear all listed peripherals and services.
 Device scanning will add  peripherals that are advertising services.
 And check the status of Bluetooth and alert the user if Off,
 *
 */
 
- (void) refreshPeripherals
{
    [self clearDevices];
    if([centralManager state] == CBCentralManagerStatePoweredOff)
    {
        [Utilities alert:WARNING Message:@"Turn On Bluetooth to Allow App to Connect to Accessories"];
    }
//    [[CBMoralManager sharedManager] stopScanning];
    [[CBMoralManager sharedManager] startScanning];
}
 
 
 
 
@end