# react-native bluetooth 개발기
# 서론
신사업을 지원하면서 react-native로 esp-bluefi와 블루투스 통신하는 기계들 간의 통신하는 개발을 시작하게 되었습니다. 서비스 특성상, 블루투스 스캔 및 connect하여 앱과 기기간 통신하는 로직은 24시간 돌아가야함으로, 백그라운드에 로직을 구현해야했고, rn은 포그라운드에서만 동작을 하기 때문에 native도 직접 개발하게 되었고, 저는 안드로이드를 java로 개발하게 되었습니다.
ios는 다른분이 swift로 개발을 하게 되었고, ios도 24시간 백그라운드에 돌아야하지만 애플 정책상, 앱을 강제 종료한 경우에는 무조건 백그라운드 로직이 취소되기 때문에, 앱이 백그라운드, 포그라운드에 있는 경우만 로직이 실행되도록 정책을 바꾸었습니다.
and는 앱을 강제종료하여도 특정 로직만 넣어주면 24시간 진행이 가능합니다. 그러나 배터리를 계속 잡아먹는 앱이 존재합니다 등, and 자체에서 해당앱이 24시간 돌고 있다는 노티를 주고 있고, 그 푸시를 보고 앱을 절전시키면 백그라운드는 종료 되었습니다.
이러한 상황을 제외하고는 24시간 스캔 및 connect와 데이터 요청 및 받아오면 mqtt 통신으로 서버에 ble data 전송하는 로직을 구현하였습니다.
mqtt는 iot 디바이스를 위한 통신 기법으로 아주 작은 리소스로 통신을 할 수 있는 프로토콜입니다. 차후에 더 자세히 알아보도록 하겠습니다.
아래는 개발하면서 어려웠던 부분 및 해결한 리스트입니다.
# 과하게 삽질한 경험들
rn부분은 여태껏 계속 해온 부분이기에 없었고, and java는 처음 경험하는 언어 및 환경이기에 작업하는데 많은 시간이 걸렸습니다. 그중 정말 어려웠던 경험들을 서술합니다.
# ble disconnect가 안되는 상황
# 현상
로직상 ble disconnect를 했고, disconnect가 했다는 method까지 콘솔에 찍힌 상황이지만 정작 디바이스를 다른 폰에서 보았을 때는 disconnect가 안되고 원래 기기에 계속 붙어 있는 현상
# 원인 파악
nrf connect라는 앱으로 해당 기기가 연결이 붙어있는지 확인, 어딘가에 붙어있다면 원래 기기의 블루투스를 끄거나 폰을 껐을때 nrf connect앱에 해당기기가 연결 가능 상태로 변경되면 원래 기기가 disconnect를 하지 않았다고 파악.
ble connect를 하면 bluetooth gatt라는 객체가 생성되고 그 gatt를 이용해 데이터를 기기로 요청하든 disconnect를 하던 하는데, 이 객체가 문제가 있다고 판단.
맨처음 기기를 스캔하고 연결을 하면 해당 gatt 객체가 생성 될 텐데, 그 객체가 계속 다른 값으로 치환되고 있고, gatt값들이 어딘가에 계속 쌓여서 가장 최근 gatt를 close하여도 기존에 쌓여있던 gatt객체는 close되지 않았기에 disconnect가 되지 않는 문제였음.
# 해결
기기와 연결하면 gatt는 무조건 하나만 생성되게 하였고, 어떠한 이유로 gatt가 비정상적으로 만들어져 기기간 통신이 불가능하면 아예 disconnect를 하고 다시 connection을 맺어 gatt 객체를 재 생성하도록 수정
# rn과 native간 스캔 연동성
rn과 native가 동시에 스캔은 가능하다. 그러나 스캔 결과는 공유되지 않는다. 그래서 만약 rn이 a라는 기기를 스캔하고 연결했다면 native는 a라는 기기를 스캔할수도 연결 할 수도 없다. 그렇기에 어느 한쪽에서 스캔을 하고 연결하여 read or write를 하려면 안쓰는 쪽은 ble scan을 취소해야한다.
# 펌웨어 업데이트시 ble disconnect가 안되는 상황
FirmwareUpgradeManager라는 라이브러리를 사용하여 해당 기기에 새로운 펌웨어 정보가 업데이트 하면 앱에서 기기에 연결하여 업데이트 하는 기능이 있었다.
rn에서도 native에서도 기기를 검색하여 펌웨어 업데이트를 해야해서 해당 펌웨어 서비스에서 스캔 및 커넥션 및 업데이트 및 disconnect를 하기로 하였다.
FirmwareUpgradeManager는 사용하기 쉬웠다. 펌웨어 업데이트할 데이터가 있는 file path와 디바이스 네임만 있으면 자체적으로 스캔 및 커넥션 및 업데이트가 가능하다. disconnect까지 해주는줄 알고 개발했는데 disconnect가 안되도록 애초에 라이브러리가 설계되어있다. 왜 그런건지 지금도 의문이다. 그래서 수동으로 해당 연결을 가진 객체를 가져와서 기기 업데이트가 완료되면 release하도록 명령을 내렸다.
# 앱 스캔 connection vs 다른 iot 기기 스캔 connection
앱의 스캔 및 연결하는 속도는 생각보다 많이 빠르다. 괜히 폰이 비싼게 아녔다. 내가 개발하는 a라는 기기는 b라는 기기가 없으면 a는 앱에 붙고, b가 있으면 앱과 연결을 취소하고 a는 b에 붙어야한다. 앱이 a에 붙고있다가 b가 스캔되어 a의 연결을 취소해도 b에 안붙고 계속 앱에 붙는 현상이 있었다.
위쪽에 ble disconnect가 안되는 현상의 원인 파악하는 방법대로 원인을 인지하였고, 파악 결과 앱이 disconnect를 해도 스캔이 돌고있으면 b라는 기기보다 더 빠르게 스캔되어 연결되는 현상이 있었다. (이게 신기한게 and는 빠르고 ios는 스캔이 느려서 ios는 b라는 기기에 붙는다.) 그래서 a와 연결 해제 및 스캔 취소 및 30초 후 스캔 재시작 하는 로직을 넣어 해결하였다.
# 스캔 및 connection 하고 물리적인 거리가 멀어져 disconnect되고 다시 가까워져 connect를 해야하는데 안되는 상황
최초 1회에 스캔할때 a라는 기기가 스캔되어 connection하고 데이터 통신까지 잘된다. 그런데 멀어지고 연결이 끊기고 다시 가까워지면 스캔에 걸려 연결을 해야하는다. 애초에 스캔에 a라는 기기가 걸리지 않는다. 2일 삽질하고 나온 결과는 스캔 시작하는 객체에 해당 기기의 고유 serviceUuid로 필터를 걸어줘야한다. 왜 그런지 모르겠다. 아무데도 나오지 않는다..... 밑에 나오는 ios의 스캔 오류를 해결하면서 혹시 이것도 그런가? 싶어서 했는데 얻어걸렸다....
# ios 백그라운드에서 스캔시 기기를 찾지 못하는 현상
ios도 스캔시 해당 기기의 고유 serviceUuid로 필터를 걸어줘야만 백그라운드에서 스캔시 기기를 스캔할 수 있었다. 이건 자료가 많아서 빨리 해결되었다.
# 앱 실행 중 블루투스를 끄거나 키면 앱이 crash 되는 현상
이 현상은 구형폰 (2010년대 폰)에서 발생하고 비교적 최신폰들은 그렇지 않았다. 원인은 권한 문제로 매니페스트 파일을 수정하여 해결하였다. (블루투스 어드민 권한 때문에 터졌다.)
# bluetooth 권한이 보이지 않는 현상
안드로이드 버전 10이하는 블루투스 권한이 없다. 위치권한만 허용하면 블루투스가 자동으로 실행된다. 11 버전 이상에서는 블루투스 권한을 따로 받야야 한다.
# iot 개발자간 업무 협의
iot 개발자들은 기기의 용량이 적기 때문에 byte 단위로 소통하고, 통신도 byte array로 들어오기 때문에 16진수로 변환하고 byte array를 연결하고 별 이상한 짓을 해야 사람이 읽을 수 있는 값으로 변환 된다. 보낼때도 byte array로 변환하여 보내야 한다. 맨처음 이 값을 읽고 보내는 과정에서 무슨 말인지 몰라 삽질을 많이 하였다. esp bluefi 문서를 보면서 진짜 방탈출 하는 기분으로 도큐먼트를 해석한 시간만 꼬박 하루를 보냈다.
# rn에서 ble disconnect시 disconnect이 제대로 되지 않는 점
위에서 본 ble disconnect가 안되는 상황
과 동일한 상황이다. 해당 로직을 ios를 개발하신 분이 짠거라 ios는 해당 이슈가 없기에 고려하지 않고 하나의 기기에 여러 connection을 넣어도 disconnect에 이상이 없는 줄 알고 개발 하였다.
다시 한번 리마인드 하면, ios는 한 기기에 대해 다중 connection을 넣어도 가장 최근 connection을 한 객체에 의해 데이터 통신 및 disconnect할때의 객체가 유지되어 모든 통신이 정상적으로 되지만 and는 여러 커넥션시 최초에 커넥션 한 ble 객체값을 알아야 disconnect가 됨으로 여러 connection을 하면 disconnect가 제대로 되지 않는다.
그렇기에 scan시 해당 기기가 여러번 scan에 걸릴텐데, 최초 1회만 connection을 맺어줘야만 제대로 동작할 수 있다.
# 앞으로도 삽질은 계속 될 것 같다..
# and, ios ci/cd 구성
rn코드는 ms의 코드푸시로 구현하여 ts, js파일이 변경되면 코드푸시가 돌아가도록 구현하였다.
and, ios는 해당 파일이 변경되면 fastlane로 버전 및 빌드 및 배포 완료 또는 에러시 슬랙으로 완료 메세지 또는 에러 로그를 알려주는 배포 스크립트를 작성하였고, github action에서 testflight나 플레이스토어에 자동으로 배포하도록 구현하였다.
이건 한글로 되있는 동영상이 많아서 비교적 쉽게 구현하였다.
# android bluetooth scan callback error
안드 6.0 마시멜로 이상부터는 ble scan callback을 받으려면 위치 권한을 받고, 실제 유저가 위치서비스를 실행해야한다. 안하면 scan 시작은 되는데, scancallback에 아무런 정보가 들어오지 않는다.
ios는 위치를 꺼도 scancallback이 들어온다.