11.14. 마무리#

여러분은 무선부터 그것을 구동하는 데 사용되는 Python API에 이르기까지 Bluetooth Low Energy를 두루 살펴보았습니다:

  • 동기 – BLE는 카메라가 그 사이에 어떤 인프라도 없이 가까이 있는 무언가와 통신하고자 할 때의 답입니다. 같은 방의 휴대폰, 손목의 웨어러블, 벽의 비콘. 짧은 범위, 가입할 네트워크 없음, 거의 무전력.

  • 무선 – 2.4 GHz, 40개 채널: 광고용 3개, 연결 데이터용 37개로, 의사 난수 시퀀스로 호핑하며 잡음이 많은 채널은 적응적으로 회피합니다. 짧은 패킷, 대부분 잠들어 있는 무선.

  • 링크 계층 – 패킷 프레이밍, 주소 지정, 연결 스케줄링, 재전송, 링크 계층 암호화. 이 중 어느 것도 Python에서 구성되지 않으며, 모두 연결 파라미터와 MTU를 통해 드러납니다.

  • Generic Access Profile (GAP) – 검색 및 연결 관리. 네 가지 역할: peripheral과 broadcaster(광고), central과 observer(스캔). 광고 페이로드는 로컬 이름, 서비스 UUID, appearance, 제조사별 데이터를 담습니다 – 31바이트에 선택적인 31바이트 스캔 응답이 더해집니다. 연결 간격, peripheral 지연, 감독 타임아웃이 열린 연결의 느낌을 좌우합니다.

  • Generic Attribute Profile (GATT) – 서비스의 트리로, 각 서비스는 특성(characteristic)을 가지며 각 특성은 선택적으로 디스크립터를 가질 수 있고, UUID로 식별됩니다(Bluetooth-SIG 표준의 경우 16비트, 커스텀의 경우 128비트). 다섯 가지 연산이 있습니다: readwrite (가져오기, 클라이언트가 시작), notifyindicate (밀어내기, 서버가 시작하며 Client Characteristic Configuration Descriptor 를 통해 구독). 페이로드 크기는 협상된 MTU에 의해 제한됩니다.

  • Python APIaioble 은 모든 BLE 패턴을 asyncio 코루틴으로 바꿉니다. peripheral은 연결을 반복하는 aioble.advertise() 이며, Service / Characteristic 객체를 한 번 빌드하고 aioble.register_services() 로 커밋합니다. central은 피어를 찾기 위한 aioble.scan(), 링크를 여는 connect(), 원격 GATT 트리를 따라가는 service()characteristic(), 그리고 실제 데이터를 위한 read() / write() / subscribe() / notified() 입니다. 연결 해제는 대기 중이던 코루틴 내부에서 aioble.DeviceDisconnectedError 로 나타납니다.

  • L2CAP 채널 – GATT의 키/값 모델에 맞지 않는 대량 바이트 스트림을 위한 탈출구입니다. aioble.DeviceConnection.l2cap_accept() / l2cap_connect() 는 GAP 연결 위에 애플리케이션별 채널을 열며, 크레딧 흐름 제어 방식의 send / recv와 GATT가 전달할 수 있는 것보다 큰 MTU를 제공합니다.

  • 페어링 및 암호화 – BLE 링크는 기본적으로 공개입니다. aioble.DeviceConnection.pair() 는 암호화된 링크를 생성하는 키 교환을 시작하며, bond=True(기본값)는 키를 영구 저장하여 이후 연결에서 핸드셰이크를 건너뜁니다. mitm=True 와 사용 가능한 IO 기능이 없으면, 암호화는 수동적 도청자로부터는 보호하지만 원래 페어링 중의 능동적 리다이렉트로부터는 보호하지 못합니다.

이것만으로도 peripheral로서 상태를 게시하고, central로서 센서 데이터를 읽고, BLE를 통해 실시간 값을 휴대폰으로 밀어내고, 페어링 및 본딩 단계로 링크를 보호하며, 드물게 대량 전송이 필요한 경우 GATT에서 벗어나 L2CAP 채널로 넘어가는 카메라 애플리케이션을 작성하기에 충분합니다.

11.14.1. 문제 해결#

BLE 실패는 대부분 양쪽이 기대하는 바가 서로 일치하지 않는 데서 비롯되며, 누구의 기대가 어긋났는지 가장 빠르게 확인하는 방법은 휴대폰 측 검사 도구입니다. 표준 도구는 nRF Connect for Mobile (Nordic Semiconductor, Android 및 iOS에서 무료)입니다: 스캔하고, 연결하고, GATT 데이터베이스를 탐색하고, 특성을 읽고 쓰며, 알림을 구독합니다 – 따라서 컴패니언 앱을 전혀 작성하지 않고도 카메라 측 동작을 독립적으로 테스트할 수 있습니다.

일반적인 실패 유형:

  • “스캐너에는 내 장치가 나타나지만 연결되지 않습니다.” 대부분의 경우 광고 패킷에 connectable=False 가 설정되어 있거나(브로드캐스터 모드), 이전 연결이 아직 열려 있고 카메라가 이미 aioble.advertise() 를 지나친 상태입니다. advertise 호출 주변에 print 문을 추가하여 확인하세요.

  • “exchange_mtu(512)를 실행했는데도 알림이 여전히 20바이트로 제한됩니다.” 협상된 MTU는 min(local, peer) 입니다 – 휴대폰이나 central 라이브러리가 자기 쪽에서 더 큰 MTU를 요청하지 않았을 수 있으며, 그 경우 연결은 23으로 유지됩니다. exchange_mtu() 가 반환된 후 mtu 를 확인하십시오. 또한 exchange_mtu() 는 연결당 한 번만 작동하므로, 첫 대규모 작업 전에 호출하십시오.

  • “페어링이 일반 오류로 실패합니다.” 두 가지 흔한 원인: IO 기능 불일치(io=3 / 입력도 출력도 없음을 선언한 캠에 mitm=True 를 요청 – 숫자 코드를 확인할 방법이 없어 페어링 엔진이 중단됨), 그리고 피어가 요구할 때 캠의 벽시계 시각이 크게 틀린 경우입니다. 첫 페어링 시도 전에 ntptime.settime() 으로 시계를 설정하십시오.

  • “알림이 클라이언트에 전혀 도착하지 않습니다.” 순서대로 확인할 두 가지: (a) characteristic이 notify=True 로 선언되었습니까? – 서버 측에서 속성 비트가 설정되어 있어야 합니다; (b) 클라이언트가 subscribe() 를 호출했습니까? – Client Characteristic Configuration Descriptor(CCCD)를 쓰지 않으면 서버는 알림을 원하는 클라이언트가 없다고 인식하여 조용히 폐기합니다.

  • “광고된 이름이 잘리거나 없습니다.” 광고 페이로드는 31바이트이며, flags + service-UUID + appearance 필드가 각각 그중 일부를 차지합니다. 긴 name= 에 여러 서비스 UUID를 더하면 넘칩니다. 이름을 줄이거나, 능동 스캔을 사용하여 스캔 응답(또 다른 31바이트)이 넘치는 부분을 전달하도록 하십시오. nRF Connect는 두 절반을 별도로 표시하므로 분할이 명확하게 보입니다.

  • “L2CAP 연결이 즉시 예외를 발생시킵니다.” 보통 PSM 불일치입니다 – 양쪽이 대역 외에서 동일한 PSM 번호에 합의해야 합니다. L2CAPConnectionError 는 Bluetooth 상태 코드를 첫 번째 인자로 전달합니다; 상태 2 (“PSM not supported”)가 그 단서입니다.

  • “본딩된 연결이 재연결할 때마다 여전히 전체 페어링 핸드셰이크를 일으킵니다.” 시작 시 aioble.security.load_secrets() 가 호출되지 않은 것입니다. 그것이 없으면 저장된 키는 플래시에 있지만 메모리로 불러와지지 않으므로, 피어의 신원을 알 수 없어 매번 처음부터 페어링이 실행됩니다.

다른 모든 방법이 실패하면, 저수준 bluetooth 모듈은 모든 기저 이벤트에 대해 발생하는 IRQ 콜백을 노출합니다. 잠시 그것을 구독하여 이벤트를 출력하는 것은 캠 측의 Wireshark 추적에 해당합니다.

11.14.2. 이 참조 자료를 나중에 활용하기#

Bluetooth 장들을 참조 자료로 취급하십시오. peripheral의 광고 페이로드의 정확한 레이아웃이나 central의 스캔 및 구독 흐름을 다시 찾아보는 것이 의도된 용도입니다. aioble — 비동기 BLEbluetooth — 저수준 Bluetooth 참조 페이지는 “이 호출의 정확한 이름이 무엇인가”가 질문일 때 모든 메서드, 플래그, 상수를 한곳에 나열합니다.