iOS端末をBLEのPeripheralにする

(2016-10-23)

CoreBluetoothプログラミングガイド

流れ

まず、CoreBluetooth.frameworkを追加する。

import CoreBluetooth

CBPeripheralManagerを生成。

peripheralManager = CBPeripheralManager(delegate: self, queue: nil)

stateが変化したらdelegateメソッドが呼ばれるので.poweredOnであることを確認できれば Managerの準備は完了。

public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager){        
    switch (peripheral.state){
    case .poweredOn:
        print("PeripheralManager state is ok")
        ready = true

    default:
        print("PeripheralManager state is ng:", peripheral.state)
        ready = false
    }
}

Characteristicを作成。CBCharacteristicProperties.read.union(CBCharacteristicProperties.notify)で、 Centralが読みにくることも、通知を受け取ることもできるようにし、CBAttributePermissions.readableでreadのみ許可する。 このvalueをnilにしておかないと、キャッシュされあとで変更できなくなる。

characteristic = CBMutableCharacteristic(
    type: CHARACTERISTIC_UUID,
    properties: CBCharacteristicProperties.read.union(CBCharacteristicProperties.notify),
    value:nil,
    permissions:CBAttributePermissions.readable)

このCharacteristicのServiceを作成し、Managerに登録する。

let service = CBMutableService(type: SERVICE_UUID, primary: true)
service.characteristics = [characteristic]
peripheralManager!.add(service)
ready = true         
public func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?){
    if(error != nil){
        print("Add Service error:", error)
    }else{
        print("Add Service ok")
    }
}

ServiceをAdvertiseする。

peripheral.startAdvertising([
    CBAdvertisementDataLocalNameKey: LOCAL_NAME,
    CBAdvertisementDataServiceUUIDsKey: [SERVICE_UUID]
    ])
public func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?){
    if(error != nil){
        print("Start Advertising error:", error)
    }else{
        print("Start Advertising ok")
    }
}

通知要求してきたCentralに向けて通知する。onSubscribedCentrals: nilで全てのCentralに送る。

peripheralManager!.updateValue(d, for: characteristic, onSubscribedCentrals: nil)

読みにきたときのdelegateメソッド。

public func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest){
    if (request.characteristic.uuid.isEqual(self.characteristic.uuid)) {
        if let value = self.characteristic.value{
            if (request.offset > value.count) {
                peripheral.respond(to: request, withResult: CBATTError.invalidOffset)
                print("Read fail: invalid offset")
                return;
            }
        }

        request.value = self.characteristic.value?.subdata(
            in: Range(uncheckedBounds: (request.offset, (self.characteristic.value?.count)! - request.offset))
        )
        peripheral.respond(to: request, withResult: CBATTError.success)
        print("Read success")
    }else{
        print("Read fail: wrong characteristic uuid:", request.characteristic.uuid)
    }
}

全体

エラーハンドリングは適当だけど、とりあえず動くものができた。

import CoreBluetooth

class BLEPeripheral : NSObject, CBPeripheralManagerDelegate {

    let CHARACTERISTIC_UUID = CBUUID(string:"5F83E23F-BCA1-42B3-B6F2-EA82BE46A93D")
    let SERVICE_UUID = CBUUID(string:"BF9CB85F-620C-4A67-BDD2-1A64213F74CA")
    let LOCAL_NAME = "my-ble"

    private var peripheralManager : CBPeripheralManager?
    private var characteristic: CBMutableCharacteristic
    private var ready = false

    public override init(){
        characteristic = CBMutableCharacteristic(
            type: CHARACTERISTIC_UUID,
            properties: CBCharacteristicProperties.read.union(CBCharacteristicProperties.notify),
            value:nil,
            permissions:CBAttributePermissions.readable)

        super.init()

        peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
    }

    public func update(_ data: Data?) -> Bool {
        if(ready){
            if let d = data{
                characteristic.value = d
                return peripheralManager!.updateValue(d, for: characteristic, onSubscribedCentrals: nil)
            }else{
                print("data is null")
            }
        }else{
            print("not ready")
        }
        return false
    }



    /*
     CBPeripheralManagerDelegate
    */

    public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager){        
        switch (peripheral.state){
        case .poweredOn:
            print("PeripheralManager state is ok")

            let service = CBMutableService(type: SERVICE_UUID, primary: true)
            service.characteristics = [characteristic]
            peripheralManager!.add(service)
            ready = true

        default:
            print("PeripheralManager state is ng:", peripheral.state)
            ready = false
        }
    }

    public func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?){
        if(error != nil){
            print("Add Service error:", error)
        }else{
            print("Add Service ok")
            peripheral.startAdvertising([
                CBAdvertisementDataLocalNameKey: LOCAL_NAME,
                CBAdvertisementDataServiceUUIDsKey: [SERVICE_UUID]
                ])
        }
    }

    public func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?){
        if(error != nil){
            print("Start Advertising error:", error)
        }else{
            print("Start Advertising ok")
        }
    }

    public func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest){
        var value: Data?
        switch request.characteristic.uuid {
        case characteristic.uuid:
            value = characteristic.value

        default: break
        }

        if let v = value{
            if (request.offset > v.count) {
                peripheral.respond(to: request, withResult: CBATTError.invalidOffset)
                print("Read fail: invalid offset")
                return;
            }

            request.value = v.subdata(
                in: Range(uncheckedBounds: (request.offset, v.count - request.offset))
            )
            peripheral.respond(to: request, withResult: CBATTError.success)
            print("Read success")
        }else{
            print("Read fail: wrong characteristic uuid:", request.characteristic.uuid)
        }
    }

    public func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic){
        print("Subscribe to", characteristic.uuid)
    }
}

現在の時間を1秒ごとに更新して通知がくることを確認した。

timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
timer!.fire()

public func update(){
    let now = String(format: "%.0f", arguments: [Date().timeIntervalSince1970])
    peripheral.update(now.data(using: String.Encoding.utf8))
}