Peripheral実装時のハマりポイント

Peripheralの実装をしていて2点ハマった部分があったので書き残しておきます。

Celtralの実装はたくさんの方が記事を書いてくれているのですがPeripheralの実装はだいぶ少なく手探りで解決策を探していたので大変でした。

wrightされた際のNotification

celtralからwrightされた際にNotificationを返す処理はble通信を行うアプリであれば頻繁に行いたい処理だと思いますが、落とし穴があります。

失敗例

私が失敗したコードは以下のものです。問題なく動きそうですがNotificationが送信されることはありません。

    //セントラルから書き込まれた値が飛んでくる
    func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
        for request in requests {
            if request.characteristic.uuid == "Notiricationを返すUUID" {
                //centralに通知したいデータ
                let data:[UInt8] = [0x01, 0x02, 0x03]
                self.configration.value = Data(bytes: data)
                self.manager.updateValue(self.configration.value!, for: self.configration, onSubscribedCentrals: nil)
                manager.respond(to: request, withResult: .success)
            }
        }
    }

解決策

解決策としてはupdateValueを送信する部分を非同期にする必要があります。サブスレッドになっていれば問題ないようでした。

Timerを使って以下のコードのように修正したところNotificationが返却されるようになりました。

    //セントラルから書き込まれた値が飛んでくる
    func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
        
        for request in requests {
            if request.characteristic.uuid == "Notiricationを返すUUID" {
            	Timer.scheduledTimer(withTimeInterval: 0, repeats: false, block: {_ in.  ⬅︎追加!!!
	                //centralに通知したいデータ
	                let data:[UInt8] = [0x01, 0x02, 0x03]
	                self.configration.value = Data(bytes: data)
	                self.manager.updateValue(self.configration.value!, for: self.configration, onSubscribedCentrals: nil)
                }
                manager.respond(to: request, withResult: .success)
            }
        }
    }

CharacteristicのReadValueの初期値

CBMutableCharacteristicの初期化時valueを設定することができますが、条件を満たしていない場合クラッシュします。

失敗例

テスト用のcharacterisiticを作成していたのでwrightのnotifyも使用するかと思い以下のように設定していました

let properties: CBCharacteristicProperties = [.notify, .read, .write]
let permissions: CBAttributePermissions = [.readable, .writeable]
var value = Data(bytes: [0x01])
            
let characteristic = CBMutableCharacteristic(type:  "1234",properties: properties, value: value, permissions: permissions)

ビルドしたところクラッシュし以下のようなログが出力されました。

2019-06-11 16:37:12.500421+0900 TestPeripheral[1838:176719] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Characteristics with cached values must be read-only'
*** First throw call stack:
(0x1d740e3a8 0x1d6613d00 0x1d7323780 0x1d7dfe32c 0x1dcf81da0 0x100da8d34 0x100daa1b8 0x1dcf8099c 0x1d7e70394 0x1d7e727d4 0x1d7e72130 0x1d7dbf688 0x1d7e6d860 0x1dcf7e42c 0x1010871f8 0x101088778 0x101090a34 0x101091778 0x101095fb8 0x1d739e024 0x1d7398cd4 0x1d7398254 0x1d95d7d8c 0x2046e04c0 0x100dafdc8 0x1d6e54fd8)
libc++abi.dylib: terminating with uncaught exception of type NSException

解決策

CBMutableCharacteristicのインスタンス作成時に値を設定するにはpropertyとpermissionの設定もread-onlyにする必要があるようです。

let properties: CBCharacteristicProperties = [.read]
let permissions: CBAttributePermissions = [.readable]
var value = Data(bytes: [0x01])
            
let characteristic = CBMutableCharacteristic(type:  "1234",properties: properties, value: value, permissions: permissions)