9148c358d0
Topic_Agent/Topic_Blog/Topics/Topics_Biz/Topics_Meeting/Topics_Rag의 마크다운 지식 문서를 Topic_General/Topic_Programming/Topic_Graphic/Topic_Business 4개 카테고리로 재분류. - 중복 제거: frontmatter의 status:duplicate/merged + duplicate_of/redirect_to 필드로 자기 자신을 중복으로 선언한 리다이렉트 stub 1032개 제거, 완전 동일 내용 파일 472개 제거, 동일 파일명·다른 내용 충돌 시 더 큰(완전한) 버전만 유지(162개 제거) — 총 1639개 중복 제거. - 분류: 폴더 단위로 명확한 항목(AI_and_ML/Coding/Architecture 등 → Programming, Comfyui/Visual_Effects → Graphic, Topics_Biz/Topics_Meeting/사업 등 → Business, Poetic_Blog_Writing/창의성/Game_Design 등 → General)은 폴더 우선순위로, 나머지 혼재 폴더(Topic_Agent/Topic_Blog/Topics 루트/Thinking & Reasoning/Other/UI_UX_Assets)는 title/tags 키워드 스코어링으로 파일 단위 분류(불명확한 경우 General로 폴백). 원본 폴더명은 "From_*" 서브폴더로 보존해 추적 가능성 유지. - 최종 배치: Programming 2784 / General 1608 / Graphic 285 / Business 249 = 4926개 문서. - 에이전트 운영 상태(.astra/.agent/.obsidian/sessions/memory/_company/docs/lessons/_shared/src)는 지식 콘텐츠가 아니므로 재분류 대상에서 제외하고 원위치 유지. - Topics/Topic_email(상위 보호 폴더 Topic_email과 파일명 100% 중복) 삭제 — 보호 폴더 자체는 미변경. - 완전히 비게 된 Topic_Agent/Topic_Blog/Topics_Biz/Topics_Rag 폴더 제거.
5.1 KiB
5.1 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| android-bluetooth-le-scanning | Android BLE — Scan / Connect / GATT | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
Android Bluetooth LE
Scan → Connect → Discover Services → Read/Write/Notify 흐름. 권한 / lifecycle / state machine 까다로움. Nordic / Polidea 같은 라이브러리 권장.
📖 핵심 개념
- BLE: low energy. peripherals (sensor, watch).
- GATT: 서비스 / characteristic / descriptor.
- Scan filter: UUID / name / MAC.
- Permission (Android 12+): BLUETOOTH_SCAN, BLUETOOTH_CONNECT.
💻 코드 패턴
Permission
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Android 11- -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
Scan
@SuppressLint("MissingPermission") // Permission check 별도
class BleScanner(private val ctx: Context) {
private val adapter = (ctx.getSystemService(BluetoothManager::class.java)).adapter
private val scanner: BluetoothLeScanner? get() = adapter?.bluetoothLeScanner
fun scan(serviceUuid: UUID): Flow<ScanResult> = callbackFlow {
val filter = ScanFilter.Builder().setServiceUuid(ParcelUuid(serviceUuid)).build()
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
val cb = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
trySend(result)
}
override fun onScanFailed(errorCode: Int) {
close(IllegalStateException("scan failed: $errorCode"))
}
}
scanner?.startScan(listOf(filter), settings, cb)
awaitClose { scanner?.stopScan(cb) }
}
}
Connect + GATT
class BleClient(private val ctx: Context, private val device: BluetoothDevice) {
private var gatt: BluetoothGatt? = null
@SuppressLint("MissingPermission")
suspend fun connect(): BluetoothGatt = suspendCancellableCoroutine { cont ->
gatt = device.connectGatt(ctx, false, object : BluetoothGattCallback() {
override fun onConnectionStateChange(g: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
g.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
g.close()
cont.resumeWithException(Exception("disconnected"))
}
}
override fun onServicesDiscovered(g: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) cont.resume(g)
else cont.resumeWithException(Exception("services discovery failed"))
}
})
}
suspend fun read(serviceUuid: UUID, charUuid: UUID): ByteArray { ... }
suspend fun write(serviceUuid: UUID, charUuid: UUID, data: ByteArray) { ... }
fun notifications(serviceUuid: UUID, charUuid: UUID): Flow<ByteArray> { ... }
fun disconnect() {
gatt?.disconnect()
gatt?.close()
gatt = null
}
}
MTU / connection priority
gatt.requestMtu(247) // 큰 패킷
gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH) // 빠른 throughput
🤔 의사결정 기준
| 상황 | 도구 |
|---|---|
| Direct BLE | Android API + wrapper |
| 복잡 GATT 시나리오 | Nordic/Polidea/Kable 라이브러리 |
| Companion Device Pairing (Android 8+) | CompanionDeviceManager (자동 pair UI) |
| BLE Beacon scanning | iBeacon / Eddystone 라이브러리 |
| BLE peripheral (앱이 server) | BluetoothGattServer |
❌ 안티패턴
- Scan 무한: 배터리 폭발 + Android 가 throttle (5회/30s). 짧게 + filter.
- discoverServices 없이 read/write: characteristic null.
- gatt.close() 누락: 메모리 + 다음 connect 실패.
- 메인 스레드 callback 안에서 무거운 작업: 다른 callback 못 받음.
- 권한 체크 안 함 Android 12+: SecurityException.
- 여러 callback 같은 gatt: 마지막 것만 호출.
- MTU 협상 안 함: 작은 패킷 만. 247 바이트 설정.
- disconnect 후 즉시 connect: race. delay 또는 state machine.
🤖 LLM 활용 힌트
- 권한 + Scan filter + state machine + close 4종 강조.
- 라이브러리 권장 — Kable (Kotlin Multiplatform) 또는 Nordic.