#ios
#Bluetooth5
#BT5

iOS11のCoreBluetoothでL2Cap Stream

iOS11のCoreBluetoothでL2Cap Stream

Bluetooth5ですよ!Bluetooth5!

というわけで、iOS同士でサンプルっぽいものを作ってみました。
iPhone7がBT Peripheral。iPad miniをBT Centralにしました。

ザックリ見てみましょう。
まずはPeripheral(CBPeripheralManager)から。

キモは2つか3つくらいありますね。

自前で定義するGATT ServiceにCBUUIDL2CAppSMCharacteristicString(UUID は ABDD3056-28FA-441D-A470-55A75A52553A になります )を追加します。
これがL2Capを使用する際に必要になるCharacteristicです。

peripheral delegateに新しく didOpenL2CAPChannel が追加されました。

[self.peripheralManager addService:self.service];
として、サービスを追加したあと

[self.peripheralManager publishL2CAPChannelWithEncryption:YES];
として、publishしてやります。日本語での意味はなんだろ?発行する??

そうすると didPublishL2CAPChannel が呼ばれ、CBL2CAPPSMの値が得られるのでそれをメンバとして保持しておきます。

順序としては、Service / Characteristic(L2Cap用のも)をそれぞれ追加。
publishL2CAPChannelWithEncryption する。
didPublishL2CAPChannel呼ばれる。
(誤解を恐れずに言うなら、TCPでいうListenした状態・・・みたいな感じ)

BLEの時と同じようにAdvertizeする。
でひとまず Peripheralはここで待機(CentralからScanされるまで)

Centralは
BLEの時と同じようにscanする。
Service / Characteristic探索する。
L2Cap用のCharacteristicが見つかったら Notification をONにしてCBL2CAPPSMの値を取得。
Notificationをここでストップする。
[peripheral openL2CAPChannel:psm];してL2Capのコネクション?を確立する。

Openした後は、Central / Periphralそれぞれで、CBL2CAPChannelにinputStream / outputStreamのプロパティがあるので、NSInputStream / NSOutputStreamを使ってデータのやり取りをする。

CBL2CAPPSMっていうのが、なんだろ?TCPでいうポート番号みたいなものだと思ってます(192っていう値が入る?固定なのかなぁ?)。

WWDC2017の動画くらいしか資料がなくて、2日3日かかっちゃいましたね。
サンプルは一式GitHubに置いてあるので、参考に・・・・なるんだろうか・・・。
まぁ、参考になったら、参考にしてやってください。
以下コード。コピペ。

Peripheral / スレーブ?です。

BTPeripheral.h
#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>


@protocol BTPeripheralDelegate<NSObject>
@optional

- (void)logDelegate:(NSString *)message;

@end


@interface BTPeripheral : NSObject<CBPeripheralManagerDelegate, NSStreamDelegate>
{
    NSString *kServiceUUID;
    NSString *kCharacteristicUUID;
    NSString* CBUUIDL2CAppSMCharacteristicString;
    CBL2CAPPSM psm;
}

@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
@property (nonatomic, strong) CBMutableCharacteristic *characteristic;
@property (nonatomic, strong) CBMutableCharacteristic *L2CapSMCharacteristic;

@property (nonatomic, strong) CBMutableService *service;
@property (nonatomic, strong) CBL2CAPChannel *l2capChannel;
//@property (nonatomic, strong) NSInputStream* inputStream;
@property (nonatomic, strong) NSOutputStream* outputStream;

@property(nonatomic, assign) id <BTPeripheralDelegate> delegate;

// ------------------------------
// CBPeripheralManagerDelegate
// ------------------------------

// Monitoring Changes to the Peripheral Manager’s State

-(void)peripheralManager:(CBPeripheralManager *)peripheral didOpenL2CAPChannel:(CBL2CAPChannel *)channel error:(NSError *)error;

// peripheralManagerDidUpdateState:
// Invoked when the peripheral manager's state is updated. (required)
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral;

// peripheralManager:willRestoreState:
// Invoked when the peripheral manager is about to be restored by the system.
- (void)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(NSDictionary *)dict;

// Adding Services

// peripheralManager:didAddService:error:
// Invoked when you publish a service, and any of its associated characteristics and characteristic descriptors, to the local Generic Attribute Profile (GATT) database.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error;

// Advertising Peripheral Data

// peripheralManagerDidStartAdvertising:error:
// Invoked when you start advertising the local peripheral device’s data.
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error;

// Monitoring Subscriptions to Characteristic Values

// peripheralManager:central:didSubscribeToCharacteristic:
// Invoked when a remote central device subscribes to a characteristic’s value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;

// peripheralManager:central:didUnsubscribeFromCharacteristic:
// Invoked when a remote central device unsubscribes from a characteristic’s value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;

// peripheralManagerIsReadyToUpdateSubscribers:
// Invoked when a local peripheral device is again ready to send characteristic value updates. (required)
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral;

// Receiving Read and Write Requests

// peripheralManager:didReceiveReadRequest:
// Invoked when a local peripheral device receives an Attribute Protocol (ATT) read request for a characteristic that has a dynamic value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request;

// peripheralManager:didReceiveWriteRequests:
// Invoked when a local peripheral device receives an Attribute Protocol (ATT) write request for a characteristic that has a dynamic value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests;

- (void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray *)invalidatedServices;

@end
BTPeripheral.m
#import "BTPeripheral.h"

@implementation BTPeripheral


-(id)init{
    // Do any additional setup after loading the view, typically from a nib.
    if(self = [super init]){
        // デフォルトの通知センターを取得する
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc addObserver:self selector:@selector(didEnterBackground:) name:@"didEnterBackground" object:nil];

        //テキトーなサービスキャラクタリスティック
        kServiceUUID = @"312700E2-E798-4D5C-8DCF-49908332DF9F";
        kCharacteristicUUID = @"FFA28CDE-6525-4489-801C-1C060CAC9767";

        //L2Cap用のCharacteristic
        CBUUIDL2CAppSMCharacteristicString = @"ABDD3056-28FA-441D-A470-55A75A52553A";

        self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];

    }
    return self;
}



- (void)setupService
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];

    // Creates the characteristic UUID
    CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
    CBUUID *l2capCharacteisticUUID = [CBUUID UUIDWithString:CBUUIDL2CAppSMCharacteristicString];

    //NSLog(@"[characteristicUUID.description] %@", characteristicUUID.description);

    // Creates the characteristic
    //self.characteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

    self.characteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyRead | CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

    self.L2CapSMCharacteristic = [[CBMutableCharacteristic alloc] initWithType:l2capCharacteisticUUID properties:CBCharacteristicPropertyRead | CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable];

    // Creates the service UUID
    CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];

    // Creates the service and adds the characteristic to it
    self.service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];

    // Sets the characteristics for this service
    [self.service setCharacteristics:@[self.characteristic, self.L2CapSMCharacteristic]];

    // Publishes the service
    [self.peripheralManager addService:self.service];
    [self.peripheralManager publishL2CAPChannelWithEncryption:YES];
}

-(void)stop
{
    [_outputStream close];
    //[_inputStream close];

    [self.peripheralManager unpublishL2CAPChannel:psm];
}

-(void)sendStreamData
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    NSString* value = @"Hello L2Cap Stream data...";
    NSData* data = [value dataUsingEncoding:NSUTF8StringEncoding];
    [_outputStream write:[data bytes] maxLength:[data length]];
}


// ------------------------------
// NSStreamDelegate
// ------------------------------

-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    NSLog(@"%@", aStream);

    switch (eventCode) {
        case NSStreamEventOpenCompleted:
            break;
        case NSStreamEventHasSpaceAvailable:
            [self sendStreamData];
            break;
        case NSStreamEventHasBytesAvailable:
            break;
        case NSStreamEventErrorOccurred:
            break;
        case NSStreamEventEndEncountered:
            break;
        case NSStreamEventNone:
            break;
        default:
            break;
    }
}

// ------------------------------
// CBPeripheralManagerDelegate
// ------------------------------

-(void)peripheralManager:(CBPeripheralManager *)peripheral didOpenL2CAPChannel:(CBL2CAPChannel *)channel error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    NSLog(@"Open L2Cap channel...");
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];
    _l2capChannel = channel;

    _outputStream = _l2capChannel.outputStream;
    //_inputStream = _l2capChannel.inputStream;

    _outputStream.delegate = self;
    //_inputStream.delegate = self;

    [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                            forMode:NSDefaultRunLoopMode];
    [_outputStream open];

    //[_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
    //                         forMode:NSDefaultRunLoopMode];
    //[_inputStream open];

}

-(void)peripheralManager:(CBPeripheralManager *)peripheral didPublishL2CAPChannel:(CBL2CAPPSM)PSM error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];
    psm = PSM;
    NSLog(@"Listen L2Cap channel...");
    NSLog(@"PSM: %d", psm);
}

-(void)peripheralManager:(CBPeripheralManager *)peripheral didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];
}

// Monitoring Changes to the Peripheral Manager’s State

// CBPeripheralManager が初期化されたり状態が変化した際に呼ばれるデリゲートメソッド
// peripheralManagerDidUpdateState:
// Invoked when the peripheral manager's state is updated. (required)
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
{
    //_manager = manager;
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];

    switch (peripheral.state) {
        case CBManagerStatePoweredOn:
            NSLog(@"%ld, CBManagerStatePoweredOn", (long)peripheral.state);
            // PowerOn なら,デバイスのセッティングを開始する.
             [self setupService];
            break;
        case CBManagerStatePoweredOff:
            NSLog(@"%ld, CBManagerStatePoweredOff", (long)peripheral.state);
            [self stop];
            break;
        case CBManagerStateResetting:
            NSLog(@"%ld, CBManagerStateResetting", (long)peripheral.state);
            break;
        case CBManagerStateUnauthorized:
            NSLog(@"%ld, CBManagerStateUnauthorized", (long)peripheral.state);
            break;
        case CBManagerStateUnsupported:
            NSLog(@"%ld, CBManagerStateUnsupported", (long)peripheral.state);
            break;
        case CBManagerStateUnknown:
            NSLog(@"%ld, CBManagerStateUnknown", (long)peripheral.state);
            break;
        default:
            break;
    }
}

// peripheralManager:willRestoreState:
// Invoked when the peripheral manager is about to be restored by the system.
- (void)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(NSDictionary *)dict
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// Adding Services

// peripheralManager:didAddService:error:
// Invoked when you publish a service, and any of its associated characteristics and characteristic descriptors, to the local Generic Attribute Profile (GATT) database.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];

    if(error){
        NSLog(@"[error] %@", [error localizedDescription]);
    }else{
        // Starts advertising the service
        [self.peripheralManager startAdvertising:@{CBAdvertisementDataLocalNameKey : @"mokyu", CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:kServiceUUID]] }];
        NSLog(@"start advertising");
    }
}

// Advertising Peripheral Data

// peripheralManagerDidStartAdvertising:error:
// Invoked when you start advertising the local peripheral device’s data.
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];

    if(error){
        NSLog(@"[error] %@", [error localizedDescription]);
    }
}

// Monitoring Subscriptions to Characteristic Values

// peripheralManager:central:didSubscribeToCharacteristic:
// Invoked when a remote central device subscribes to a characteristic’s value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];
}

// peripheralManager:central:didUnsubscribeFromCharacteristic:
// Invoked when a remote central device unsubscribes from a characteristic’s value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];
}

// peripheralManagerIsReadyToUpdateSubscribers:
// Invoked when a local peripheral device is again ready to send characteristic value updates. (required)
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];
}

// Receiving Read and Write Requests

// peripheralManager:didReceiveReadRequest:
// Invoked when a local peripheral device receives an Attribute Protocol (ATT) read request for a characteristic that has a dynamic value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];

    CBCharacteristic* characteristic = request.characteristic;

    if([characteristic.UUID.UUIDString isEqualToString:kCharacteristicUUID]){
        uint battery_level = 39;
        NSData *dataBatteryLevel = [NSData dataWithBytes:&battery_level length:sizeof(battery_level)];
        request.value = dataBatteryLevel;
        [self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
    }else if([characteristic.UUID.UUIDString isEqualToString:CBUUIDL2CAppSMCharacteristicString]){
        uint16_t value = psm;
        NSData* psmValue = [NSData dataWithBytes:&value length:sizeof(value)];
        request.value = psmValue;
        [self.peripheralManager respondToRequest:request withResult:CBATTErrorSuccess];
    }
}

// peripheralManager:didReceiveWriteRequests:
// Invoked when a local peripheral device receives an Attribute Protocol (ATT) write request for a characteristic that has a dynamic value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    [self.delegate logDelegate:NSStringFromSelector(_cmd)];
}

// peripheral:didModifyServices:
// Invoked when a peripheral’s services have changed.
- (void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray *)invalidatedServices
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

-(void)didEnterBackground:(NSNotification *)notification
{
    [self stop];
}

@end

こっからCentral / マスタ?ね。

#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>
#import "BTPeripheral.h"

@interface BTManager : NSObject<CBCentralManagerDelegate, CBPeripheralDelegate>
{
    NSString *kServiceUUID;
}

@property (strong, nonatomic) CBCentralManager *centralManager;
@property (strong, nonatomic) CBPeripheral *peripheral;
@property (strong, nonatomic) BTPeripheral* p;


+(id)sharedInstance;
- (id)init __attribute__((unavailable("init is not available")));

// ------------------------------
// CBCentralManagerDelegate
// ------------------------------

// Monitoring Connections with Peripherals

// centralManager:didConnectPeripheral:
// Invoked when a connection is successfully created with a peripheral.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;

// centralManager:didDisconnectPeripheral:error:
// Invoked when an existing connection with a peripheral is torn down.
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;

// centralManager:didFailToConnectPeripheral:error:
// Invoked when the central manager fails to create a connection with a peripheral.
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;

// Discovering and Retrieving Peripherals

// centralManager:didDiscoverPeripheral:advertisementData:RSSI:
// Invoked when the central manager discovers a peripheral while scanning.
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI;

// centralManager:didRetrieveConnectedPeripherals:
// Invoked when the central manager retrieves a list of peripherals currently connected to the system.
- (void)centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals;

// centralManager:didRetrievePeripherals:
// Invoked when the central manager retrieves a list of known peripherals.
- (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals;

// Monitoring Changes to the Central Manager’s State

// centralManagerDidUpdateState:
// Invoked when the central manager’s state is updated. (required)
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;

// centralManager:willRestoreState:
// Invoked when the central manager is about to be restored by the system.
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary *)dict;

// ------------------------------
// CBPeripheralDelegate
// ------------------------------

// Discovering Services

// peripheral:didDiscoverServices:
// Invoked when you discover the peripheral’s available services.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error;
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error;

@end
BTManager.m
#import "BTManager.h"
#import "BTPeripheral.h"

@implementation BTManager

static BTManager* me;


+(id)sharedInstance
{

    @synchronized(self) {
        if(!me){
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                me = [[self alloc] initWithSelf];
            });
        }
    }
    return me;
}


- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (id)init
{
    [NSException raise:NSGenericException
                format:@"Disabled. Use +[[%@ alloc] %@] instead",
     NSStringFromClass([self class]),
     NSStringFromSelector(@selector(initWithSelf))];
    return nil;
}

-(id)initWithSelf
{
    if(self){
        self = [super init];
    }

    kServiceUUID = @"312700E2-E798-4D5C-8DCF-49908332DF9F";
    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];

    return self;

}

-(void)start
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    // 単一デバイスの発見イベントを重複して発行させるか?
    CBUUID* uuid = [CBUUID UUIDWithString:kServiceUUID];
    [self.centralManager scanForPeripheralsWithServices: @[uuid]
                                                options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];
}


-(void)stop
{
    if(_peripheral != nil){
        _peripheral.delegate = nil;
        [self.centralManager cancelPeripheralConnection:_peripheral];
    }
}

// ------------------------------
// CBCentralManagerDelegate
// ------------------------------

// Monitoring Connections with Peripherals

// centralManager:didConnectPeripheral:
// Invoked when a connection is successfully created with a peripheral.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    //NSLog(@"%@", NSStringFromSelector(_cmd));
    //NSLog(@"%@", peripheral.description);

    // Clears the data that we may already have
    // Sets the peripheral delegate
    [peripheral setDelegate:self];

    // Asks the peripheral to discover the service
    [peripheral discoverServices:@[ [CBUUID UUIDWithString:kServiceUUID] ]];
}

// centralManager:didDisconnectPeripheral:error:
// Invoked when an existing connection with a peripheral is torn down.
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    //NSLog(@"%@", NSStringFromSelector(_cmd));
    //if(error){
    //        NSLog(@"[error] %@", [error localizedDescription]);
    //        NSLog(@"[error] %@", [error localizedFailureReason]);
    //        NSLog(@"[error] %@", [error localizedRecoverySuggestion]);
    //}else{
    //NSLog(@"disconnect");

    if([self.peripheral isEqual:peripheral]){
        self.peripheral.delegate = nil;
        self.peripheral = nil;
    }
    //}
}

// centralManager:didFailToConnectPeripheral:error:
// Invoked when the central manager fails to create a connection with a peripheral.
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// Discovering and Retrieving Peripherals

// デバイス発見時
// centralManager:didDiscoverPeripheral:advertisementData:RSSI:
// Invoked when the central manager discovers a peripheral while scanning.
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    //NSLog(@"%@", NSStringFromSelector(_cmd));
    //NSLog(@"[RSSI] %@", RSSI);


    self.peripheral = peripheral;
    //NSLog(@"Connecting to pripheral %@", peripheral);
    // 発見されたデバイスに接続
    [self.centralManager connectPeripheral:peripheral options:nil];
    [self.centralManager stopScan];
}


// centralManager:didRetrieveConnectedPeripherals:
// Invoked when the central manager retrieves a list of peripherals currently connected to the system.
- (void)centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// centralManager:didRetrievePeripherals:
// Invoked when the central manager retrieves a list of known peripherals.
- (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// Monitoring Changes to the Central Manager’s State

// CBCentralManager が初期化されたり状態が変化した際に呼ばれるデリゲートメソッド
// centralManagerDidUpdateState:
// Invoked when the central manager’s state is updated. (required)
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    //NSLog(@"%@", NSStringFromSelector(_cmd));
    switch (central.state) {
        case CBManagerStatePoweredOn:
            //NSLog(@"CBCentralManagerStatePoweredOn");
            [self start];
            break;
        case CBManagerStatePoweredOff:
            //NSLog(@"CBCentralManagerStatePoweredOff");
            [self stop];
            break;
        case CBManagerStateResetting:
            //NSLog(@"CBCentralManagerStateResetting");
            break;
        case CBManagerStateUnauthorized:
            //NSLog(@"CBCentralManagerStateUnauthorized");
            break;
        case CBManagerStateUnsupported:
            //NSLog(@"CBCentralManagerStatePoweredOn");
            break;
        case CBManagerStateUnknown:
            //NSLog(@"CBCentralManagerStateUnknown");
            break;
        default:
            break;
    }
}

// centralManager:willRestoreState:
// Invoked when the central manager is about to be restored by the system.
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary *)dict
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}


// ------------------------------
// CBPeripheralDelegate
// ------------------------------

// Discovering Services

// peripheral:didDiscoverServices:
// Invoked when you discover the peripheral’s available services.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    //NSLog(@"%@", NSStringFromSelector(_cmd));
    if(error){
        NSLog(@"[error] %@", [error localizedDescription]);
    }else{

        for(CBService *service in peripheral.services) {
            if([service.UUID.UUIDString isEqualToString:kServiceUUID]){
                //NSLog(@"hoge");
                _p = [[BTPeripheral alloc] initWithServicePeripheral:peripheral service:service];
            }
        }
    }
}

-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    ;;
}
@end
BTPeripheral.h
#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>


#define CBUUIDL2CAPPSMCharacteristicString @"ABDD3056-28FA-441D-A470-55A75A52553A"

@interface BTPeripheral : NSObject<CBPeripheralDelegate, NSStreamDelegate>
{
    NSString *kCharacteristicUUID;
    NSString* CBUUIDL2CAppSMCharacteristicString;
    CBL2CAPPSM psm;
}

@property(nonatomic, strong)CBPeripheral* peripheral;
@property(nonatomic, strong)CBService* service;
@property(nonatomic, strong)NSInputStream* inputStream;
//@property(nonatomic, strong)NSOutputStream* outputStream;
@property(nonatomic, strong)CBL2CAPChannel* l2cap;

- (id)init __attribute__((unavailable("init is not available")));
- (id)initWithServicePeripheral:(CBPeripheral *)peripheral service:(CBService *)service;

// ------------------------------
// CBPeripheralDelegate
// ------------------------------

-(void)peripheral:(CBPeripheral *)peripheral didOpenL2CAPChannel:(CBL2CAPChannel *)channel error:(NSError *)error;

// Discovering Services

// peripheral:didDiscoverServices:
// Invoked when you discover the peripheral’s available services.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error;

// peripheral:didDiscoverIncludedServicesForService:error:
// Invoked when you discover the included services of a specified service.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(NSError *)error;

//Discovering Characteristics and Characteristic Descriptors

// peripheral:didDiscoverCharacteristicsForService:error:
// Invoked when you discover the characteristics of a specified service.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error;

// peripheral:didDiscoverDescriptorsForCharacteristic:error:
// Invoked when you discover the descriptors of a specified characteristic.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;

// Retrieving Characteristic and Characteristic Descriptor Values

// peripheral:didUpdateValueForCharacteristic:error:
// Invoked when you retrieve a specified characteristic’s value, or when the peripheral device notifies your app that the characteristic’s value has changed.
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;

// peripheral:didUpdateValueForDescriptor:error:
// Invoked when you retrieve a specified characteristic descriptor’s value.
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error;

// Writing Characteristic and Characteristic Descriptor Values

// peripheral:didWriteValueForCharacteristic:error:
// Invoked when you write data to a characteristic’s value.
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;

// peripheral:didWriteValueForDescriptor:error:
// Invoked when you write data to a characteristic descriptor’s value.
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error;

// Managing Notifications for a Characteristic’s Value

// peripheral:didUpdateNotificationStateForCharacteristic:error:
// Invoked when the peripheral receives a request to start or stop providing notifications for a specified characteristic’s value.
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;

// Retrieving a Peripheral’s Received Signal Strength Indicator (RSSI) Data

// peripheralDidUpdateRSSI:error:
// Invoked when you retrieve the value of the peripheral’s current RSSI while it is connected to the central manager.
- (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(NSError *)error;

// Monitoring Changes to a Peripheral’s Name or Services

// peripheralDidUpdateName:
// Invoked when a peripheral’s name changes.
- (void)peripheralDidUpdateName:(CBPeripheral *)peripheral;

// peripheral:didModifyServices:
// Invoked when a peripheral’s services have changed.
- (void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray *)invalidatedServices;


@end
BTPeripheral.m
#import "BTPeripheral.h"

@implementation BTPeripheral

- (id)init
{
    [NSException raise:NSGenericException
                format:@"Disabled. Use +[[%@ alloc] %@] instead",
     NSStringFromClass([self class]),
     NSStringFromSelector(@selector(initWithServicePeripheral:service:))];
    return nil;
}


- (id)initWithServicePeripheral:(CBPeripheral *)peripheral service:(CBService *)service
{
    if(self = [super init]){
        kCharacteristicUUID = @"FFA28CDE-6525-4489-801C-1C060CAC9767";
        CBUUIDL2CAppSMCharacteristicString = @"ABDD3056-28FA-441D-A470-55A75A52553A";

        NSLog(@"peripheral start...");

        self.peripheral = peripheral;
        self.peripheral.delegate = self;
        self.service = service;

        NSArray* characteristics = @[
                                     [CBUUID UUIDWithString:kCharacteristicUUID],
                                     [CBUUID UUIDWithString:CBUUIDL2CAppSMCharacteristicString],
                                     ];
        [self.peripheral discoverCharacteristics:characteristics forService:service];

    }
    return self;
}

-(void)receiveStreamData
{
    NSLog(@"%@", NSStringFromSelector(_cmd));

    NSMutableData* data = [NSMutableData data];
    uint8_t buf[1024];
    [_inputStream read:buf maxLength:1024];
    [data appendBytes:buf length:sizeof(buf)];

    NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"receiveStreamData: %@", str);
}


// ------------------------------
// NSStreamDelegate
// ------------------------------

-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    NSLog(@"%@", aStream);

    switch (eventCode) {
        case NSStreamEventOpenCompleted:

            break;
        case NSStreamEventHasSpaceAvailable:
            break;
        case NSStreamEventHasBytesAvailable:
            [self receiveStreamData];
            break;
        case NSStreamEventErrorOccurred:
            break;
        case NSStreamEventEndEncountered:
            break;
        case NSStreamEventNone:
            break;
        default:
            break;
    }
}


// ------------------------------
// CBPeripheralDelegate
// ------------------------------

-(void)peripheral:(CBPeripheral *)peripheral didOpenL2CAPChannel:(CBL2CAPChannel *)channel error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));

    if(error){
        NSLog(@"%@", error);
    }
    _l2cap = channel;
    //_outputStream = _l2cap.outputStream;
    _inputStream = _l2cap.inputStream;
    //_outputStream.delegate = self;
    _inputStream.delegate = self;

    //[_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
    //                        forMode:NSDefaultRunLoopMode];
    //[_outputStream open];

    [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
                       forMode:NSDefaultRunLoopMode];
    [_inputStream open];

}


// Discovering Services

// peripheral:didDiscoverServices:
// Invoked when you discover the peripheral’s available services.

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));;;
}

//Discovering Characteristics and Characteristic Descriptors

// peripheral:didDiscoverCharacteristicsForService:error:
// Invoked when you discover the characteristics of a specified service.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));

    if(error){
        NSLog(@"[error] %@", [error localizedDescription]);
    }else{
        for (CBCharacteristic *characteristic in service.characteristics) {
            if ([characteristic.UUID.UUIDString isEqualToString:kCharacteristicUUID]) {
                NSLog(@"characteristics is found!");
                [peripheral setNotifyValue:YES forCharacteristic:characteristic];
            }else if([characteristic.UUID.UUIDString isEqualToString:CBUUIDL2CAppSMCharacteristicString]){
                [peripheral setNotifyValue:YES forCharacteristic:characteristic];
            }
        }
    }
}
// peripheral:didDiscoverIncludedServicesForService:error:
// Invoked when you discover the included services of a specified service.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverIncludedServicesForService:(CBService *)service error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}



// peripheral:didDiscoverDescriptorsForCharacteristic:error:
// Invoked when you discover the descriptors of a specified characteristic.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}



// Retrieving Characteristic and Characteristic Descriptor Values

// peripheral:didUpdateValueForCharacteristic:error:
// Invoked when you retrieve a specified characteristic’s value, or when the peripheral device notifies your app that the characteristic’s value has changed.
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    if(error){
        NSLog(@"[error] %@", [error localizedDescription]);
    }//else{
        if([characteristic.UUID.UUIDString isEqualToString:kCharacteristicUUID]){
            uint value;
            NSData* data = characteristic.value;
            [data getBytes:&value length:sizeof(uint)];

            NSLog(@"data: %d", value);
        }else if([characteristic.UUID.UUIDString isEqualToString:CBUUIDL2CAppSMCharacteristicString]){
            uint16_t value;
            NSData* data = characteristic.value;
            [data getBytes:&value length:sizeof(uint16_t)];
            psm = value;

            NSLog(@"PSM: %d", value);
            [peripheral openL2CAPChannel:psm];
            [peripheral setNotifyValue:NO forCharacteristic:characteristic];
        }

    //}
}

// peripheral:didUpdateValueForDescriptor:error:
// Invoked when you retrieve a specified characteristic descriptor’s value.
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// Writing Characteristic and Characteristic Descriptor Values

// peripheral:didWriteValueForCharacteristic:error:
// Invoked when you write data to a characteristic’s value.
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    //NSLog(@"write characteristic");
    //NSLog(@"%@", NSStringFromSelector(_cmd));

}

// peripheral:didWriteValueForDescriptor:error:
// Invoked when you write data to a characteristic descriptor’s value.
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// Managing Notifications for a Characteristic’s Value

// peripheral:didUpdateNotificationStateForCharacteristic:error:
// Invoked when the peripheral receives a request to start or stop providing notifications for a specified characteristic’s value.
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    if(error){
        NSLog(@"[error] %@", [error localizedDescription]);
    }else{
        // Notification has started
        if (characteristic.isNotifying) {
            if([characteristic.UUID.UUIDString isEqualToString:kCharacteristicUUID]){
                [peripheral readValueForCharacteristic:characteristic];
            }else if([characteristic.UUID.UUIDString isEqualToString:CBUUIDL2CAppSMCharacteristicString]){
                [peripheral readValueForCharacteristic:characteristic];
            }
        }
    }

}

// Retrieving a Peripheral’s Received Signal Strength Indicator (RSSI) Data

// peripheralDidUpdateRSSI:error:
// Invoked when you retrieve the value of the peripheral’s current RSSI while it is connected to the central manager.
- (void)peripheralDidUpdateRSSI:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// Monitoring Changes to a Peripheral’s Name or Services

// peripheralDidUpdateName:
// Invoked when a peripheral’s name changes.
- (void)peripheralDidUpdateName:(CBPeripheral *)peripheral
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// peripheral:didModifyServices:
// Invoked when a peripheral’s services have changed.
- (void)peripheral:(CBPeripheral *)peripheral didModifyServices:(NSArray *)invalidatedServices
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

@end