보이스 프로필 연동 규격
이 페이지는 E2E Sample 참고 자료다. 직접 구현 협의는 담당별 범위, DeviceAgent ↔ AI-Agent 구현 요청서, DeviceAgent ↔ 등록 App 구현 요청서를 사용한다. 아래 APP·Backend·IoTAgent의 API·토픽은 외부 담당들이 협의할 제품 설계를 강제하지 않는다.
09-17 변경: 관심사 원본은 서버로 이전했다. App의 최초 등록 후·상시 변경은 HTTPS 저장이며, AI-Agent 조회는 반대 방향인 AI-Agent → DeviceAgent → IoTAgent → 서버다. 신규 필드·토픽·그림. 아래 interestsSet/interestsGet은 이전 로컬 규격으로 현재 AI-Agent가 SERVER_OWNED_PERSONALIZATION을 반환한다.
담당별 시작 위치
이 페이지는 전체 연결과 시나리오의 기준이다. 실제 코딩에 필요한 필드·타입·예제는 아래 상세 명세에서 확인한다.
| 내가 개발하는 모듈 | 연결할 상대 | 구현할 내용 | 상세 명세 |
|---|---|---|---|
| DeviceAgent | Registration App·AI-Agent·IotAgent | Binder 요청 분기, 호출자 검증, 등록 화면 실행, callback 중계, commit 저장/응답 | 함수·Bundle 명세 |
| IotAgent | DeviceAgent·AWS IoT Core | MQTT 구독/발행, action→method 변환, JSON↔Bundle 타입 보존, 실패 결과 반환 | IoT Core 토픽 명세 + callback 배선 |
| 모바일 App | AWS Backend | 등록·취소·조회·삭제 API, 등록 상태 실시간 표시, 오류 화면 | HTTPS API + 실시간 상태 |
| AWS Backend·관제 | 모바일 App·IotAgent | 계정/기기 권한, MQTT 요청/결과 연결, 상태 API, Cloud 저장/ACK | HTTPS·MQTT 명세 |
| Registration App | DeviceAgent 경유 AI-Agent | 등록 제어 Bundle 전송, 상태 callback 표시 | 등록 제어 + 상태 callback |
| AI-Agent | DeviceAgent | 녹음/식별 실행, 상태 생성, 프로필 commit, 관리 결과 | 프로필 전달 + 관리 API |
상세 명세에서 확인할 것
기기 Binder는 등록 App ↔ DeviceAgent와 AI-Agent ↔ DeviceAgent로 나눠 요청·응답 값을 확인한다.
| 문서 | 들어 있는 내용 | 이 문서만으로 확정하지 않는 것 |
|---|---|---|
| 등록 App·AI-Agent ↔ DeviceAgent 인터페이스 정의 | 실제 함수, 메서드별 필수 입력, Android 타입, 수신 대상, 비동기 결과, Kotlin 예제 | 제품 Cloud 저장 완료 처리 |
| IoTAgent ↔ IoT Core 토픽 명세 | 발행·구독 주체, 9종 action, JSON 필드·결과, 상태/대화 이벤트, QoS·만료·오류 | 임베딩 업로드·Cloud ACK의 미확정 wire format |
| 모바일·관제 HTTPS·MQTT 명세 | URL/method, JSON 예제, 토픽과 발행/구독, 타입 변환, HTTP 오류, SSE | 제품 인증 API, 임베딩 업로드 wire format, Cloud ACK 토픽 |
현재 코드에 있는 계약과 제품에서 추가 정의할 계약을 혼동하지 않는다. 추가 항목은 Cloud 전달 경계에 모았다.
읽는 순서
- 아래 흐름도에서 내 모듈의 송신·수신 상대를 확인한다.
- 담당별 상세 명세에서 요청 Bundle/JSON과 결과 callback을 한 쌍으로 구현한다.
- 상태·오류 표를 화면과 Backend 처리에 연결한다.
- 상세 명세의 구현 확인 항목으로 정상·실패·취소를 각각 시험한다.
먼저 보는 결론
- 모바일 App은 HTTPS만 사용하고 MQTT에 직접 연결하지 않는다.
- A1에서 MQTT에 연결하는 모듈은 IotAgent 하나다.
- DeviceAgent는 요청과 호출자를 검증하고 Binder 메시지를 중계한다.
- 등록 App은 UI를, OnDevice Agent Service는 녹음·검증·프로필 생성을 담당한다.
- 한 작업은 시작부터 종료까지 같은
requestId를 사용한다. - 원본 음성과 Query Embedding은 MQTT·Cloud로 보내지 않는다.
- 현재 로컬 PoC와 제품 AWS 연동은 같은 것이 아니다.
구현 상태 표시
| 표시 | 의미 |
|---|---|
| 구현됨 | 현재 소스에서 경로와 검증 로직을 확인함 |
| PoC 전용 | 로컬 Gateway·MQTT·시스템 프로퍼티를 이용한 시험 경로 |
| 제품 필요 | AWS Backend/IotAgent/DeviceAgent에 추가 연동이 필요한 부분 |
계약 버전
| 범위 | contractVersion |
|---|---|
| 등록·프로필 조회·삭제 | voice-profile-v2 |
| 서버 관심사 조회·응답 | voice-personalization-v1 |
지원하지 않는 버전은 실행하지 않는다. 제품에서는 오류 응답까지 반환해야 하며, 현재 일부 수신 경로는 로그만 남기고 반환하므로 보완이 필요하다.
통신 경계
| 송신 → 수신 | 방식 | 현재 상태 | 목적 |
|---|---|---|---|
| 모바일 App → AWS Backend | HTTPS/JSON | 제품 필요 | 등록·취소·조회·삭제·관심사 요청 |
| AWS Backend ↔ AWS IoT Core | AWS SDK/MQTT | 제품 필요 | 기기 명령과 상태 전달 |
| PC Gateway ↔ 로컬 MQTT | HTTP/SSE + MQTT | PoC 전용 | AWS 없이 모바일 Sample 시험 |
| MQTT → IotAgent | MQTT JSON | 명령 수신 구현됨 | A1의 MQTT 단일 진입점 |
| IotAgent ↔ DeviceAgent | Binder Bundle |
구현됨 | JSON과 기기 내부 계약 변환 |
| DeviceAgent ↔ 등록 App | Binder callback + Activity | 구현됨 | 화면 실행과 실시간 상태 전달 |
| DeviceAgent ↔ OnDevice Agent Service | Binder Bundle |
구현됨 | 등록 제어·프로필 전달·관리 API |
등록 메시지 흐름
프로필 조회와 삭제 흐름
모바일 App의 목록은 Gateway 캐시가 아니라 A1 기기에 로컬 저장된 프로필을 조회한다.
조회는 기기에 저장된 프로필 목록이며 Cloud 동기화 완료 목록이 아니다. 삭제는 voiceId를 기준으로 로컬 프로필·임베딩을 정리한다. 서버 관심사 삭제는 서버 후속 처리다. DeviceAgent의 전달 대기 사본 삭제는 별도 시도이며 실패해도 기존 성공 결과가 전달될 수 있다. 전체 사본 삭제나 Cloud 삭제 완료로 해석하지 않는다.
서버 관심사 조회 흐름
관심사는 서버가 voiceId를 기준으로 관리하고 모바일 App에서 변경한다. AI-Agent가 getVoicePersonalization을 요청하면 DeviceAgent → IoTAgent → 서버로 전달하고, 역방향 voicePersonalizationResult 콜백의 interestCodes:ArrayList<String>으로 받는다. AI-Agent 로컬 DB 조회가 아니다. 현재 샘플 MQTT 경로와 실제 AWS 운영 연결은 구분한다.
전체 시나리오 흐름
등록 시작, 진행 상태, 완료와 Cloud ACK, 취소·실패, 목록 조회·삭제를 한 화면에서 확인할 수 있다. 실선은 현재 기기 구현 경로이고, 보라색 점선은 제품 AWS 연동에서 추가해야 하는 경로다.
모바일 HTTPS API
아래 경로는 현재 PC Gateway와 모바일 Sample App에서 동작한다. 제품 AWS Backend는 같은 의미를 유지해 구현한다.
| Method | Path | 설명 |
|---|---|---|
POST |
/voice-profiles/enrollments |
등록 시작 |
GET |
/voice-profiles/enrollments/{requestId} |
등록 상태 조회 |
POST |
/voice-profiles/enrollments/{requestId}/cancel |
등록 취소 |
GET |
/voice-profiles?deviceId=... |
기기 등록 화자 조회 |
DELETE |
/voice-profiles/{voiceId}?deviceId=... |
화자 삭제 |
GET |
/voice-profiles/{voiceId}/interests |
관심사 조회 |
PUT |
/voice-profiles/{voiceId}/interests |
관심사 저장 |
GET |
/voice-events?deviceId=... |
PoC 실시간 SSE 이벤트 |
등록 시작 요청
{
"contractVersion": "voice-profile-v2",
"requestId": "vp-20260911-0001",
"method": "startVoiceProfileEnrollment",
"deviceId": "A1-001",
"nickname": "silogood",
"locale": "ko-KR",
"personalizationEnabled": true,
"issuedAtMs": 1789092000000,
"expiresAtMs": 1789092300000
}
| 필드 | 설명 | 규칙 |
|---|---|---|
requestId |
한 등록 작업의 추적 ID | 재시도해도 같은 작업이면 유지 |
deviceId |
대상 A1 기기 | Backend가 접근 권한 확인 |
nickname |
표시 이름 | 식별 키로 사용하지 않음 |
locale |
등록 화면·발화 언어 | 예: ko-KR, en-US |
personalizationEnabled |
개인화 사용 여부 | true 또는 false |
issuedAtMs |
명령 생성 시각 | Unix epoch millisecond |
expiresAtMs |
명령 만료 시각 | 현재 Sample은 생성 후 5분 |
MQTT 계약
토픽
| 방향 | 토픽 | QoS | 상태 |
|---|---|---|---|
| Backend → A1 | skmg/airbot/{deviceId}/v1/command/voiceProfileEnrollment |
1 권장 | 명령 수신 구현됨 |
| A1 → Backend | skmg/airbot/{deviceId}/v1/response/voiceProfileEnrollment/{requestId} |
1 권장 | 로컬 Bridge만 구현됨 |
상태 이벤트와 최종 결과는 현재 PoC에서 같은 response 토픽에 발행된다. 제품 AWS에서도 requestId가 포함된
동일 응답 경로를 사용하면 모바일·Backend의 상관관계 처리가 단순해진다.
명령 봉투
{
"action": "start",
"payload": {
"contractVersion": "voice-profile-v2",
"requestId": "vp-20260911-0001",
"nickname": "silogood",
"locale": "ko-KR",
"personalizationEnabled": true,
"issuedAtMs": 1789092000000,
"expiresAtMs": 1789092300000
}
}
action |
DeviceAgent 메서드 | 계약 |
|---|---|---|
start |
startVoiceProfileEnrollment |
voice-profile-v2 |
cancel |
cancelVoiceProfileEnrollment |
voice-profile-v2 |
list |
listVoiceProfiles |
voice-profile-v2 |
delete |
deleteVoiceProfile |
voice-profile-v2 |
interestsSet |
setVoiceProfileInterests |
voice-memory-v1 |
interestsGet |
getVoiceProfileInterests |
voice-memory-v1 |
상태 이벤트
{
"contractVersion": "voice-profile-v2",
"method": "voiceProfileStateChanged",
"requestId": "vp-20260911-0001",
"stateSequence": 8,
"state": "SET_ACCEPTED",
"setIndex": 2,
"setCount": 5,
"progress": 100,
"reasonCode": "",
"terminal": false,
"nickname": "silogood"
}
| 필드 | 설명 |
|---|---|
stateSequence |
UI가 이전보다 큰 값만 반영하는 상태 순번. 현재 생성 완료·품질 실패 경로의 송신 순번 보완 필요 |
state |
등록 상태 enum |
setIndex / setCount |
현재 녹음 세트와 전체 세트 수 |
progress |
현재 단계 진행률 0..100 |
reasonCode |
실패·대기 이유. 성공이면 빈 문자열 가능 |
terminal |
true면 더 이상 진행되지 않는 최종 상태 |
voiceId |
COMPLETED에서 필수 |
Binder 메서드
등록 화면과 세션 제어
| 메서드 | 호출 방향 | 설명 |
|---|---|---|
getVoiceProfileEnrollmentState |
등록 App → Service | 최신 상태 복구 |
startVoiceProfileEnrollment |
DeviceAgent/등록 App → Service | 등록 시작 |
retryVoiceProfileSet |
등록 App → Service | 실패한 세트 재녹음 |
pauseVoiceProfileEnrollmentForExit |
등록 App → Service | 나가기 확인 동안 일시 정지 |
cancelVoiceProfileEnrollment |
등록 App/IotAgent → Service | 등록 취소 |
setVoiceProfilePersonalization |
등록 App → Service | 개인화 설정 저장 |
retryVoiceProfileCreation |
등록 App → Service | 로컬 프로필 생성 재시도 |
retryVoiceProfileDelivery |
등록 App → Service | DeviceAgent 전달 재시도 |
closeVoiceProfileRegistration |
등록 App → Service | 등록 UI 종료 |
voiceProfileStateChanged |
Service → 등록 App/IotAgent | 실시간 상태 이벤트 |
프로필 전달과 관리
| 요청 | 결과 | 설명 |
|---|---|---|
commitVoiceProfile |
voiceProfileCommitResult |
생성 프로필을 DeviceAgent에 전달 |
listVoiceProfiles |
voiceProfileListResult |
등록 화자 조회 |
deleteVoiceProfile |
voiceProfileDeleteResult |
프로필과 관련 데이터 삭제 |
setVoiceProfileInterests |
voiceProfileInterestsResult |
관심사 저장 |
getVoiceProfileInterests |
voiceProfileInterestsResult |
관심사 조회 |
등록 상태 enum
| 값 | 의미 | terminal |
|---|---|---|
WAITING_START |
사용자 시작 대기 | false |
RECORDING_SET |
현재 세트 녹음 중 | false |
SET_ACCEPTED |
현재 세트 통과 | false |
SET_REJECTED |
현재 세트 거절, 재녹음 가능 | false |
PERSONALIZATION_SETTINGS |
개인화 사용 여부 선택. 발음 수정 아님 | false |
CREATING_LOCAL_PROFILE |
임베딩·Voice ID 생성 | false |
PENDING_DEVICE_DELIVERY |
DeviceAgent 전달 중 | false |
COMPLETED |
등록 완료 | true |
PROFILE_FAILED |
시작 거절 또는 로컬 프로필 생성 실패 | 시작 거절 true / 생성 실패 false |
DELIVERY_FAILED |
DeviceAgent 또는 Cloud 전달 미완료, 재시도 가능 | false |
CANCELLED |
사용자·원격 취소 | true |
SET_REJECTED, PROFILE_FAILED, DELIVERY_FAILED는 재시도 UI를 제공할 수 있지만 해당 상태 이벤트 자체는 현재 세션 결과를 나타낸다.
오류와 대기 코드
오디오·임베딩 검사
| 코드 | 의미 | 권장 UI |
|---|---|---|
NO_SPEECH |
음성 샘플 없음 | 다시 녹음 |
TOO_SHORT |
유효 길이 부족 | 더 길게 말하도록 안내 |
INVALID_AUDIO |
유효하지 않은 샘플 | 다시 녹음 |
TOO_QUIET |
음량 부족 | 기기에 가까이 말하도록 안내 |
TOO_LOUD |
클리핑 과다 | 조금 떨어져 말하도록 안내 |
AUDIO_BIAS |
DC 편향 과다 | 다시 녹음 |
INSUFFICIENT_SPEECH |
실제 발화 구간 부족 | 문장을 이어 말하도록 안내 |
TOO_NOISY |
신호 대 잡음비 부족 | 조용한 곳에서 다시 녹음 |
INVALID_EMBEDDING |
임베딩 값·차원 오류 | 프로필 생성 재시도 |
INCONSISTENT_SPEAKER |
세트 간 동일 화자 조건 미달 | 같은 사용자가 다시 등록 |
취소·전달
| 코드 | 의미 | 현재 처리 |
|---|---|---|
USER_CANCELLED |
등록 App에서 취소 | 임시 데이터 정리 |
MOBILE_CANCELLED |
모바일 원격 취소 | 같은 requestId 세션 취소 |
DEVICE_DELIVERY_REJECTED |
DeviceAgent가 프로필 payload 거절 | DELIVERY_FAILED |
DEVICE_OUTBOX_FAILED |
암호화 대기 저장 실패 | commit 거절 |
WAITING_CLOUD_SYNC |
DeviceAgent 저장은 성공, Cloud ACK 미연결 | DELIVERY_FAILED, 제품 연동 필요 |
LOCAL_TEST_ACK |
PoC 강제 완료 | Cloud 완료로 해석 금지 |
프로필 commit 계약
Service가 DeviceAgent에 전달하는 필드는 다음과 같다.
| 필드 | 필수 | 설명 |
|---|---|---|
requestId |
O | 등록 요청 추적 ID |
voiceId |
O | Service가 생성한 식별 ID |
nickname |
O | 표시 이름 |
locale |
O | 기본 언어 |
personalizationEnabled |
O | 개인화 사용 여부 |
referenceEmbedding |
O | 등록 대표 임베딩. 원본 음성이 아님 |
현재 DeviceAgent는 payload를 검증한 뒤 EncryptedSharedPreferences 기반 대기 저장소에 저장한다.
{
"contractVersion": "voice-profile-v2",
"method": "voiceProfileCommitResult",
"requestId": "vp-20260911-0001",
"accepted": true,
"cloudSynced": false,
"reasonCode": "WAITING_CLOUD_SYNC"
}
현재 대기 저장소에 구현된 기능은 save()와 removeByVoiceId()다. 목록 조회, 재부팅 후 자동 재전송,
Cloud ACK 수신 및 ACK 후 삭제는 제품 구현이 필요한 기능이다.
순서·중복·만료 처리
requestId: 요청과 모든 상태·결과를 연결한다.stateSequence: 같은 요청에서 더 큰 값만 UI에 반영한다.- 만료 검사는
expiresAtMs기준이다.issuedAtMs자체로 만료를 판단하지 않으며, 만료값 누락 시 현재 기본값은Long.MAX_VALUE다. 제품 발신자는 만료값을 반드시 지정해야 한다. - IotAgent 명령 경로와 DeviceAgent 원격 시작·취소 경로에 만료 검사가 있고 Service 자체에는 없다.
- MQTT QoS 1의 재전달을 안전하게 처리하는 것은 제품 요구사항이다. 현재 같은
requestId의 start도 세트를 초기화하므로 자동 재시작 대신 상태 조회가 필요하다. - 다른 활성 요청은 Service에서
ANOTHER_SESSION_ACTIVE로 거절하지만, 등록 App을 거치는 원격 요청은 화면에서 무시되어 실패가 돌아오지 않을 수 있다.
<a id="implementation-gaps"></a>
현행 한계와 담당별 보완
아래는 구현 완료 항목이 아니라 제품 적용 전 남은 작업이다. 실제 메서드·기본값은 Binder 13절을 함께 확인한다.
| 담당 | 현재 확인된 한계 | 완료 조건 |
|---|---|---|
| AI-Agent | 같은 start가 세트 초기화 | 동일 요청 재수신은 기존 상태 반환, 녹음 중복 시작 없음 |
| AI-Agent | 생성 중 취소 후 늦은 완료, 700ms 후속 작업 실행 가능 | 취소·새 요청 이후 이전 작업이 상태·저장 결과를 되살리지 않음 |
| AI-Agent | 이전 session 복사로 상태 순번 역전 가능 | 모든 상태 경로에서 순번 증가; UI가 완료/오류를 놓치지 않음 |
| AI-Agent·등록 App | 생성 실패 후 세트가 비워져 생성 재시도 무동작 가능 | 남은 데이터에 맞춰 재녹음 또는 재생성을 안내 |
| 등록 App·DeviceAgent·IotAgent | 충돌·화면 실행 실패·잘못된 요청이 원격에 무응답일 수 있음 | 실패를 같은 requestId로 전달하고 모바일 대기 종료 |
| DeviceAgent | UID에 속한 패키지 중 하나가 허용되면 통과 | 공유 UID 신뢰 범위를 명시하고 개별 앱 구분 필요 시 인증 방식 보완 |
| AI-Agent·DeviceAgent | 로컬 삭제는 전체 트랜잭션 아님; 대기 사본 제거 실패가 성공 응답에 가려짐 | 부분 실패 표시, 재시도·재조회로 각 사본 삭제 확인 |
| IotAgent·AWS Backend | 제품 결과 발행·임베딩 업로드·Cloud ACK 미연결 | 업로드 형식·저장 ACK·실패 응답 확정 후 실제 AWS 연동 |
| DeviceAgent·AI-Agent·AWS Backend | ACK 대기 타이머·재부팅 복구 미완성 | 대기 저장 조회·재전송·늦은 ACK 처리·성공 후 정리 검증 |
등록 Service의 세션/pending은 메모리다. DeviceAgent의 암호화 대기 파일이 있다고 해서 Service 세션이 재부팅 후 자동 복구되는 것은 아니다.
보안과 데이터 제한
- 모바일 App에는 AWS IoT 인증서나 MQTT 비밀값을 넣지 않는다.
- IotAgent만 기기 MQTT 연결을 소유한다.
- DeviceAgent는 UID에 속한 패키지와 허용 메서드를 검사한다.
android.uid.system을 공유하는 앱 각각의 신원까지 분리해 인증하는 것은 아니다. Bundle의 caller 문자열만으로 이 한계를 해결할 수 없다. - 새 voice-profile-v2 등록 경로는 원본 PCM/WAV를 외부로 보내지 않는다. Query Embedding도 기기 내부에서 사용한다. 기존 수동 등록의 WAV 저장 경로는 별도로 점검한다.
- Reference Embedding은 최소 권한, 전송 암호화, 저장 암호화와 삭제 추적을 적용한다.
- 로그에는 임베딩 배열, 원본 음성, 계정 식별정보를 출력하지 않는다.
- 제품 삭제 목표는 로컬·전달 대기·Cloud 사본 정리다. 현재 로컬 성공 응답만으로 모든 사본 삭제를 보장하지 않는다.
PoC 실행 경로
현재 로컬 시험은 다음 경로를 사용한다.
모바일 Sample App
→ HTTP/SSE
PC Gateway :8788
→ 로컬 MQTT :1883
IotAgent VoiceProfileMqttTestBridge
→ DeviceAgent → 등록 App / OnDevice Agent Service
IotAgent 시험 Bridge는 persist.sys.vp.mqtt_test_uri로 활성화한다. adb reverse tcp:1883 tcp:1883은
USB/ADB 연결 시험용이며 제품 통신 경로가 아니다.
제품 구현 점검표
| 항목 | 현재 | 완료 조건 |
|---|---|---|
| 제품 MQTT 명령 수신 | 구현됨 | AWS IoT Core에서 시작·취소·조회·삭제 확인 |
| 제품 MQTT response/event 발행 | 미연결 | IotAgent가 상태와 결과를 AWS에 발행 |
계정-voiceId 연결 |
미구현 | Backend Registry 저장·조회·삭제 |
| Reference Embedding 업로드 | 미구현 | 암호화 업로드와 접근 통제 |
| Cloud ACK | 미구현 | DeviceAgent까지 ACK 반환 |
| 대기 데이터 재전송 | 미구현 | 재부팅·네트워크 복구 후 자동 재시도 |
| ACK 후 대기 데이터 삭제 | 미구현 | 성공 ACK에 한해 삭제 |
| 원본 음성 비전송 | 구현됨 | 로그·Binder·MQTT에서 PCM/WAV 없음 확인 |
연동 시험 순서
아래는 완료 판정에 필요한 시험 목록이며 모두 통과했다는 결과표가 아니다. 특히 중복 시작·생성 중 취소·순번 역전은 위 보완 이후 회귀 시험이 필요하다.
- 같은
requestId가 모바일, Gateway/Backend, MQTT, IotAgent, DeviceAgent, Service 로그에 남는지 확인한다. WAITING_START → RECORDING_SET → ... → COMPLETED와 증가하는stateSequence를 확인한다.- 실패 세트에서
SET_REJECTED와 정확한reasonCode가 오는지 확인한다. - 취소 후 녹음이 멈추고
CANCELLED,terminal=true가 오는지 확인한다. COMPLETED에 유효한voiceId가 있는지 확인한다.- 등록 후 list 결과와 기기 로컬 매칭 결과가 같은
voiceId인지 확인한다. - 패킷·로그·저장소에 원본 PCM/WAV가 없는지 확인한다.
- 제품 AWS 경로에서는 Cloud ACK 전후의 대기 저장 유지·삭제를 별도로 확인한다.
/health 성공이나 빈 profile 목록 응답은 연결만 증명한다. 등록 성공은 같은 requestId의
COMPLETED + terminal=true + voiceId까지 확인해야 한다.
구현 근거와 상세 문서
기준 코드는 VoiceProfileContract.java, CmdTopic.java, VoiceProfileMqttTestBridge.java,
DeviceAgent.java, VoiceProfilePendingStore.java, VoiceProfileIpcContract.kt와
ForegroundService.kt다.