← Docs hub

모바일·관제 전체 인터페이스 명세

제품 연동 보강: 초기 등록·관심사 완료 화면, PUT 응답 유실과 복구, 계정·프로필 접근 권한, 동시 수정·버전 충돌, 시험표. 버전 조건·HTTP 409는 제품 제안이며 현재 샘플 API에 구현된 필드·결과가 아니다.

09-29 변경: 초기 등록은 설정 → 관심사 선택 → 등록 진행 순서다. 관심사는 비워도 된다. Gateway가 등록 완료 후 실제 voiceId·개인화 동의를 확인하고 서버 DB에 저장한다. 평소 수정은 화자별 HTTPS GET/PUT을 사용한다. 기기에 interestsGet/interestsSet을 보내는 경로는 사용하지 않는다. 서버 저장·AI-Agent 조회 전체 규격. 제품 계정 인증은 별도 통합한다.

모바일 등록 화면과 완료 기준

화면 사용자 동작 처리
1. 설정 연결 정보·닉네임·언어·개인화 사용 여부 입력 후 다음 아직 등록 요청을 보내지 않음
2. 관심사 0~10개 선택 후 등록요청 보내기 빈 목록 허용. 뒤로 가면 설정과 선택값 유지
3. 진행 기기에서 녹음, 모바일에서 상태 확인 요청 중 중복 전송 방지. requestId·순번으로 상태 반영
관심사 저장 실패 관심사 저장 재시도 녹음 재시작 없이 같은 등록 건의 서버 저장 재시도
등록 이후 수정 화자 탭 → 화자 선택 → 관심사 → 저장 해당 voiceId의 관심사 전체 교체; 빈 목록은 비우기

초기 관심사와 실제 등록 결과의 연결

interestCodes는 모바일 → Gateway 등록 요청에만 추가된다. Gateway는 이를 임시 보관하고 기기 MQTT start에서는 제거한다. AI-Agent가 만든 voiceId가 돌아오면 기기 목록에서 같은 ID와 실제 개인화 동의를 확인한 뒤 서버에 저장한다. 닉네임만으로 연결하지 않는다.

interestSaveState 의미 모바일 처리
NONE 관심사 필드가 없는 이전 요청 등록 상태만 표시
PENDING 등록 완료를 기다리는 선택값 저장 완료로 표시하지 않음
SAVING 실제 프로필 확인·관심사 저장 중 진행 상태 유지
SAVED 샘플 서버 DB 저장 완료 진행 화면에서 완료 표시
FAILED 프로필 확인 또는 저장 실패 저장만 재시도 가능
SKIPPED 취소·실패 또는 개인화 비허용으로 저장 안 함 관심사 저장 성공과 구분

관심사 편집기 상태 정리

등록 이후 관심사 편집기는 선택한 deviceId·voiceId와 요청 번호를 함께 관리한다.

상태·조건 모바일 처리
LOADING 관심사 조회 응답을 기다리며 저장할 수 없음
READY 조회가 성공한 뒤에만 체크박스 변경과 저장 허용
SAVING 중복 저장을 막고 프로필 삭제·목록 새로고침·다른 화자 선택·Backend API 교체를 차단
오래된 조회·저장 응답 현재 요청 번호와 맞지 않으면 반영하지 않음
저장 성공 응답 success와 응답 voiceId가 선택 화자와 같은지 확인한 뒤 완료 표시
기기·화자·API 변경 편집 상태와 선택값을 초기화

빈 interestCodes: []도 유효한 선택이며, 개인화 허용 프로필에는 빈 목록으로 저장한다. COMPLETED만으로 관심사 저장 또는 임베딩 Cloud 저장까지 성공한 것은 아니다.

취소 요청은 먼저 CANCEL_REQUESTED로 표시하고 임시 관심사를 폐기한다. 실제 기기 CANCELLED 이벤트를 받은 뒤 취소 완료로 표시한다. MQTT 발행 실패는 HTTP 502이며 기기 취소 성공으로 취급하지 않는다. 취소 뒤 늦게 도착한 완료 이벤트는 관심사를 저장하지 않는다.

등록 진행·임시 선택값은 Gateway 메모리라 서버 재시작 시 복구되지 않는다. 이미 저장된 관심사는 SQLite에 남는다. 화면 재생성 시 모바일의 요청 ID와 입력값은 복원하지만 서버 작업까지 복구하는 것은 아니다.

관심사 저장 재시도 예시

POST /voice-profiles/enrollments/vp-example-001/retry-interests, body {}. 존재하는 요청은 HTTP 200과 최신 등록 상태를 반환한다. 없는 요청은 HTTP 404와 {"error":"request_not_found"}다. HTTP 200만으로 저장 성공을 판단하지 않고 interestSaveState를 확인한다. 완료된 등록의 PENDING/FAILED만 재시도하며 SAVED 등은 상태만 반환한다.

{"requestId":"vp-example-001","state":"COMPLETED","terminal":true,"voiceId":"example-voice-001","interestSaveState":"SAVED"}

위 응답은 핵심 필드 발췌다. 등록 state와 interestSaveState는 별도이며 기존 기기 Binder 상태 enum을 추가한 것이 아니다.

검증: Gateway 31건, 모바일 JVM 13건, Android 화면 시험 2건 통과. 실기기 등록 화면 진입·취소 왕복 확인. 새 실제 음성 등록 완료와 실제 휴대폰 화면 전체 검증은 별도 확인이 필요하다.

전체 명세: HTTP API · 9개 MQTT action 예제 · 미완료 계약. 앞부분은 공통 규칙이며 각 API의 요청·결과·오류·재시도는 7절부터 확인한다.

모바일·Backend 개발자와 IotAgent 개발자가 주고받을 요청·결과의 약속이다. 기기 내부 구현은 DeviceAgent 함수·Bundle 명세를 사용한다. 모바일은 HTTPS로 Backend에 요청하고 Backend는 MQTT로 IotAgent에 전달한다. 현재 API 예제는 PC Gateway 구현 기준이며 제품 인증·AWS 결과 발행은 추가 구현이 필요하다.

담당별 읽는 범위

담당 필수로 읽을 절 구현 결과
모바일 App 2. HTTPS, 4. 실시간 상태 요청 화면과 실제 기기 결과가 일치
AWS Backend·관제 2. HTTPS, 3. MQTT, 5. Cloud 저장 API 요청을 기기 결과와 연결하고 계정 권한 보장
IotAgent 3. MQTT, DeviceAgent Bundle 명세 JSON→Bundle 요청과 Bundle→JSON 결과 양쪽 구현

서로 다른 세 가지 결과

확인한 결과 알 수 있는 것 아직 알 수 없는 것
HTTP 202 / MQTT 발행 성공 Backend가 요청을 접수하거나 보냄 등록 화면이 열렸는지, 녹음했는지
sendModuleCommand 반환 0 DeviceAgent가 해당 분기의 요청을 수락/중계함 AI-Agent 작업이 성공했는지
상태/관리 callback 기기 작업의 진행 상태 또는 실제 결과 별도 ACK 없는 Cloud 저장 완료

모바일 성공 화면은 요청 전송 결과가 아니라 해당 작업의 상태·결과로 판단한다.

1. 연결 원칙

구간 송신자가 할 일 수신자가 할 일
모바일 → Backend deviceId, 요청 데이터 전송 로그인 계정의 기기·voiceId 접근 권한 확인
Backend → IotAgent 명령 토픽에 action/payload 발행 구독 메시지 검증, Bundle 변환, DeviceAgent 호출
AI-Agent → DeviceAgent → IotAgent 결과 method와 requestId 보존 응답 토픽에 JSON 발행
Backend → 모바일 HTTP 결과 또는 실시간 이벤트 전달 실제 기기 결과로 화면 갱신

모바일에 기기 인증서나 MQTT 전체 권한을 넣지 않는다. 제품 인증 방식·토큰 발급 URL은 Backend에서 확정한다. nickname은 표시 이름, voiceId는 AI-Agent가 만든 화자 키다. 로그인 계정과 voiceId 연결은 Backend가 관리한다.

2. HTTPS 요청 목록

base URL 뒤에 아래 경로를 붙인다. JSON body는 Content-Type: application/json. 제품은 HTTPS를 사용한다. 각 관리 API의 deviceId query를 생략하지 않는다.

method / path 입력 기기 action
POST /voice-profiles/enrollments 아래 등록 JSON start
GET /voice-profiles/enrollments/{requestId} 경로의 작업 ID Backend 상태 조회, 기기 호출 아님
POST /voice-profiles/enrollments/{requestId}/cancel 빈 JSON 가능 cancel
POST /voice-profiles/enrollments/{requestId}/retry-interests 빈 JSON 완료된 등록의 프로필 확인·관심사 저장 재시도; 녹음 시작 아님
GET /voice-profiles?deviceId={id} deviceId list
DELETE /voice-profiles/{voiceId}?deviceId={id} voiceId, deviceId delete
GET /voice-profiles/{voiceId}/interests?deviceId={id} voiceId, deviceId 없음; 서버 DB 조회
PUT /voice-profiles/{voiceId}/interests?deviceId={id} body {"interestCodes":["WEATHER"]} 없음; 서버 DB 저장
GET /voice-events?deviceId={id} requestId/voiceId 선택 필터 SSE 구독

관리 요청의 requestId는 Backend/Gateway가 생성한다. 등록은 아래 예제처럼 모바일이 제공하는 현재 API를 따른다. 제품에서 Backend 생성으로 바꾸면 모바일 API도 함께 변경한다.

등록 시작 body

{
  "contractVersion": "voice-profile-v2",
  "method": "startVoiceProfileEnrollment",
  "requestId": "vp-example-001",
  "deviceId": "A1-EXAMPLE",
  "nickname": "Niky",
  "locale": "ko-KR",
  "personalizationEnabled": true,
  "interestCodes": ["WEATHER", "HEALTH"],
  "issuedAtMs": 0,
  "expiresAtMs": 0
}

예제의 두 시각 0은 실제 송신 시 현재 Unix ms와 만료 시각으로 교체한다. Sample 만료는 5분이다.

필드 JSON 타입 요구사항
contractVersion / method string 예제 상수 그대로
requestId string 필수. 동일 작업의 상관관계 ID
deviceId string 필수. 토픽의 deviceId와 동일
nickname string 필수. trim 후 1~40자, 제어 문자·경로 구분자 금지
locale string 필수. 한국어 ko-KR, 영어 en-US. 언어값 규약과 현재 검증 범위
personalizationEnabled boolean 현재 입력 필드. 기기 화면의 최종 선택은 enabled Bundle로 별도 전달
interestCodes array of string 선택. [] 허용, 중복 금지, 최대 10종: WEATHER, AIR_QUALITY, HEALTH, SCHEDULE, TRAVEL, FOOD, CULTURE, SPORTS, FINANCE, TECHNOLOGY. Gateway가 보관하며 기기 start에는 미전달
issuedAtMs / expiresAtMs integer 생성/만료 시각. 만료한 요청은 재실행하지 않음

발음 보조 필드는 외부 계약에서 제거했다. 닉네임만 전달한다. 임의로 키를 추가한 것만으로 영어 발음 저장이 지원된다고 표시하지 않는다. 원본 음성·임베딩·모바일 계정 비밀번호는 이 요청에 넣지 않는다.

HTTP 응답의 의미

현재 등록 시작은 202이며 본문에 requestId, state, detail, terminal, updatedAtMs, nickname, voiceId, stateSequence, interestSaveState가 들어간다. 등록 전 voiceId는 빈 문자열이다. 선택한 관심사 원문은 상태 응답에 노출하지 않는다. state가 DEVICE_COMMAND_SENT여도 등록 화면 실행이나 녹음 완료를 보장하지 않는다. 등록 상태 GET은 200, 없는 requestId는 404다. 관리 API는 결과 JSON의 success를 반드시 확인한다. 일부 실패도 HTTP 200으로 반환한다. 프로필 삭제는 성공 200, PROFILE_NOT_FOUND 404, 그 외 업무 실패 409다. 요청 오류 400, MQTT 오류 502, 기기 응답 timeout 504를 구분한다. 제품 인증 오류 401/403은 Backend에서 추가 정의한다.

현재 취소 POST는 Gateway의 상태를 먼저 CANCELLED로 바꾼다. 기기 취소 ACK가 아니다. 제품 UI는 기기 voiceProfileStateChanged의 CANCELLED를 받아 종료를 확정하도록 Backend도 보완해야 한다.

3. MQTT 토픽과 메시지

IoTAgent 개발은 IoTAgent ↔ AWS IoT Core 전체 토픽 명세를 기준으로 한다. action별 입력·결과, 상태 이벤트의 success 처리, 서버 관심사 조회와 제품 미연결 범위를 정리했다.

이 절은 Backend와 IotAgent가 함께 맞추는 규격이다. 요청 발행만 구현하지 말고 결과 발행까지 한 쌍으로 구현한다.

토픽 발행 구독
skmg/airbot/{deviceId}/v1/command/voiceProfileEnrollment Backend 해당 기기 IotAgent
skmg/airbot/{deviceId}/v1/response/voiceProfileEnrollment/{requestId} IotAgent Backend

명령·결과는 QoS 1, retained=false를 제품 권장값으로 한다. Backend는 허가된 기기의 response 하위 토픽을 구독한다. 모바일은 MQTT 직접 연결 대신 Backend 이벤트를 받는다. QoS 전달 ACK는 Cloud 저장 ACK가 아니다.

명령 JSON → Bundle

{
  "action": "delete",
  "payload": {
    "contractVersion": "voice-profile-v2",
    "method": "deleteVoiceProfile",
    "requestId": "delete-example-001",
    "voiceId": "vp-example-001",
    "expiresAtMs": 0
  }
}

expiresAtMs는 미래의 실제 시각으로 교체한다. IotAgent가 action으로 method를 선택하고 필드 타입을 맞춘다.

action Bundle method 버전 추가 필드
start startVoiceProfileEnrollment voice-profile-v2 nickname, locale, personalizationEnabled
cancel cancelVoiceProfileEnrollment voice-profile-v2 같은 등록 requestId, reason 선택
list listVoiceProfiles voice-profile-v2 없음
delete deleteVoiceProfile voice-profile-v2 voiceId
interestsSet setVoiceProfileInterests voice-memory-v1 voiceId, interestCodes
interestsGet getVoiceProfileInterests voice-memory-v1 voiceId

JSON string→putString, boolean→putBoolean, limit→putInt, expiresAtMs→putLong, interestCodes→putStringArrayList. Bundle의 공통 method/contractVersion/requestId를 반드시 넣는다. 현재 CmdTopic은 issuedAtMs를 기기 수신 시각으로 설정하므로 Backend 원본 생성 시각과 동일하다고 가정하지 않는다.

결과 Bundle → JSON

결과는 action/payload로 다시 감싸지 않고 결과 객체를 response 토픽에 발행한다. requestId는 토픽과 JSON에서 일치해야 한다.

{
  "contractVersion": "voice-profile-v2",
  "method": "voiceProfileDeleteResult",
  "requestId": "delete-example-001",
  "voiceId": "vp-example-001",
  "success": false,
  "reasonCode": "PROFILE_NOT_FOUND"
}

프로필 목록의 profiles는 JSON 객체 배열이다. ArrayList<Bundle>을 JSON 문자열로 이중 인코딩하지 않는다. 빈 목록은 []이며 조회 성공과 조회 실패를 success로 구분한다. HTTP Gateway는 일부 envelope 필드를 제거하므로 MQTT 결과를 HTTP와 완전히 동일하다고 가정하지 않는다.

전체 결과 필드와 Android 타입은 관리 결과 Bundle에서 정의한다.

4. 모바일 실시간 상태

Backend 구현: 기기 결과 수신 → 대상 계정/기기 구독자 선택 → 이벤트 전달. 모바일 구현: method/requestId/voiceId 확인 → 해당 화면 갱신 → 재연결 시 조회 API로 복구.

현재 SSE content-type은 text/event-stream이며 이벤트 이름은 voice-profile이다. data는 JSON 객체다. keepalive 주석은 상태 변경이 아니다.

event: voice-profile
data: {"contractVersion":"voice-profile-v2","method":"voiceProfileStateChanged","requestId":"vp-example-001","stateSequence":8,"state":"SET_ACCEPTED","setIndex":2,"setCount":5,"progress":100,"terminal":false,"reasonCode":"","nickname":"Niky"}

수신 method 모바일 처리
voiceProfileStateChanged requestId와 상태 순번 확인 후 진행 화면 갱신
COMPLETED 상태 진행 화면 유지. 실제 voiceId와 interestSaveState로 등록·관심사 저장 결과를 구분; 관심사 화면으로 자동 이동하지 않음
SET_REJECTED 상태 녹음 오류 표시. 재녹음 동작은 기기 등록 App에서 수행
PROFILE_FAILED / DELIVERY_FAILED 실패 사유 표시. 등록 완료로 취급하지 않음
CANCELLED 상태 해당 등록 진행 종료

뒤의 상태 이름은 method가 아니라 voiceProfileStateChanged의 state 값이다. 재연결하면 상태/기기 목록 API로 다시 조회한다. 현재 SSE에 영구 이벤트 재생 보장은 없다. 등록 상태 순번은 requestId별로 처리한다.

5. 등록 임베딩의 Cloud 전달

등록 대표 임베딩은 AI-Agent가 commitVoiceProfile의 FloatArray로 DeviceAgent에 전달한다. 이는 모바일 일반 조회 응답과 다른 경로다. profiles 응답에 임베딩을 넣지 않는다.

아래는 제품에서 추가 구현할 인터페이스이며 현재 코드의 확정 API가 아니다.

연결 정의할 입력/출력 구현 담당
DeviceAgent → IotAgent requestId, voiceId, 등록 대표 임베딩, locale, nickname, 동의 설정 SoC
IotAgent → Backend 저장 모델/버전, 차원, 인코딩, payload 크기 제한, 검증 값과 등록 데이터 SoC + Backend + AI-Agent
Backend → IotAgent 실제 저장 성공/실패 ACK, requestId/voiceId, 실패 사유 Backend + SoC
IotAgent → DeviceAgent → AI-Agent 저장 ACK를 voiceProfileCommitResult에 연결 SoC + AI-Agent
삭제의 Cloud 동기화 로컬 삭제 결과, Cloud 사본 삭제 결과, 단절 시 재처리 Backend + SoC

이 경로의 MQTT action/토픽·wire format은 아직 없다. 기존 구현에 없는 syncAck 함수를 호출하면 안 된다. 모델 차원과 인코딩을 합의한 후 별도 버전으로 명세를 확정한다. 기기 원본 PCM/WAV와 인식용 query embedding은 이 경로에도 보내지 않는다.

6. 양쪽 구현 완료 확인

  1. Backend의 start JSON으로 등록 App이 열리고 상태가 모바일로 돌아온다.
  2. 프로필 목록은 Backend 요청 이력이 아니라 기기의 실제 profiles다.
  3. 삭제 성공 후 조회에서 해당 voiceId가 사라진다. timeout은 삭제 성공이 아니다.
  4. 관심사 PUT 후 같은 voiceId GET이 일치하고 다른 화자에게 섞이지 않는다.
  5. 등록 상태 callback을 실시간으로 표시하고 재연결 조회 시 중복되지 않는다.
  6. 취소 HTTP 응답과 기기 CANCELLED를 구분한다.
  7. 중복 요청·기기 오프라인·잘못된 Bundle 타입·권한 없는 계정에 대한 오류를 확인한다.
  8. 임베딩 Cloud 저장 ACK까지 구현하기 전에는 제품 종단 간 등록 완료로 표시하지 않는다.

코드 근거: PC Gateway server.py의 HTTP handler/public_status/sanitize_management_result/respond_sse, IotAgent CmdTopic.java와 VoiceProfileMqttTestBridge.java, DeviceAgent·AI-Agent Contract. 기존 시나리오별 흐름도는 함께 유지한다.

7. HTTP API별 전체 명세

현재 PC Gateway와 모바일 Sample 기준의 전체 지원 계약이다. 내부 시험용 /health와 /internal/device-status는 제품 모바일 API 범위에서 제외한다. nickname 기반 관심사 호환 경로는 현재 Service가 voiceId를 요구하므로 신규 연동에 사용하지 않는다.

공통: deviceId/voiceId는 URL 인코딩하여 전달한다. 관리 요청 deviceId는 [A-Za-z0-9_-]+ 최대128자. body는 JSON 객체. HTTP 오류 body는 error:String, detail:String(선택)이며 업무 오류는 success:Boolean/reasonCode:String으로 구분한다. 제품 인증은 미구현 계약이므로 인증 헤더 이름을 임의로 확정하지 않는다.

상태 객체: requestId:String, state:String, detail:String, terminal:Boolean, updatedAtMs:integer. deviceId는 현재 public_status가 제거한다. 관리 객체: requestId:String, method:String, success:Boolean, reasonCode:String과 해당 Binder 결과의 업무 필드. contractVersion은 현재 HTTP 관리 응답에서 제거된다.

HTTP-01 등록 시작

호출: POST /voice-profiles/enrollments

입력 필드: body의 공통 등록 필드 필수/personalizationEnabled 선택.

요청 body: 아래 JSON.

{
  "contractVersion": "voice-profile-v2",
  "method": "startVoiceProfileEnrollment",
  "requestId": "vp-request-001",
  "deviceId": "A1-EXAMPLE",
  "nickname": "Niky",
  "locale": "ko-KR",
  "personalizationEnabled": true,
  "issuedAtMs": 0,
  "expiresAtMs": 0
}

성공·결과: 202 + 등록 상태 객체. Device 실행 완료가 아님.

{
  "requestId": "vp-request-001",
  "state": "DEVICE_COMMAND_SENT",
  "detail": "Command sent",
  "terminal": false,
  "updatedAtMs": 1789430400000
}

오류: 400 invalid_request, 502 mqtt_publish_failed.

MQTT/Binder 연결: start → startVoiceProfileEnrollment. 해당 Bundle 명세의 필드와 타입을 적용한다.

중복·대기·취소: 같은 requestId라도 Gateway가 다시 publish할 수 있으므로 start 자동 재시도 금지. 상태 조회 우선.

연동 검증: 현재 모바일 입력→Backend 발행→Device Activity 실행→상태 수신을 확인.

HTTP-02 등록 상태 조회

호출: GET /voice-profiles/enrollments/{requestId}

입력 필드: path requestId 필수.

요청 body: 없음. 아래 {}는 body를 보내라는 뜻이 아니라 본문 없음의 예시다.

{}

성공·결과: 200 + Backend 상태 객체.

{
  "requestId": "vp-request-001",
  "state": "DEVICE_COMMAND_SENT",
  "detail": "Command sent",
  "terminal": false,
  "updatedAtMs": 1789430400000
}

오류: 404 request_not_found.

MQTT/Binder 연결: 이 API 자체가 기기에 새 명령을 발행하지 않는다.

중복·대기·취소: Backend 메모리 상태 조회. Gateway 재시작 후 상태 복구 보장 없음. AI-Agent GET_STATE와 별개.

연동 검증: 접수 상태와 실제 기기 상태를 구분.

HTTP-03 등록 취소

호출: POST /voice-profiles/enrollments/{requestId}/cancel

입력 필드: path requestId 필수, body {}.

요청 body: 아래 JSON.

{}

성공·결과: 현재200 + Gateway CANCELLED 객체. 기기 취소 ACK 아님.

{
  "requestId": "vp-request-001",
  "state": "CANCELLED",
  "detail": "Enrollment request cancelled",
  "terminal": true,
  "updatedAtMs": 1789430400000
}

오류: 404 request_not_found, 502 mqtt_publish_failed.

MQTT/Binder 연결: cancel → cancelVoiceProfileEnrollment. 해당 Bundle 명세의 필드와 타입을 적용한다.

중복·대기·취소: 제품은 기기 CANCELLED로 완료 확정해야 함. 반복 취소의 실제 기기 반응도 확인.

연동 검증: 네트워크 단절 시 취소200을 기기 정지 증거로 쓰지 않음.

HTTP-04 기기 프로필 목록

호출: GET /voice-profiles?deviceId={deviceId}

입력 필드: query deviceId 필수.

요청 body: 없음. 아래 {}는 body를 보내라는 뜻이 아니라 본문 없음의 예시다.

{}

성공·결과: 200 + voiceProfileListResult에서 contractVersion을 제외한 관리 객체.

{
  "requestId": "vp-request-001",
  "method": "voiceProfileListResult",
  "success": true,
  "reasonCode": "",
  "profiles": [
    {
      "voiceId": "vp-example-001",
      "nickname": "Niky"
    }
  ]
}

오류: 400 invalid_request, 502 mqtt_request_failed, 504 device_timeout; 업무 실패 success=false.

MQTT/Binder 연결: list → listVoiceProfiles. 해당 Bundle 명세의 필드와 타입을 적용한다.

중복·대기·취소: 읽기 재시도 가능. 기기 응답8초 기본 대기.

연동 검증: 빈 profiles와 PROFILE_LIST_FAILED 구별.

HTTP-05 프로필 삭제

호출: DELETE /voice-profiles/{voiceId}?deviceId={deviceId}

입력 필드: path voiceId와 query deviceId 필수.

요청 body: 없음. 아래 {}는 body를 보내라는 뜻이 아니라 본문 없음의 예시다.

{}

성공·결과: 200 + voiceProfileDeleteResult 관리 객체.

{
  "requestId": "vp-request-001",
  "method": "voiceProfileDeleteResult",
  "success": true,
  "reasonCode": "",
  "voiceId": "vp-example-001"
}

오류: 404 PROFILE_NOT_FOUND, 409 기타 업무 실패, 400/502/504 전송 오류.

MQTT/Binder 연결: delete → deleteVoiceProfile. 해당 Bundle 명세의 필드와 타입을 적용한다.

중복·대기·취소: timeout 후 목록 조회로 확인. Cloud 삭제는 별도 구현.

연동 검증: ENROLLMENT_ACTIVE와 삭제 후 기기 목록 검증.

HTTP-08 관심사 조회

현재 처리: Backend 서버 DB 조회. 기기 요청 없음. 서버에 아직 확인된 voiceId가 없으면 HTTP 404 PROFILE_NOT_FOUND. 성공 시 revision, personalizationEnabled가 추가 반환된다.

호출: GET /voice-profiles/{voiceId}/interests?deviceId={deviceId}

입력 필드: voiceId/deviceId 필수.

요청 body: 없음. 아래 {}는 body를 보내라는 뜻이 아니라 본문 없음의 예시다.

{}

성공·결과: 200 + voiceProfileInterestsResult 관리 객체.

{
  "requestId": "vp-request-001",
  "method": "voiceProfileInterestsResult",
  "success": true,
  "reasonCode": "",
  "voiceId": "vp-example-001",
  "interestCodes": [
    "WEATHER",
    "HEALTH"
  ]
}

오류: 400/502/504; FEATURE_DISABLED/INTERESTS_FAILED는 success=false.

MQTT/Binder 연결: interestsGet → getVoiceProfileInterests. 해당 Bundle 명세의 필드와 타입을 적용한다.

중복·대기·취소: 읽기 재시도 가능, nickname 전용 경로를 새 구현에 사용하지 않음.

연동 검증: voiceId별 독립 조회 확인.

HTTP-09 관심사 저장

현재 처리: Backend 서버 DB 전체 교체. 기기 요청 없음. 허용 코드 10종·중복 없음·빈 배열 가능. 미등록 ID는 404, 개인화 비허용/잘못된 코드는 400. 최초 등록 후 저장과 이후 수정 모두 지원한다. 서버 저장 실패는 503 BACKEND_UNAVAILABLE.

호출: PUT /voice-profiles/{voiceId}/interests?deviceId={deviceId}

입력 필드: voiceId/deviceId 필수, body interestCodes:string[] 필수.

요청 body: 아래 JSON.

{
  "interestCodes": [
    "WEATHER",
    "HEALTH"
  ]
}

성공·결과: 200 + voiceProfileInterestsResult 관리 객체.

{
  "requestId": "vp-request-001",
  "method": "voiceProfileInterestsResult",
  "success": true,
  "reasonCode": "",
  "voiceId": "vp-example-001",
  "interestCodes": [
    "WEATHER",
    "HEALTH"
  ]
}

오류: 400/502/504; FEATURE_DISABLED/INTERESTS_FAILED는 success=false.

MQTT/Binder 연결: interestsSet → setVoiceProfileInterests. 해당 Bundle 명세의 필드와 타입을 적용한다.

중복·대기·취소: 목록 전체 교체. []은 해제. 다른 목록 요청의 순서 역전은 버전 방어 없음.

연동 검증: 저장 후 GET과 비교, 빈 목록, 중복 코드 거절 확인.

HTTP-11 실시간 이벤트 구독

호출: GET /voice-events?deviceId={deviceId}

입력 필드: deviceId 필수; requestId, voiceId 필터 선택.

요청 body: 없음. 아래 {}는 body를 보내라는 뜻이 아니라 본문 없음의 예시다.

{}

성공·결과: 200 text/event-stream, event: voice-profile.

{
  "contractVersion": "voice-profile-v2",
  "method": "voiceProfileStateChanged",
  "requestId": "vp-request-001",
  "stateSequence": 8,
  "state": "SET_ACCEPTED",
  "setIndex": 2,
  "setCount": 5,
  "progress": 100,
  "reasonCode": "",
  "terminal": false,
  "nickname": "Niky"
}

오류: deviceId 오류400; 연결 종료는 재연결/조회로 복구.

MQTT/Binder 연결: 이 API 자체가 기기에 새 명령을 발행하지 않는다.

중복·대기·취소: 15초 이벤트가 없으면 keepalive. 영구 replay/Last-Event-ID 보장 없음. 큐100개 초과 시 이전 이벤트 유실 가능.

연동 검증: 등록 상태 반영과 상태순번, 재연결 후 조회 복구 확인.

8. action별 MQTT 전체 예제

각 요청의 expiresAtMs=0과 issuedAtMs=0은 실제 송신 전에 현재·미래 시각으로 교체한다. response 토픽을 먼저 구독하고 발행한다. 관리 요청의 기본 응답 대기는8초, 구독 확인3초·publish 대기3초는 별도다. timeout 뒤 늦은 결과를 성공/실패로 임의 덮어쓰지 않는다.

MQTT start

발행자 Backend / 구독자 IotAgent

토픽: skmg/airbot/A1-EXAMPLE/v1/command/voiceProfileEnrollment

{
  "action": "start",
  "payload": {
    "contractVersion": "voice-profile-v2",
    "requestId": "vp-request-001",
    "method": "startVoiceProfileEnrollment",
    "issuedAtMs": 0,
    "expiresAtMs": 0,
    "nickname": "Niky",
    "locale": "ko-KR",
    "personalizationEnabled": true
  }
}

결과 발행자 IotAgent / 구독자 Backend

토픽: skmg/airbot/A1-EXAMPLE/v1/response/voiceProfileEnrollment/vp-request-001

{
  "contractVersion": "voice-profile-v2",
  "method": "voiceProfileStateChanged",
  "requestId": "vp-request-001",
  "stateSequence": 8,
  "state": "RECORDING_SET",
  "setIndex": 1,
  "setCount": 5,
  "progress": 0,
  "reasonCode": "",
  "terminal": false,
  "nickname": "Niky"
}

결과 필드 전체는 voiceProfileStateChanged 참조. 제품 IotAgent의 실제 AWS 결과 publish는 추가 구현이 필요하다.

MQTT cancel

발행자 Backend / 구독자 IotAgent

토픽: skmg/airbot/A1-EXAMPLE/v1/command/voiceProfileEnrollment

{
  "action": "cancel",
  "payload": {
    "contractVersion": "voice-profile-v2",
    "requestId": "vp-request-001",
    "method": "cancelVoiceProfileEnrollment",
    "issuedAtMs": 0,
    "expiresAtMs": 0,
    "reason": "MOBILE_CANCELLED"
  }
}

결과 발행자 IotAgent / 구독자 Backend

토픽: skmg/airbot/A1-EXAMPLE/v1/response/voiceProfileEnrollment/vp-request-001

{
  "contractVersion": "voice-profile-v2",
  "method": "voiceProfileStateChanged",
  "requestId": "vp-request-001",
  "stateSequence": 8,
  "state": "CANCELLED",
  "setIndex": 1,
  "setCount": 5,
  "progress": 0,
  "reasonCode": "USER_CANCELLED",
  "terminal": true,
  "nickname": "Niky"
}

결과 필드 전체는 voiceProfileStateChanged 참조. 제품 IotAgent의 실제 AWS 결과 publish는 추가 구현이 필요하다.

MQTT list

발행자 Backend / 구독자 IotAgent

토픽: skmg/airbot/A1-EXAMPLE/v1/command/voiceProfileEnrollment

{
  "action": "list",
  "payload": {
    "contractVersion": "voice-profile-v2",
    "requestId": "vp-request-001",
    "method": "listVoiceProfiles",
    "issuedAtMs": 0,
    "expiresAtMs": 0
  }
}

결과 발행자 IotAgent / 구독자 Backend

토픽: skmg/airbot/A1-EXAMPLE/v1/response/voiceProfileEnrollment/vp-request-001

{
  "contractVersion": "voice-profile-v2",
  "method": "voiceProfileListResult",
  "requestId": "vp-request-001",
  "success": true,
  "reasonCode": "",
  "profiles": [
    {
      "voiceId": "vp-example-001",
      "nickname": "Niky"
    }
  ]
}

결과 필드 전체는 voiceProfileListResult 참조. 제품 IotAgent의 실제 AWS 결과 publish는 추가 구현이 필요하다.

MQTT delete

발행자 Backend / 구독자 IotAgent

토픽: skmg/airbot/A1-EXAMPLE/v1/command/voiceProfileEnrollment

{
  "action": "delete",
  "payload": {
    "contractVersion": "voice-profile-v2",
    "requestId": "vp-request-001",
    "method": "deleteVoiceProfile",
    "issuedAtMs": 0,
    "expiresAtMs": 0,
    "voiceId": "vp-example-001"
  }
}

결과 발행자 IotAgent / 구독자 Backend

토픽: skmg/airbot/A1-EXAMPLE/v1/response/voiceProfileEnrollment/vp-request-001

{
  "contractVersion": "voice-profile-v2",
  "method": "voiceProfileDeleteResult",
  "requestId": "vp-request-001",
  "success": true,
  "reasonCode": "",
  "voiceId": "vp-example-001"
}

결과 필드 전체는 voiceProfileDeleteResult 참조. 제품 IotAgent의 실제 AWS 결과 publish는 추가 구현이 필요하다.

MQTT interestsGet

발행자 Backend / 구독자 IotAgent

토픽: skmg/airbot/A1-EXAMPLE/v1/command/voiceProfileEnrollment

{
  "action": "interestsGet",
  "payload": {
    "contractVersion": "voice-memory-v1",
    "requestId": "vp-request-001",
    "method": "getVoiceProfileInterests",
    "issuedAtMs": 0,
    "expiresAtMs": 0,
    "voiceId": "vp-example-001"
  }
}

결과 발행자 IotAgent / 구독자 Backend

토픽: skmg/airbot/A1-EXAMPLE/v1/response/voiceProfileEnrollment/vp-request-001

{
  "contractVersion": "voice-memory-v1",
  "method": "voiceProfileInterestsResult",
  "requestId": "vp-request-001",
  "success": true,
  "reasonCode": "",
  "voiceId": "vp-example-001",
  "interestCodes": [
    "WEATHER",
    "HEALTH"
  ]
}

결과 필드 전체는 voiceProfileInterestsResult 참조. 제품 IotAgent의 실제 AWS 결과 publish는 추가 구현이 필요하다.

MQTT interestsSet

발행자 Backend / 구독자 IotAgent

토픽: skmg/airbot/A1-EXAMPLE/v1/command/voiceProfileEnrollment

{
  "action": "interestsSet",
  "payload": {
    "contractVersion": "voice-memory-v1",
    "requestId": "vp-request-001",
    "method": "setVoiceProfileInterests",
    "issuedAtMs": 0,
    "expiresAtMs": 0,
    "voiceId": "vp-example-001",
    "interestCodes": [
      "WEATHER",
      "HEALTH"
    ]
  }
}

결과 발행자 IotAgent / 구독자 Backend

토픽: skmg/airbot/A1-EXAMPLE/v1/response/voiceProfileEnrollment/vp-request-001

{
  "contractVersion": "voice-memory-v1",
  "method": "voiceProfileInterestsResult",
  "requestId": "vp-request-001",
  "success": true,
  "reasonCode": "",
  "voiceId": "vp-example-001",
  "interestCodes": [
    "WEATHER",
    "HEALTH"
  ]
}

결과 필드 전체는 voiceProfileInterestsResult 참조. 제품 IotAgent의 실제 AWS 결과 publish는 추가 구현이 필요하다.

9. 미완료 계약과 연동 중단 조건

항목 현재 코드의 한계 구현 전 확정할 내용
Cloud 등록 업로드 commit은 DeviceAgent 저장까지만 연결 업로드 토픽/API, 인코딩, 모델 차원·버전, 크기 제한
Cloud 저장 ACK 제품 수신/전달 경로 없음 requestId+voiceId 상관관계, 재시도, 성공/실패 코드, 지연 ACK 처리
취소 완료 Gateway가 기기 확인 전에 CANCELLED 기기 확인 대기 상태와 실패/timeout 표시, 이미 저장된 사본 처리
요청 실패 일부 기기 검증은 로그 후 종료 Backend까지 업무 실패를 반환할 envelope/오류 코드
인증 Sample에 제품 계정 인증 없음 모바일 로그인, 기기 소유권, API scope, 인증서·IoT topic 정책
재연결 SSE/등록 메모리 상태는 휘발성 저장/보관 기간과 조회 복구, 중복 명령 저장 정책

위 항목은 '완료된 함수'로 호출할 수 없다. 기존 입력·응답 명세를 구현하되 이 영역은 제품 확장 계약을 정한 후 연동한다.

Keyboard shortcuts

⌘K / Ctrl+KOpen command palette
/Focus search
g hGo to home
g pGo to projects
g sGo to sessions
j / kNext / prev row (tables)
?Show this help
EscClose dialogs

Structured queries

Mix key:value filters with free text in the palette:

type:sessionOnly session pages
project:llm-wikiFilter by project name (substring)
model:claudeFilter by model name (substring)
date:>2026-03-01Sessions after a date
date:<2026-04-01Sessions before a date
tags:rustPages mentioning a tag/topic
sort:dateSort results by date (newest first)

Example: type:session project:llm-wiki date:>2026-04 sort:date