11.6. 서비스와 특성(characteristic)¶
GAP가 두 장치를 열린 연결 상태로 만들고 나면, 그 위 계층인 Generic Attribute Profile, 즉 GATT가 그 연결을 통해 흐르는 바이트에 의미를 부여해야 합니다. 여기서 BLE의 선택은 특이합니다. TCP가 원시 바이트 스트림을 노출하고 자체적인 프레이밍을 고안하는 일을 애플리케이션에 맡기는 반면, GATT는 한쪽이 호스팅하고 다른 쪽이 읽고 쓰고 구독하는 작은 키/값 데이터베이스를 노출합니다.
그 데이터베이스가 바로 애플리케이션 설계자들이 BLE 작업 시간의 대부분을 고민하며 보내는 대상입니다. 카메라가 휴대폰에 게시하는 것, 카메라가 원격 센서에서 관찰하는 것, 블루투스 키보드가 어느 키가 눌렸는지 호스트에 알리는 방식 – 이 모두가 어딘가의 GATT 데이터베이스에 들어 있는 특성 값입니다.
11.6.1. 하나가 아니라 두 개의 역할 축¶
흔한 혼동의 원인: peripheral / central과 server / client는 동의어가 아니라 서로 독립적인 두 개의 축입니다.
Peripheral과 central은 연결 시점에 정해지는 GAP 역할입니다. peripheral은 광고(advertise)하고 연결을 받으며, central은 스캔하고 연결을 시작합니다. 이는 링크가 성립되는 순간에 확정되며 이후에는 바뀌지 않습니다.
Server와 client는 특성 연산마다 정해지는 GATT 역할입니다. server는 특성을 호스팅하고, client는 그것을 읽거나 쓰거나 구독합니다.
이 두 축은 명세상 서로 분리되어 있습니다. peripheral은 대개 server이고(심박수 스트랩이 측정값을 게시), central은 대개 client이지만(휴대폰이 그것을 읽음), BLE는 어떤 조합이든 허용합니다 – peripheral이 방금 연결된 central에서 특성을 발견할 수도 있고, 하나의 연결이 양쪽에서 동시에 서비스를 호스팅할 수도 있습니다.
대부분의 카메라 애플리케이션은 관례적인 조합(peripheral + server, 또는 central + client)을 따르므로, 이 섹션의 나머지 부분에서는 관례적인 경우를 설명할 때 이들을 하나의 축으로 취급합니다. 구분이 중요한 경우에는 두 용어를 모두 명시적으로 표기합니다.
11.6.2. 데이터베이스 내부¶
GATT 데이터베이스는 트리입니다. 리프(leaf)는 실제 바이트를 담고, 가지(branch)는 관련 리프들을 사람이 이해할 수 있는 단위로 묶습니다.
GATT 데이터베이스. 서비스는 특성을 묶고, 특성은 애플리케이션의 바이트를 담으며, 디스크립터는 특성에 관한 메타데이터를 담습니다.¶
노드에는 세 종류가 있습니다:
서비스(service)는 관련 값들의 논리적 그룹입니다. Bluetooth SIG는 흔한 사용 사례에 대해 표준 서비스 정의를 게시합니다 – 배터리 잔량용 Battery Service, 온도 / 습도 / 압력용 Environmental Sensing, 심박수 모니터용 Heart Rate – 덕분에 휴대폰의 범용 앱이 한 번도 본 적 없는 서비스를 인식할 수 있습니다. 애플리케이션은 SIG가 표준화하지 않은 것들을 위해 자체 서비스를 자유롭게 정의할 수도 있습니다.
특성(characteristic)은 서비스 안에 있는 하나의 이름 붙은 값입니다. Battery 서비스에는 단 하나의 특성 – 1바이트 백분율인 Battery Level – 이 있습니다. Environmental Sensing에는 온도, 습도, 압력 등에 대한 별도의 특성이 있습니다. 특성은 GATT 연산의 단위입니다 – 특성을 읽고, 특성을 쓰고, 특성을 구독합니다.
descriptor(디스크립터)는 characteristic에 부착된 메타데이터입니다. 일부 디스크립터는 표준화되어 있습니다 – Client Characteristic Configuration Descriptor(CCCD)가 가장 유명한데, 여기에 쓰기를 하는 것이 클라이언트가 서버에게 “이 characteristic에 대한 알림을 보내라”고 알리는 방법이기 때문입니다. 다른 것들은 사용자 정의이며 표현 형식이나 확장 속성 같은 것들을 담습니다.
GATT server(일반적으로 peripheral)는 시작 시점에 자신의 데이터베이스를 한 번 선언하며, 실행 중에는 데이터베이스가 바뀌지 않습니다. GATT client(일반적으로 central)는 연결 후에 데이터베이스에 무엇이 있는지 발견(discover)합니다 – 트리를 순회하면서 발견한 서비스의 UUID를 읽고, 그다음 각 서비스 안의 특성을 읽습니다.
11.6.3. UUID¶
모든 service, characteristic, descriptor는 그것이 어떤 종류의 것인지를 식별하는 UUID(Universally Unique IDentifier)를 가집니다. UUID는 세 가지 너비로 제공됩니다:
16비트. Bluetooth SIG가 정의한 표준을 위해 예약되어 있습니다. Battery Service는
0x180F입니다. Battery Level(특성)은0x2A19입니다. 전체 목록은 Bluetooth SIG의 지정 번호(assigned-numbers) 사이트인 https://www.bluetooth.com/specifications/assigned-numbers/ 에 게시되어 있습니다.32비트. 거의 사용되지 않는 중간 단계입니다.
128비트. 그 외 모두가 사용하는 것입니다 – 벤더나 애플리케이션이 무작위로 하나를 생성하여 자신의 커스텀 서비스나 특성에 사용합니다. 자체 프로토콜을 정의하는 카메라가 여기에 해당합니다.
bluetooth.UUID 클래스는 세 가지 폭 중 어느 것이든 받아들입니다:
import bluetooth
BATTERY_SERVICE = bluetooth.UUID(0x180F)
CUSTOM_SERVICE = bluetooth.UUID("12345678-1234-5678-9abc-def012345678")
16비트 UUID는 작은 광고 페이로드로 인코딩되며, 이것이 표준 service가 존재할 때 그것을 선호하는 이유 중 하나입니다 – 0x180D(Heart Rate)을 광고하는 심박 측정 스트랩은 2바이트가 들지만, 커스텀 UUID는 16바이트가 듭니다. 표준 상호 운용성이 필요 없는 애플리케이션의 경우, 생성된 128비트 UUID가 올바른 답입니다.
11.6.4. SIG 표준 서비스가 주는 이점¶
표준 서비스를 사용하는 근거는 단순합니다: 기존 앱이 이미 그것과 통신하는 방법을 알고 있다는 것입니다. Heart Rate 서비스(0x180D)를 광고하고 Heart Rate Measurement 특성(0x2A37)을 노출하는 장치는 누구도 새 코드를 작성하지 않아도 전 세계 모든 피트니스 앱과 동작합니다. 같은 데이터를 커스텀 UUID로 다시 구현하는 장치는 자체 동반 앱과 자체 프로토콜 문서가 필요합니다.
표준에는 대가가 따릅니다. 각 특성 내부의 바이트 레이아웃이 명세되어 있습니다 – SIG는 Heart Rate Measurement가 단일 바이트 플래그 필드에 이어 8비트 또는 16비트 심박수 값이 오고, 선택적으로 R-R 간격이 뒤따른다고 결정했습니다 – 그리고 규격을 준수하는 장치는 이 레이아웃을 따라야 합니다. 커스텀 서비스에는 그런 제약이 없습니다.
카메라를 위한 실용적인 답: 가지고 있는 데이터의 종류에 대해 표준 서비스가 존재하면 그것을 사용하고(Battery Service, Environmental Sensing), 애플리케이션 고유의 것에는 128비트 UUID로 커스텀 서비스를 정의하십시오.
11.6.5. 서버 측과 클라이언트 측 객체¶
동일한 개념적 구성 요소(서비스, 특성, 디스크립터)에 대해 모든 GATT 라이브러리는 두 가지 병렬 객체 집합을 노출합니다:
서버 측 객체 – peripheral이 호스팅한다고 선언하는 것.
aioble에서는aioble.Service,aioble.Characteristic,aioble.Descriptor입니다. 이들은 광고가 시작되기 전에 생성되고aioble.register_services()로 등록됩니다.클라이언트 측 객체 – central이 연결 후 상대방에서 발견하는 것.
aioble에서는aioble.ClientService,aioble.ClientCharacteristic,aioble.ClientDescriptor입니다. 이들은 연결 후aioble.DeviceConnection.service()/services()를 통해 순회됩니다.