Core Bluetooth Tutorial for iOS: Heart Rate Monitor 에서 필요한 부분을 의역 했습니다.

Core Bluetooth 튜토리얼에서 심박측정기 같은 양립할수 있는 디바이스에서 데이터를 되찾고 연결하고 발견하는 방법을 배우게 될것입니다

오늘날 세계에서 유용한 제품(gadgets)이 널리 사용될때 이러한 장치들 사이의 통신은 이 제품을 사용하기 위해 이끌수도 있고 더 효과적인 방법으로 이러한 제품에 의해 제공된 정보로 이끌수도 있습니다. 이를 위해 애플은 심박수 센서, 디지털 온도 조절 장치, 운동장비 등 실제 기기와 통신할수 있는 Core Bluetooth framework를 도입 했습니다. BLE(Bluetooth Low Engergy) 를 통해 무선 기술에 연결할수 있다면 Core Bluetooth framework는 이것을 연결할수 있습니다.

이 튜토리얼에서 Core Bluetooth framework 의 핵심 개념 과 양립한 디바이스에서 데이터를 되찾고 연결하고 발견하는 방법을 배우게 될것입니다. 블루투스 심박수 센서와 함께 커뮤니케이션 하는 심박수 모니터링앱을 만들기 위해 이 기술을 사용할것 입니다.

https://www.amazon.com/gp/product/B007S088F4/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=B007S088F4&linkCode=as2&tag=raywend-20 이것은 우리가 사용할 심박수 센서 이지만 다른것을 사용해도 괜찮습니다.

먼저 블루투스에서 사용하는 용어들을 살펴봅니다: centrals, peripherals, services and, characteristices

Centrals and Peripherals

Bluetooth 디바이스는 central 또는 peripheral 입니다.

  • Central:Bluetooth 디바이스로부터 데이터를 받는 객체입니다
  • Peripheral:다른 디바이스에서 소비할 데이터를 발행하는 Bluetooth 디바이스 입니다.

Advertising Packets

Bluetooth peripherals 는 advertising packets의 형태로 가진 어떤 데이터들을 방송합니다. 이 패킷은 peripherals의 이름, 핵심 기능과 같은 정보들을 포함합니다. 또한 이들은 peripherals이 제공할수 있는 어떤 관계된 추가적인 데이터를 포함할수도 있습니다.

central의 작업은 advertising packets을 찾고, 어떤 perpherals 와 관계된걸 찾고 추가적인 정보를 위해 각 디바이스에 연결합니다.

Services and Characteristics

Advertising packets은 매우 작아서 많은 정보를 포함할수 없습니다. 더 많은 데이터를 공유하기 위해 central은 perpheral로 연결해야 합니다.

peripheral의 데이터는 servicescharacteristicㄴ로 정리 되어 집니다.

  • Service: 데이터 모음과 peripheral의 기능과 특정 기능을 설명하는 관련 동작의 모음 입니다. 예를 들어 심박수 센서는 Heart Rate 서비스를 가집니다. peripheral은 한개 이상의 서비스를 가질수 있습니다.
  • Characteristic: peripherals의 서비스에 대해서 더 자세한 설명을 제공합니다. 예를들어, 심박수 서비스는 분당 심장 박동 값을 특정한 Heart Rate Measurement가 포함되어 있습니다. 이 서비스는 한개이상의 characteristic를 가질수 있습니다. 또다른 characteristic는 Body sensor Location 인데 이것은 단순하게 센서가 의도된 바디인지 설명해주는 스트링 입니다.

각 service 와 characteristic는 16 비트 또는 128 비트 값의 UUID 입니다.

Getting Started

먼저 여기 에서 시작 프로젝트를 다운받습니다.

Note: iOS 시뮬레이터는 블루투스르 지원하지 않습니다.

Perparing for Core Bluetooth

먼저 Core Bluetooth framework를가져옵니다. HRMViewController.swift 를 열고 다음 코드를 추가합니다.

import CoreBluetooth

Core Bluetooth framework의 대부분 작업은 delegate methods를 통해서 이루어질것입니다. central은 CBCenteralManager에 의해서 대표되고 이들의 delegate는 CBCentralManagerDelegate 입니다. CBPeripheral은 peripheral 이고 이들의 dleegate는 CBPeripheralDelegate 입니다.

가장 먼저 해야하는 일은 CBCentralManagerDelegate를 채택하는 합니다.

extension HRMViewController: CBCentralManagerDelegate {
  func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
      } 
   }
}

이후 XCdoe의 도움을 받아 다음 처럼 추가합니다.

placeholder를 적당한 값으로 교체할수 있습니다.

switch central.state {
  case .unknown:
    print("central.state is .unknown")
  case .resetting:
    print("central.state is .resetting")
  case .unsupported:
    print("central.state is .unsupported")
  case .unauthorized:
    print("central.state is .unauthorized")
  case .poweredOff:
    print("central.state is .poweredOff")
  case .poweredOn:
    print("central.state is .poweredOn")
}

이 시점에서 빌드하고 실행합니다. 콘솔에는 아무것도 출력되지 않습니다 왜냐하면 실제 CBCentralManager를 생성하지 않았기 때문입니다.

bodySensorLocationLabel 레이블 아래에 다음 코드를 추가합니다.

var centralManager: CBCentralManager!

다음으로 viewDidLoad() 의 시작점에 다음 코드를 추가합니다.

centralManager = CBCentralManager(delegate: self, queue: nil)

빌드 하고 실행하면 다음 출력을 봐야합니다.

central.state is .poweredOn

Note: Bluetooth가 꺼져있다면 central.state is .porweredOff를 받을수 있습니다.

이제 central은 powered on 되었고 다음 단계는 central은 심박센서를 찾기 위함 입니다. Bluetooth-speak에서 central은 peripherals를 찾기 위핸 스캔이 필요합니다.

Scanning for Peripherals

추가할 많은 methods 에서 name을 바로 주는 대신 힌트를 줄것입니다. 이 경우에 당신이 스켄할수 있는 것과 함께 centralManager가 있는지 확인 해야 합니다.

centralManager 초기화 이후 라인에 centralManager.scan을 시작하고 당신이 사용하려는 매소드를 찾을수 있습니다.

scanForPeripherals(withServices: [CBUUID]?, options: [String: Any]?) 매소드 같아 보입니다. 이것은 선택하고 withServices매개변수에 nil을 설정하고 options 매개변수를 사용하지 않는다면 지웁니다. 결국 다음과 같은 형태가 될것입니다.

콘솔을 실행하고 API MISUSE 메세지에 주의합니다

API MISUSE: <CBCentralManager: 0x1c4462180> can only accept this command while in the powered on state

scanForPeripherals 매소드를 다음과 같이 이동 시킵니다.

case .poweredOn:
  print("central.state is .poweredOn")
  centralManager.scanForPeripherals(withServices: nil)
}

빌드 하고 실행하고 콘솔을 확인하면 API MISUSE 메시지가 더이상 보이지 않습니다. 하지만 심박기를 찾았나요?

아마 peripheral를 찾기 위해 delegate method 구현이 필요할것입니다. Bluetooth-speak 에서 peripheral찾는것은 discovering으로서 알려져 있고 사용할 delegate methods는 discover 이라는 단어를 가질것입니다.

` centralManagerDidUpdateState(_:) 매소드의 끝에 discover` 이라는 단어를 타이핑 시작하면 다음 메소드르 찾을수 있습니다.

해당 메소드를 선택하고 다음과 같이 작성합니다.

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
                    advertisementData: [String: Any], rssi RSSI: NSNumber) {
  print(peripheral)
}

빌드 하고 실행합니다. 주변에 얼마나 많은 gadgets들이 있는지에 따라서 다양한 블루투스 장치를 볼수 있ㅅ브니다.

<CBPeripheral: 0x1c4105fa0, identifier = D69A9892-...21E4, name = Your Computer Name, state = disconnected>
<CBPeripheral: 0x1c010a710, identifier = CBE94B09-...0C8A, name = Tile, state = disconnected>
<CBPeripheral: 0x1c010ab00, identifier = FCA1F687-...DC19, name = Your Apple Watch, state = disconnected>
<CBPeripheral: 0x1c010ab00, identifier = BB8A7450-...A69B, name = Polar H7 DCB69F17, state = disconnected>

이들 중 하나는 심박 측정기 이고. 이걸 입고 있어야 유효한 값을 받을수 있습니다.

Scanning for Peripherals with Specific Services

heart rate monitors에 대해서만 스켄할수는 없을까요? Bluetooth-speak 에서 제공한 Heart Rate 서비스를 제공한 peripherals에 대해서만 스캔할수 있습니다. 이것을 하기 위해 심박수 Heart Rate service를 위한 UUID가 필요합니다. Bluetooth services specification page 에서 heart rate를 검색하고 UUID를 기록하세요. 0x180D

import statements 바로 아래에 다음 코드를 추가합니다.

let heartRateServiceCBUUID = CBUUID(string: "0x180D")

scanForPeripherals(withServices: nil) 이후에 다음 코드를 업데이트 합니다.

centralManager.scanForPeripherals(withServices: [heartRateServiceCBUUID])

앱을 빌드 하고 실행하면 다음과 같은값을 볼수 있습니다.

<CBPeripheral: 0x1c0117220, identifier = BB8A7450-...A69B, name = Polar H7 DCB69F17, state = disconnected>
<CBPeripheral: 0x1c0117190, identifier = BB8A7450-...A69B, name = Polar H7 DCB69F17, state = disconnected>

다음으로 heart rate peripheral 을 참조하기 위해 저장하고 추가적인 peripherals 를 위한 스캔을 멈출것 입니다.

CGPeripherals 타입의 heartRatePeripheral 변수 인스턴스를 상단에 추가합니다.

var heartRatePeripheral: CBPeripheral!

peripheral 를 한번 찾은후, 참조를 저장하고 스캔을 멈춥니다. centralManager(_:didDiscover:advertisementData:rssi:), 에서 print(peripheral) 직후에 다음 코드를 추가합니다.

heartRatePeripheral = peripheral
centralManager.stopScan()

앱을 빌드하고 실행합니다. 그러면 다음의 출력을 봐야 합니다.

<CBPeripheral: 0x1c010ccc0, identifier = BB8A7450-...A69B, name = Polar H7 DCB69F17, state = disconnected>

Connecting to a Peripheral

peripheral 에서 데이터를 얻기 위해 이것을 연결 해야 합니다. centralManager.stopScan() 바로 직후에 centralManager.connect 타이핑을 시작하고 connect(peripheral: CBPeripheral, options: [String: Any]?을 볼수 있을것 입니다.

이것은 선택하고 코드를 다음처럼 작성합니다.

centralManager.connect(heartRatePeripheral)

좋습니다. 이제 심박수 장치와 연결되었습니다. 하지만 연결되었는지 어떻게 확인할수 있나요?

centralManager(_:didDiscover:advertisementData:rssi:) 메소드 직후에 centralManager(_:didConnect:):를 타이핑 하세요

플레이스홀더 코드를 다음과 같이 변경합니다.

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
  print("Connected!")
}

앱을 빌드하고 실행하면 다음과같은 메시지를 콘솔에서 볼수 있습니다.

Connected!

Discovering a Peripheral’s Services

이제 연결했고 다음 단계는 peripheral의 services를 발견 하는것입니다. 심박수 심박수 서비스와 함께 peripheral를 요청했고 이 서비스를 지원하는걸 알고 있어도 이것을 사용하기 위해 service 발견이 필요합니다.

연결 이후에 이들의 서비스를 찾기위해 peripheral의discoverServices(nil) 를 호출합니다.

heartRatePeripheral.discoverServices(nil)

UUIDs 에서 여기의 서비스를 위해 전달할수 있지만 심박수 모니터가 무엇을 더 할수 잇는지 모든 서비스를 찾아볼것입니다.

빌드 하고 실행하면 API MISUSE 메시지를 콘솔에서 볼것입니다.

API MISUSE: Discovering services for peripheral <CBPeripheral: 0x1c010f6f0, ...> while delegate is either nil or does not implement peripheral:didDiscoverServices:
API MISUSE: <CBPeripheral: 0x1c010f6f0, ...> can only accept commands while in the connected state

두번째 메시지는 peripheral은 연결중에만 커맨드가 동작함을 나타냅니다. 이 문제는 peripheral로 연결을 초기화 했지만 discoverServices(_:) 호출 이전에 연결이 끝난것을 기다리지 않았기 때문에 나타나는 메시지 입니다.

heartRatePeripheral.discoverServices(nil)centralManager(_:didConnect:)print("Connected!") 직후에 추가합니다.

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
  print("Connected!")
  heartRatePeripheral.discoverServices(nil)
}

앱을 빌드하고 실행합니다. 그러면 다음과 같은 API MISUSE라는 메시지를 볼수 있습니다.

API MISUSE: Discovering services for peripheral <CBPeripheral: ...> while delegate is either nil or does not implement peripheral:didDiscoverServices:

Core Bluetooth framework는 services를 발견했다고 지시했지만 peripheral(_:didDiscoverServices:) 델리게이트 메소드를 구현하지 않았습니다.

이 메소드의 이름은 peripheral delegate method 이므로 CBPeripheralDelegate를 준수해야 구현할수 있음을 알려줍니다.

extension HRMViewController: CBPeripheralDelegate {

}

아래 메소드를 추가합니다.

이 메소드는 발견된 services 목록을 제공하지 않지만 peripheral에 의해서 한개 이상의 services가 발견되었음을 주의 합니다. 이것은 peripheral 객체는 services의 목록을 주는 한개의 속성을 가진 객체라는 사실 입니다. 다음 코드를 새롭게 추가한 메소드에 추가합니다

guard let services = peripheral.services else { return }

for service in services {
  print(service)
}

BluetoothBestPractices

TP40013257-CH6-SW1

TP40013257-CH6-SW1