첫 입사 후 정신없었던 3개월을 지나 한개의 앱 마이그레이션과 한개의 앱을 신규 출시했다!
이제 어디가서 자기소개 할때 머쓱하지만 직업은 개발자입니다, 라고 말할 수 있게 되었다.👏
우리 회사는 초음파 기술을 기반으로 근육을 측정할 수 있는 기기를 보유하고 있다. 회사에서 만든 앱을 이용해 그 기기를 사용자들이 사용할 수 있게끔 하고있다.
이 기기는 블루투스를 이용해 앱과 연결된다. 블루투스 기술을 접해본적이 없어서 입사 초반엔 블루투스를 이해하는데 정말 많은 시간을 쏟았다. 그 과정에서 겪었던 경험들에 대해서 써보고자 한다.
(왜냐면 입사 초기에 검색해도 자료가 안 나와서 조금 눈물이 났기 때문이다)
그래서 이 글은 ble의 기본적인 사항에 대해서 정리한다기 보다는 다른 사람은 어떻게 처리할까? 정도가 궁금했을 때 읽기 좋은 글이 되었으면 좋겠다는 생각으로 작성한다.
보안상 모든 구조와 로직을 글에 다 공유할수는 없지만, 어쨌든 기기에서 실시간으로 데이터가 넘어온다.
그 데이터를 사용자가 볼 수 있도록(기기에서 넘어오는 데이터를 쌩으로 보여주는 일은 없다. 적당히 보여줘야 하는 정보로 가공한다) 앱에서 처리하고 시각화해야 했다.
그러니까, 이런 흐름을 가진다.
기기 ➡️ GATT ➡️ byte 수신
byte ➡️ 버퍼 ➡️ 파싱
SharedFlow emit ➡️ ViewModel 처리
StateFlow로 UI에 반영
BLE 통신은 GATT를 통해 byte 데이터가 지속적으로 들어오기 때문에, 비동기 데이터 스트림 처리가 특히 신경써야하는 부분이었다.
StateFlow는 가장 최근 값 단 하나만 유지하고, 새 구독자에게는 가장 최신 값만 전달된다.
반면 SharedFlow는 버퍼를 가지고 여러 이벤트를 모두 순차적으로 전달하기 때문에 상태가 아니라 이벤트 처리에 적합하다.
ble 기기로부터 수신되는 데이터는 사용자의 요청 없이도 주기적으로 전달되는 연속적인 byte 스트림이다. 여러개의 byte가 모여야 하나의 유의미한 값이 된다.
그러니까 하나의 현재 상태를 덮어쓰는게 아니고, 매번 새로 발생한 이벤트로 쌓여야 하고 순서대로 처리되어야 한다.
이런 정합성을 가진 데이터를 처리해본적이 없어 기존에 하던 방식대로 stateFlow를 사용했는데, 데이터가 누락된 채로 들어오는 경우가 있었다.
때문에 stateFlow를 사용했을 때 패킷 중 일부만 전달되거나, 처리중 새 값으로 덮어쓰여 데이터 누락이 발생했던 것!
BLE 기기의 onCharacteristicChanged() 콜백은 굉장히 짧은 주기로 호출되며, 각 호출마다 byte 배열이 전달된다.
문제는 이 콜백이 메인 스레드가 아닌 백그라운드 스레드에서 동시에 여러 번 호출될 수 있다는 점.
(을 알기까지 몇번의 수정이 있었는지🥲)
따라서 byte 데이터를 순차적으로 수신하고, 이를 버퍼에 누적하는 로직에는 동기화 처리(synchronized)가 반드시 필요했다.
그래서 dataLock 객체를 사용해 onCharacteristicChanged() 블록 내에서 byte 버퍼 누적 로직 전체를 synchronized 처리해 동일 시점에 여러 데이터가 들어오더라도 버퍼가 꼬이지 않고,일정 크기 이상의 데이터가 쌓였을 때만 SharedFlow로 방출해 정확히 패킷 단위로 처리되게 신경썼다.
입사 후 앱을 만들며 가장 신경쓰였던 두 가지가 블루투스 데이터 처리와 근육 인체 모델 그리기인데, 검색해도 자료가 잘 나오지 않아서 정말 많은 시행착오가 있었다.
그러니 이 글이 필요한 누군가의 눈에 꼭 잘 띄길 바라면서 마무리~!
근육 인체 모델 그리기는 사진 자료도 있어야 설명이 원활할것 같아서 글을 쓰기가 무섭지만 그래도 그것도 어딘가에 필요한 누군가가 있으리라 생각한다...! 그러니까 그건 언제가 되더라도 꼭 작성해보고자 한다!