← Docs hub

SKIX Manager Main 장치 제어 리팩터링 분석

refactoring/skix-manager-main은 main의 851cacb4에서 갈라져 나온 뒤, df342d4a부터 91a8ae92까지 9개 커밋으로 LLM Agent의 장치 제어 경계를 기존 DeviceControl scalar IPC에서 SKIX SDK의 도메인별 Manager API로 옮겼다.

핵심 성과는 조회·상태·보안·스케줄 호출을 typed DeviceCommunicator port로 노출하고, 앱 코드가 raw Bundle 결과를 직접 해석하던 책임을 adapter 내부로 모은 것이다. 다만 쓰기 명령 큐에는 아직 DeviceCommand(method: String, inData: Map)가 남아 있으므로 완전한 end-to-end 타입화가 끝난 상태는 아니다.

SKIX Manager Main 리팩터링 Before/After

원본 크기로 구조도 열기

분석 기준

항목 기준
기준 브랜치 origin/main
기준 커밋 851cacb4 (version change v1.20.07 / db 133)
분석 HEAD 91a8ae92
커밋 범위 origin/main..refactoring/skix-manager-main의 9개 커밋
파일 변화 15개 파일, 1,678줄 추가, 1,426줄 삭제
검증 수준 Git diff·코드 정적 분석·SDK 경계 검사
제외 Android 전체 빌드, APK 설치, DeviceAgent/SKIX Service 실연결, 실기기 음성 시나리오

변경량에는 자동 재기준화된 dictionary_positive_test.json 1,171줄과 바이너리 AAR/SO가 포함된다. 따라서 줄 수만으로 리팩터링 복잡도를 평가하면 안 된다.

무엇이 바뀌었나

1. DeviceControlProxy 직접 결합을 SkixManager로 교체

기존 DeviceCommunicator는 DeviceControlProxy에 문자열 method와 Bundle을 전달하고, 반환 Bundle을 Map<String, Any>로 변환했다. 호출자는 키 이름, 문자열 boolean, JSON payload의 형식을 알아야 했다.

변경 후 구현체는 SkixManager.getInstance(context)를 소유하고 다음 SKIX SDK Manager 도메인 API를 호출한다.

SDK 도메인 Agent가 사용하는 대표 계약
llm() 상태, STT/TTS text, 날씨·공기질 요청, AI log, upload URL, LlmEvent
setting() 언어·음성·볼륨·밝기·privacy·lock·night mode·대기화면 theme
execution() pause/resume/stop과 cleaning·interaction·movement 상태 snapshot
airCleaner() 운전 상태, 청정 위치, 운전 request
security() SecurityBasicAvailability, isB2bMode()
schedule() on-device schedule 조회·등록·중지·모드 판정
error() active error code와 DeviceErrorUpdate listener
airQuality() IndoorAirQuality snapshot
manage() alert, battery, filter, firmware, AWS 연결, 환경·locale
map() / amr() map 존재와 station 상태
interaction() launcher screen request

연결 lifecycle도 initializeDeviceControlProxy()/DeviceControlProxy.disconnect()에서 initializeSkixManager()/SkixManager.connectDevice()/disconnectDeviceController()로 바뀌었다. LLM, error, setting mode callback은 각각 SDK listener로 분리됐다.

2. 소비자가 구현체 대신 typed DeviceCommunicator port에 의존

service/api/DeviceCommunicator.kt는 기존의 범용 함수 sendCommand(command, params): Map<String, Any>와 getSingleData()를 제거하고 의미가 드러나는 메서드를 제공한다.

기존 호출 변경 후
sendCommand(GET_ERROR_STATUS) 후 errorCode cast getActiveErrorCodes(): List<String>
sendCommand(GET_DEVICE_STATUS) 후 JSON 역직렬화 getDeviceActionStatus(): DeviceActionStatus?
getSingleData(GET_LOCK_MODE_ENABLE, enable) isLockModeEnabled(): Boolean
sendCommand(GET_SECURITY_BASIC_AVAILABLE) getSecurityBasicAvailability()
sendCommand(GET_SCHEDULE) getOnDeviceSchedules(): List<DeviceSchedule>
sendCommand(ADD_SCHEDULE, Map) registerOnDeviceSchedule(AppScheduleRequest)
sendCommand(SET_DEVICE_STATUS, Map) controlDeviceExecution(Action, ExecutionScope, Boolean)

ApiCallValidator, ForegroundService, FunctionCallHandler도 concrete service.DeviceCommunicator가 아닌 service.api.DeviceCommunicator를 받도록 정리됐다. 이로써 validator와 handler의 테스트 대역을 구현체 상속 없이 만들 수 있고, raw transport 형식 변경의 영향이 adapter 쪽으로 좁아진다.

3. callback도 도메인 event로 전환

기존 단일 callback은 method string과 Bundle을 해석해 날씨, 공기질, error, 언어, 음성, TTS 요청을 분기했다. 변경 후에는 다음 listener가 역할을 나눈다.

이 변화는 callback payload key 누락이나 잘못된 cast를 줄이지만, SDK event type과 Agent store의 대응이 정확한지는 통합시험으로 확인해야 한다.

보안·스케줄 계약 변화

B2B 보안모드

eca4c33f는 GET_B2B_MODE 결과에서 문자열 isB2B를 꺼내던 로직을 security().isB2bMode()로 교체했다. 기본 가능 여부도 SecurityBasicAvailability의 isB2bRestricted(), isSubscriptionRequired(), isAvailable()로 판정한다.

구버전 보드에서 isB2bMode() API가 없을 수 있다는 기존 호환 전략은 유지한다. 직접 호출이 실패하면 false로 두고 typed availability 판정으로 이어지므로, API 미지원 하나만으로 보안모드 전체가 unknown error로 종료되지 않는다.

On-device 스케줄

기존 schedule map은 AppScheduleRequest와 DeviceSchedule로 전환됐다. SDK의 ScheduleRegistrationResult(delivered, accepted, code, message)를 기존 Sk48Schedule.RegisterResult로 바꾸는 overload도 추가됐다. accepted=true 또는 빈 code는 성공, REDUNDANCY_ERROR/중복 메시지는 중복, 나머지는 일반 오류로 정규화한다.

함께 포함된 기능 보정

커밋 분류 내용
df342d4a 패키징 보정 release에서 제외되던 native .so를 포함하고 libGenie.so 추가
4c6e9073 핵심 리팩터링 legacy DeviceControl AAR을 SKIX Framework SDK AAR로 교체하고 도메인 Manager 적용
eca4c33f 계약 리팩터링 B2B 판정을 typed security API로 전환
f18fcafe 경계 정리 sendCommand()/getSingleData() scalar adapter 제거
2dd595d4 회귀 방지 raw DeviceAgent IPC와 legacy AAR 재유입을 막는 검사 스크립트 추가
2adcc914 오류 분류 수정 S07_4 주행 불가 기타 오류를 관리용 OtherErrors에 포함
7d1ee371 matcher 수정 지나치게 넓은 영문 <sk_19> 규칙 Who is/Who + current/currently 제거
810ecee0 의존성 역전 validator·service·handler가 typed DeviceCommunicator port에 의존
91a8ae92 test fixture 현재 matcher 결과에 맞게 positive fixture 재기준화 및 도구 명시

남아 있는 구조적 경계

쓰기 큐는 아직 문자열·Map 기반

FunctionCallHandler의 다수 setter는 계속 DeviceCommandMethod와 MutableMap<String, Any>로 DeviceCommand를 만든다. adapter의 executeTypedCommand()가 이 값을 다시 when으로 분기해 SDK request를 만든다.

즉 현재 구조는 다음 두 단계 중 첫 단계가 완료된 상태다.

  1. 완료: consumer query/state/security/schedule API와 raw IPC 분리
  2. 미완료: setter command까지 sealed command 또는 SDK request로 완전 타입화

이 잔존 adapter는 기존 명령 큐의 순서·취소 동작을 유지하는 완충층이지만, 잘못된 key나 type은 컴파일 때가 아니라 executeTypedCommand()에서 false로 거절된다. 다음 단계에서는 queue payload 자체를 도메인 command로 바꾸는 것이 가장 큰 구조 개선점이다.

port가 SDK model을 직접 노출

port가 AirCleanerStateSnapshot, AvailableCleaningPosition, SecurityBasicAvailability, DeviceSchedule 등 SDK model을 반환한다. raw IPC 결합은 제거됐지만 application 계층은 여전히 SDK DTO 변화의 영향을 받는다. 장기적으로 SDK 교체 가능성까지 목표로 한다면 Agent-owned model로 한 번 더 매핑해야 한다.

코드 리뷰에서 발견한 후속 확인 항목

우선순위 정적 근거 필요한 확인
높음 requestOutdoorAirQualityData()가 requestWeatherForecast()를, requestOutdoorWeatherData()가 requestOutdoorAirQuality()를 호출 메서드명과 SDK 호출이 교차된 의도인지 확인하고 날씨·공기질 각각 callback E2E 검증
높음 getNightModeScheduleTime()가 SDK list의 [0], [1]을 길이 확인 없이 접근 미설정·부분 설정 상태의 반환 계약과 예외 처리 검증
중간 미지원 또는 key/type 불일치 command는 log만 남기고 drop 모든 DeviceCommandMethod와 executeTypedCommand() coverage 대조
중간 version 없는 skix-framework-sdk-release.aar 바이너리를 저장소에 직접 포함 SDK 산출 버전·source commit·호환 DeviceAgent 버전을 릴리즈 메타데이터에 기록
중간 여러 getter가 연결 준비 상태를 일관되게 검사하지 않음 연결 전·재연결 중 query 호출과 SDK exception contract 검증

첫 번째 항목은 코드 이름과 호출 대상이 정반대로 배치된 정적 위험 후보다. 실제 SDK 명세와 실기기 callback을 확인하지 않았으므로 이 문서에서는 확정 장애로 판정하지 않는다.

검증 결과와 경계

검증 결과 의미
git log origin/main..HEAD 9개 커밋 확인 사용자 지정 분석 범위와 일치
git diff --stat origin/main...HEAD 15개 파일, +1,678/-1,426 변경 규모 확인
bash tools/verify-skix-sdk-boundary.sh 통과 LLM source에 raw DeviceControlProxy/IDeviceControl, 직접 sendCommand, getSingleData, legacy AAR 의존 없음
Android unit/build 사용자 요청에 따라 미수행 컴파일·단위 회귀 결과를 이 문서에서 주장하지 않음
실기기 SKIX 연동 실기기 미검증 connect, listener, 장치 실행, 날씨·공기질, 보안·스케줄 E2E는 별도 Gate

SDK 경계 검사는 금지 패턴의 부재를 확인하는 정적 guard다. typed method의 의미가 정확하거나 실제 DeviceAgent와 호환된다는 증거는 아니다.

권장 검증 순서

  1. SDK/DeviceAgent 호환 버전과 AAR provenance를 확정한다.
  2. connect/disconnect/reconnect와 listener 중복 등록 여부를 확인한다.
  3. 날씨와 실외 공기질 요청을 각각 실행해 request/event/store 대응을 확인한다.
  4. privacy·lock·B2B·subscription 조합별 보안모드 응답을 확인한다.
  5. map 유무와 중복 schedule별 ScheduleRegistrationResult 매핑을 확인한다.
  6. setter queue의 모든 command를 SDK domain request와 일대일 대조한다.
  7. 마지막으로 음성 발화 → matcher → function call → 장치 상태 → TTS까지 회귀한다.

결론

이번 9개 커밋은 단순 AAR 교체가 아니라 Agent가 DeviceAgent transport 세부사항을 직접 해석하던 구조에서 SKIX SDK의 도메인 계약을 사용하는 구조로 이동한 1단계 리팩터링이다. 조회와 핵심 상태 판단의 타입 안정성, listener 책임 분리, consumer의 port 의존은 분명한 개선이다.

다만 setter queue의 문자열·Map envelope, SDK model 직접 노출, 날씨/공기질 요청 교차 가능성은 남아 있다. 따라서 현재 판정은 정적 SDK 경계 전환 완료, 제품 동작 검증 전이다.

Connections

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