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 참조 페이지는 “이 호출의 정확한 이름이 무엇인가”가 질문일 때 모든 메서드, 플래그, 상수를 한곳에 나열합니다.