광고와 스캔: 나 여기 있어요
새 무선 이어폰 케이스를 열면 휴대폰 화면에 “연결하시겠습니까?” 창이 뜬다. 운동 앱을 켜면 주변의 심박계 이름이 목록에 나타나고, 지하철역을 지나면 분실물 태그의 위치가 갱신된다. 이 모든 것의 시작은 LE 기기가 허공에 외치는 짧은 자기소개, 광고다. 광고 패킷 하나에는 겨우 31바이트가 들어가고, 기기는 그것을 수십 ms에서 수 초마다 세 채널에 나눠 외친다. 듣는 쪽은 언제, 얼마나 오래 귀를 기울여야 할까? 이 장에서는 광고 패킷을 바이트 단위로 직접 만들고, 두 기기가 서로를 찾는 데 걸리는 시간을 시뮬레이션한다.
- 광고자·스캐너·센트럴·페리페럴 같은 GAP 역할과, 광고 PDU의 종류(연결 가능·스캔 가능·방향 지정)를 구분한다.
- 광고 이벤트가 광고 간격과 0~10 ms 무작위 지연(advDelay)으로 반복되는 이유를 안다.
- 길이·종류·값으로 된 AD 구조를 써서 31바이트 광고 데이터를 직접 만들고, 한글 이름이 왜 빨리 자리를 차지하는지 안다.
- 스캔 창과 스캔 간격, 광고 간격이 발견 시간과 전력을 어떻게 정하는지 시뮬레이터로 확인한다.
- 5.0의 확장 광고(보조 채널, 최대 1,650바이트), 주기적 광고, 그리고 연결 요청까지의 흐름을 이해한다.
외치는 쪽과 듣는 쪽
LE에서 처음 만나는 두 기기는 대칭이 아니다. 한쪽은 외치고 한쪽은 듣는다. GAP(1장)는 기기가 맡을 수 있는 역할을 넷으로 정한다.
- 브로드캐스터(Broadcaster): 광고만 하고 연결은 받지 않는다. 비콘, 온도계가 값을 광고에 실어 뿌리는 경우.
- 옵서버(Observer): 광고를 듣기만 한다. 비콘 수신기.
- 페리페럴(Peripheral): 연결 가능한 광고를 하다가 연결 요청을 받아들이는 쪽. 심박계, 이어폰, 도어락.
- 센트럴(Central): 스캔하다가 원하는 기기에 연결 요청을 보내고, 연결된 뒤에는 시간표를 정하는 쪽. 보통 휴대폰이다.
링크 계층은 이 역할에 맞춰 광고 상태, 스캔 상태, 개시(initiating) 상태, 연결 상태 사이를 오간다. 한 기기가 여러 역할을 동시에 맡을 수도 있다. 휴대폰은 시계와 연결된 센트럴이면서, 동시에 다른 휴대폰에게 자신을 광고하는 페리페럴이 될 수 있다.
새 손님(페리페럴)은 입구 근처의 세 테이블(광고 채널 37·38·39)을 돌며 짧게 외친다. “심박계입니다. 연결 가능합니다.” 그리고 잠깐 귀를 기울인 뒤 다음 테이블로 간다. 이걸 일정한 간격으로 되풀이한다. 기다리던 손님(센트럴)은 세 테이블 중 한 곳에 앉아 일정 시간 귀를 기울이고(스캔 창), 쉬었다가, 다음 테이블로 옮긴다. 외치는 순간과 듣는 순간, 그리고 테이블이 겹쳐야 서로를 알게 된다.
광고 이벤트: 세 채널에 한 번씩, 조금씩 흔들며
광고하는 기기는 광고 이벤트(advertising event)마다 같은 광고 PDU를 광고 채널에 하나씩, 보통 37 → 38 → 39 순서로 보낸다. 세 채널에 나눠 보내는 이유는 2장과 4장에서 봤다. 한 채널이 Wi-Fi에 막혀 있거나 페이딩 골짜기에 빠져 있어도 나머지가 살아남는다.
이벤트 사이의 시간은 광고 간격에 무작위 지연을 더한 값이다.
T_advEvent = advInterval + advDelay, advInterval 20 ms ~ 10,485.759375 s, advDelay 0~10 ms(shall): [Vol 6] Part B §4.4.2.2.1 · 광고 채널 선택: [Vol 6] Part B §4.4.2.1
광고 간격을 얼마로 할지는 사양이 정하지 않고, GAP가 권장값을 준다. 사용자가 막 버튼을 눌러 “지금 찾아 줘” 상태일 때는 30~60 ms로 빠르게, 그 상태로 30초쯤 지나면 1~1.2 s의 느린 배경 광고로 바꾸라는 식이다. 빠를수록 금방 발견되지만 배터리를 더 쓴다(9장).
| GAP 권장 타이머 | 값 | 뜻 |
|---|---|---|
| TGAP(adv_fast_interval1) | 30 ~ 60 ms | 사용자가 시작한 연결 가능 광고 (LE 1M) |
| TGAP(adv_fast_interval2) | 100 ~ 150 ms | 사용자가 시작한 연결 불가 광고 (LE 1M) |
| TGAP(adv_fast_period) | 30 s | 빠른 광고를 유지하는 최소 시간 |
| TGAP(adv_slow_interval) | 1 ~ 1.2 s | 배경 광고 (LE 1M) |
| TGAP(scan_fast_interval / window) | 30 ~ 60 ms / 30 ms | 사용자가 시작한 스캔 |
| TGAP(scan_slow_interval1 / window1) | 1.28 s / 11.25 ms | 배경 스캔 |
| TGAP(lim_adv_timeout) | 180 s | 제한 발견 모드로 광고할 수 있는 최대 시간 |
GAP 타이머(Table A.1): [Vol 3] Part C Appendix A. Coded PHY용 값은 대략 3배로 따로 정해져 있다(예: adv_fast_interval1_coded 90~180 ms).
광고 패킷 해부: 31바이트의 경제학
광고 채널의 패킷은 LE 1M에서 프리앰블 1바이트, 모든 광고가 같이 쓰는 접근 주소 0x8E89BED6 4바이트, PDU, CRC 3바이트로 이루어진다. PDU(Protocol Data Unit, 프로토콜 데이터 단위)는 한 계층이 상대편의 같은 계층에 건네는 데이터 덩어리로, 링크 계층에서는 패킷 가운데 실제 내용이 담긴 부분이다. 앞뒤의 프리앰블·접근 주소·CRC는 이 덩어리를 실어 나르고 확인하는 포장이다. 광고 PDU는 2바이트 헤더(종류, 주소 종류, 길이)와 페이로드다. 가장 흔한 ADV_IND의 페이로드는 광고자의 주소 AdvA 6바이트와 호스트가 넣는 AdvData 0~31바이트다.
| 레거시 광고 PDU | 연결 | 스캔 요청 | 쓰임 |
|---|---|---|---|
| ADV_IND | 가능 | 가능 | 일반적인 페리페럴 (“누구든 연결하세요”) |
| ADV_DIRECT_IND | 특정 기기만 | — | 아는 센트럴에게 빨리 재연결 요청 (데이터 없음) |
| ADV_SCAN_IND | — | 가능 | 연결은 안 받지만 추가 정보는 스캔 응답으로 |
| ADV_NONCONN_IND | — | — | 비콘, 순수 방송 |
AdvData 안은 AD 구조(AD structure)를 이어 붙인 것이다. AD 구조 하나는 [길이 1바이트][종류 1바이트][값]이다. 길이는 종류와 값을 합친 바이트 수다. 종류 번호(AD type)와 각 값의 형식은 Core 밖의 Assigned Numbers와 Core Specification Supplement에 정해져 있다. 자주 쓰는 것은 플래그(0x01), 16비트 서비스 UUID 목록(0x02·0x03), 이름(0x08 일부·0x09 전체), 송신 전력(0x0A), 제조사 데이터(0xFF)다.
LE 패킷 = 프리앰블·접근 주소·PDU(2~258바이트)·CRC: [Vol 6] Part B §2.1 · 광고 채널 접근 주소 0x8E89BED6: [Vol 6] Part B §2.1.2 · 광고 PDU 종류(Table 2.3)와 Length 1~255: [Vol 6] Part B §2.3 · ADV_IND 페이로드: §2.3.1.1 · AD 구조 형식: [Vol 3] Part C §11
광고 데이터 만들기: 31바이트에 무엇을 넣을까
31바이트가 모자랄 때 레거시 광고의 방법은 스캔 응답(scan response)이다. 스캐너가 광고를 들은 직후 SCAN_REQ로 “더 알려 줘”라고 물으면, 광고자는 같은 채널에서 150 µs 뒤 SCAN_RSP로 31바이트를 더 보낸다. 이렇게 묻는 스캔을 능동 스캔, 듣기만 하는 것을 수동 스캔이라 한다. 휴대폰의 블루투스 설정 화면이 기기 이름을 보여 주는 것은 대개 능동 스캔 덕분이다.
스캔과 발견 시간: 언제 귀를 기울일까
스캐너는 스캔 창(scanWindow) 동안 광고 채널 하나를 듣고, 스캔 간격(scanInterval)마다 다음 창을 연다. 창마다 다른 광고 채널을 듣는 것이 권장된다. 창과 간격이 같으면 쉬지 않고 계속 듣는다. 계속 들으면 수신기가 계속 켜져 있으므로 전력이 많이 든다. 휴대폰은 화면이 켜져 있을 때는 촘촘히, 꺼져 있을 때는 드문드문 스캔한다.
scanWindow ≤ scanInterval < 40.96 s, 창마다 다른 광고 채널(should), 모든 광고 채널 사용(shall): [Vol 6] Part B §4.4.3
발견되려면 세 가지가 맞아야 한다. 광고 패킷이 날아가는 순간에 스캐너가 창을 열고 있어야 하고, 그 창의 채널이 패킷의 채널과 같아야 하며, 패킷이 창 안에 통째로 들어가야 한다. 어긋나면 다음 광고 이벤트를 기다린다. 그래서 발견 시간은 고정값이 아니라 분포다.
발견 시간: 광고 간격 × 스캔 창 × 스캔 간격
분실물 태그가 2초마다 광고하고, 지나가는 휴대폰은 화면이 꺼진 상태로 1.28초마다 11.25 ms씩만 스캔한다. 휴대폰이 태그 옆을 5초 동안 지나간다면 발견할 확률은?
시뮬레이터에서 광고 간격 2 s, 스캔 간격 1.28 s, 듀티 약 0.9%로 맞추고 분포를 보자.
확장 광고와 주기적 광고
레거시 광고는 4.0의 단순함을 그대로 갖고 있다. 31바이트, 세 채널, LE 1M뿐이다. 5.0의 확장 광고(extended advertising)는 이 한계를 우회했다. 광고 채널에는 짧은 ADV_EXT_IND만 보내고, 그 안의 포인터(AuxPtr)가 “몇 µs 뒤, 범용 채널 몇 번, 어떤 PHY로 본문이 간다”고 알려 준다. 본문 AUX_ADV_IND는 37개 범용 채널 중 하나에서 보내고, 길면 AUX_CHAIN_IND로 이어 붙인다.
확장 광고의 쓸모는 크게 셋이다. 첫째, 긴 데이터(최대 1,650바이트). 둘째, 광고 채널에서도 LE Coded PHY를 써서 장거리에서 발견되기(3장). 셋째, 한 기기가 서로 다른 내용·간격·주소를 가진 여러 광고 세트를 동시에 돌리기. 예를 들어 이어폰이 “연결 가능 광고”와 “분실물 찾기용 비연결 광고”를 따로 보낼 수 있다.
주기적 광고(periodic advertising)는 여기서 한 걸음 더 나간다. 7.5 ms ~ 81.91875 s의 고정된 간격으로 AUX_SYNC_IND를 보내고, 듣는 쪽은 한 번 시각과 채널 순서(채널 선택 알고리즘 #2, 4장)를 알아낸 뒤 연결 없이 그 시각에만 깨어나 받는다. 연결 없는 방송이지만 시간표가 있는 방송이다. LE 오디오 브로드캐스트(10장)와 5.4의 응답 있는 주기적 광고 PAwR(12장)이 이 위에 서 있다. 5.4에는 광고 내용을 미리 나눈 키로 암호화하는 암호화된 광고 데이터도 추가되었다.
호스트 광고 데이터 최대 1,650바이트: [Vol 6] Part B §2.3.4.9 · 확장 광고 이벤트: §4.4.2.2.2 · 주기적 광고 간격 7.5 ms ~ 81.91875 s: §4.4.2.2.3 · ADV_EXT_IND는 광고 채널에서 1M·Coded만: Table 2.3
광고에서 연결로: CONNECT_IND
센트럴이 연결 가능한 광고를 받으면, 같은 채널에서 150 µs 뒤 CONNECT_IND를 보낸다. 이 PDU 안에 앞으로의 약속이 전부 들어 있다. 이 연결에서 쓸 접근 주소, CRC 초깃값, 첫 연결 이벤트가 열릴 전송 창의 위치와 크기, 연결 간격, 페리페럴 지연, 감독 타임아웃, 채널 맵, 채널 선택 알고리즘 #1의 hopIncrement, 센트럴의 수면 시계 정확도. 광고자는 CONNECT_IND를 받는 순간 광고를 멈추고 연결 상태로 들어간다. 두 기기는 이제 광고 채널을 떠나 약속한 시각에 범용 채널에서 다시 만난다(6장).
확장 광고에서 연결할 때는 범용 채널에서 AUX_CONNECT_REQ와 AUX_CONNECT_RSP를 주고받는다. CONNECT_IND의 헤더에 있는 ChSel 비트가 1이면 두 기기 모두 알고리즘 #2를 쓰겠다는 뜻이다.
CONNECT_IND·AUX_CONNECT_REQ 페이로드(AA, CRCInit, WinSize, WinOffset, Interval, Latency, Timeout, ChM, Hop, SCA): [Vol 6] Part B §2.3.3.1 · 광고-응답 간격 T_IFS_150: §4.1.1
광고는 암호화되지 않은 방송이다(5.4의 암호화된 광고 데이터를 쓰지 않는 한). 광고에 고정된 주소나 일련번호를 넣으면 지나가는 사람이 그 기기를 계속 추적할 수 있다. 그래서 휴대폰과 이어폰은 광고 주소를 몇 분마다 바꾸는 RPA를 쓴다(8장). 광고 데이터에도 개인을 특정할 수 있는 정보를 넣지 않는 것이 원칙이다.
Classic은? 조회(inquiry)와 호출(page)
지금까지는 모두 LE 이야기였다. 클래식 블루투스(BR/EDR)에는 LE의 광고가 없다. 대신 처음 보는 기기를 찾을 때는 조회(inquiry), 찾은 기기에 연결할 때는 호출(page)이라는 다른 절차를 쓴다. 가장 큰 차이는 누가 먼저 움직이느냐다. LE는 찾아지고 싶은 쪽이 계속 광고하고, 클래식은 찾는 쪽이 계속 외치고 찾아지고 싶은 쪽은 가끔 귀만 연다.
| LE | 클래식 (BR/EDR) | |
|---|---|---|
| 계속 보내는 쪽 | 찾아지고 싶은 기기(광고) | 찾는 기기(조회: 짧은 ID 패킷을 계속 보냄) |
| 귀를 여는 쪽 | 찾는 기기(스캔) | 찾아지고 싶은 기기(조회 스캔, 발견 가능 모드) |
| 채널 | 광고 채널 3개(37·38·39) | 조회 전용 32개 주파수를 16개씩 두 묶음(열차 A·B)으로 나눠 돎 |
| 대답 | 스캔 응답(선택) | FHS 패킷(주소·클록), 이어서 확장 조회 응답(EIR, 이름·서비스, 최대 240바이트) |
| 연결 | 광고를 듣고 CONNECT_IND | 상대 주소로 만든 접근 코드로 호출(page), 상대는 페이지 스캔 중(연결 가능 모드) |
LE는 찾는 사람이 조용히 서 있고, 찾아지고 싶은 사람들이 저마다 손을 흔드는 방식이다. 클래식은 반대로 찾는 사람이 강당 구역을 돌며 “여기 누구 있어요?”를 계속 외치고, 찾아지고 싶은 사람은 가끔 고개를 들어 듣다가 자기 구역에서 외침이 들리면 이름표(FHS)를 내민다. 이미 아는 사람이라면 이름(주소)을 불러 바로 데려온다. 이것이 호출이다.
찾는 쪽은 32개 조회 주파수를 16개씩 나눈 열차 A·B 가운데 하나를 10 ms에 한 바퀴씩 돈다. 한 열차를 적어도 256번(약 2.56초) 반복한 뒤 다른 열차로 바꾼다. 찾아지는 쪽은 조회 스캔 간격(기본 2.56초)마다 창(기본 11.25 ms) 동안 한 주파수만 듣는다. 이 주파수는 1.28초마다 바뀐다. 창이 열차 한 바퀴(10 ms)보다 길어서, 듣는 주파수가 지금 열차에 들어 있으면 그 창 안에서 외침을 듣는다. 아니면 열차가 바뀔 때까지 기다려야 한다.
조회 열차와 조회 스캔이 만나는 순간
이미 페어링된 클래식 이어폰이 조회 스캔을 꺼 두었다(발견 불가 모드). 휴대폰이 이 이어폰에 다시 연결할 수 있을까?
조회와 호출은 서로 다른 절차다.
듀얼 모드 기기는 클래식과 LE를 둘 다 한다. 이런 기기는 LE 광고로도 자신을 알릴 수 있는데, 앞의 광고 패킷 예에서 Flags(0x06)에 들어 있던 “BR/EDR 미지원” 비트가 바로 “나는 클래식을 하지 않는다”는 표시다. 클래식도 하는 기기는 이 비트를 켜지 않는다.
조회·조회 스캔·조회 응답(32개 주파수, 열차 A·B, Ninquiry ≥ 256, 10.24 s, FHS 뒤 EIR): [Vol 2] Part B §8.4, §8.4.1~§8.4.3 · 호출(연결 설정): §8.3 · 조회 스캔 간격·창 기본값 2.56 s·11.25 ms: [Vol 4] Part E §6.2, §6.3 · 발견 가능·연결 가능 모드: [Vol 3] Part C §4.1, §4.2 · EIR 형식(240바이트): [Vol 3] Part C §8 · Flags의 BR/EDR 미지원 비트: Core Specification Supplement(Core 밖)
핵심 정리
- GAP 역할은 브로드캐스터·옵서버·페리페럴·센트럴. 페리페럴이 광고하고 센트럴이 스캔한 뒤 연결을 요청한다.
- 광고 이벤트마다 광고 채널 37·38·39에 같은 PDU를 보낸다. 이벤트 간격은 advInterval(20 ms~약 2.9시간) + advDelay(0~10 ms 난수). 무작위 지연이 반복 충돌을 막는다.
- 레거시 광고 PDU = 헤더 2 + AdvA 6 + AdvData ≤ 31바이트. AdvData는 [길이][종류][값]의 AD 구조를 이어 붙인 것이고, 종류 번호는 Assigned Numbers에 있다. 모자라면 스캔 응답(+31바이트)이나 확장 광고를 쓴다.
- 발견하려면 시각·채널·길이가 맞아야 한다. 발견 시간은 광고 간격과 스캔 창/간격으로 정해지는 분포이며, 빠를수록 전력을 더 쓴다. GAP는 빠른 광고 30~60 ms, 배경 광고 1~1.2 s 등을 권장한다.
- 확장 광고(5.0)는 광고 채널의 짧은 ADV_EXT_IND가 범용 채널의 AUX_ADV_IND를 가리키는 구조로, 최대 1,650바이트, 2M·Coded PHY, 여러 광고 세트를 지원한다. 주기적 광고는 연결 없이 시간표대로 방송한다.
- 센트럴은 광고 150 µs 뒤 CONNECT_IND로 접근 주소, 연결 간격, 채널 맵 등 연결의 모든 약속을 전달한다.
- 클래식(BR/EDR)에는 광고가 없다. 찾는 쪽이 조회(32개 주파수를 열차 A·B로 돎)를 외치고 찾아지는 쪽은 조회 스캔으로 듣다가 FHS·EIR로 대답하며, 연결은 상대 주소로 호출(page)한다.
확인 퀴즈
광고 간격을 100 ms로 설정했을 때 실제 광고 이벤트 사이의 시간은?
AdvData에 “Flags(3바이트) + 16비트 UUID 하나(4바이트) + 전체 이름”을 넣으려 한다. 이름에 쓸 수 있는 최대 바이트는?
모든 광고 채널 패킷이 같은 접근 주소 0x8E89BED6을 쓰는 이유는?
스캔 간격 100 ms, 스캔 창 100 ms로 설정하면?
확장 광고에서 긴 본문(AUX_ADV_IND)을 광고 채널이 아닌 범용 채널로 보내는 장점은?
클래식(BR/EDR)에서 처음 보는 기기를 찾을 때 계속 신호를 보내는 쪽은?
CONNECT_IND에 들어 있지 않은 것은?