Files
VesiscanClinicalAndroid/app
dw.jang b0351342de fix(ble): 옛 연결의 콜백이 새 연결 상태를 덮어쓰고 있었다
연결 해제 후 **빠르게** [연결] 을 누르면 연결이 안 됐다. 병원 임상·정렬·설정탭의
[연결 해제] 는 전부 같은 `disconnect()` 이고, 문제는 그 뒤에 있었다.

## 원인 — gattCallback 이 주인을 확인하지 않았다

`gattCallback` 은 객체 하나를 모든 연결이 공유한다. `gatt.disconnect()` 는 비동기라 옛
연결의 콜백이 **새 연결이 들어선 뒤에** 도착한다:

    ① [연결 해제]  gatt.disconnect()                    ← 콜백은 몇 초 뒤
    ② 바로 [연결]  옛 gatt close + bluetoothGatt = 새 GATT
    ③ 옛 GATT 의 STATE_DISCONNECTED 도착 →
                   bluetoothGatt = null                  ← 새 참조를 지운다
                   isConnected.value = false

③ 이후 새 GATT 가 STATE_CONNECTED 를 받아도 그 분기는 `bluetoothGatt` 를 대입하지 않아
**isConnected=true 인데 bluetoothGatt 가 null** 이 된다. sendRaw 가 그 필드를 쓰므로
명령이 하나도 안 나간다. 반대로 isConnected 가 false 로 덮이면 화면만 "연결 안 됨".

늦게 온 **알림(onCharacteristicChanged)** 이 섞이면 더 나쁘다 — 다른 기기의 파형이
화면에 뜬다.

## 고친 것 셋

**① 주인 확인** `isStaleGatt()` 를 만들어 콜백 일곱 개 전부에 걸었다
(onConnectionStateChange · onMtuChanged · onServicesDiscovered · onDescriptorWrite ·
onCharacteristicChanged ×2 · onReadRemoteRssi). 주인이 아니면 **닫고 버린다** — 안 닫으면
그 핸들이 누수다. onConnectionStateChange 는 `handler.post` 안에서 **한 번 더** 본다:
post 사이에 새 연결이 들어설 수 있다.

`bluetoothGatt == null` 이면 통과시킨다. 연결을 막 만들어 대입 전인 구간이 있고, 그때
막으면 STATE_CONNECTED 를 놓쳐 영원히 연결되지 않는다.

**② STATE_CONNECTED 에서 `bluetoothGatt = gatt`** 뒷북이 지워도 여기서 다시 잡힌다.

**③ 재연결 쿨다운 600ms** close() 는 핸들만 돌려주고 컨트롤러의 링크 정리는 조금 뒤에
끝난다. 그 틈에 다시 열면 0x3E(status 62)나 반쪽 연결이 된다. 마지막 disconnect 로부터
충분히 지났으면 기다리지 않는다 — 평소 연결이 느려지면 안 된다.

예약은 하나만 둔다(두 번 열리면 하나가 고아가 된다). [연결 해제] 는 예약을 **취소**한다 —
안 그러면 끊은 뒤 600ms 만에 스스로 다시 연결된다.

테스트 143개 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 14:29:42 +09:00
..