← Docs hub

Cleaning은 무엇을 소유하고, 무엇을 확인하는가

청소 서비스는 청소 요청과 팬 관측을 소유한다. 이동 청소에 사용하는 구동부의 정지 상태는 AMR의 책임이고, 전체 작업 완료는 Task의 책임이다. 팬이 멈췄다는 사실 하나로 이 세 가지를 함께 완료 처리하지 않는다.

이번 단위는 CleaningManagerService 내부의 팬 관측·정지 근거 계약이다. 제품 연결 미완: MCU 수신 어댑터, 실제 OFF 송신, 이동 청소 종료 조정은 아직 이 계약에 연결하지 않는다. 기존 DeviceAgent의 동작을 변경했다는 뜻이 아니다.

전체 아키텍처 Atlas → 상태·자원 소유권 지도 → 이 Cleaning 상세 순서로 읽으면 전체에서의 위치를 확인할 수 있다.

Cleaning 팬 관측과 정지 근거의 폴더·호출 구조

SVG 원본 · Mermaid 원본

1. 폴더 구조와 책임

청소 소유 구동 자원을 이동 작업에 넘기는 별도 절차는 Cleaning → AMR 인계 상세에서 관리한다. 그 경로도 팬 정지 근거를 청소 전체 종료나 구동부 idle로 대체하지 않는다.

정본 루트는 skix-framework/base/services/core/java/com/sk/airbot/frameworkservice/server/다.

control/
  lifecycle/ServiceLifecycleSupervisor.java  서비스 수명·재시작 세대
  resource/ResourceManagerService.java       공유 자원 점유·재확인
execution/
  task/TaskManagerService.java               작업 실행·취소·완료
domain/
  cleaning/
    CleaningManagerService.java             Cleaning 서비스 진입점·상태 소유
    CleaningState.java                      기존 전체 청소 상태 정의 — 유지
    state/
      CleaningFanStateAuthority.java        팬 관측 변경과 정지 근거 판단
      CleaningFanObservation.java           실제 수신을 변환한 typed 입력
      CleaningFanSnapshot.java              불변 조회값: 요청과 관측 분리
      CleaningFanObserverToken.java         현재 관측 작성자 식별
      CleaningFanStopToken.java             특정 정지 요청 경계 식별
      CleaningFanStopEvidence.java          조회 시점의 팬 정지 근거
      CleaningFanStateException.java        거부 이유
    policy/CleaningOperationPolicy.java      기존 청소 조건 판정
    execution/CleaningTransitionPort.java         이동 전 청소 중단 협력 계약
  amr/
    state/AmrStateAuthority.java             주행·도킹 요청과 실제 관측
    execution/MovementTaskSession.java            Task·자원 권한을 검사하는 이동 실행

domain/cleaning/state/를 공통 State 서비스에 합치지 않는다. 공통 조회 사본이 아닌 Cleaning 자체의 관측 변경 규칙이기 때문이다. SDK Manager와 Binder에는 이번 내부 API를 추가하지 않는다. 앱의 정지 요청과 내부 팬 관측용 정지 경계는 서로 다른 API다.

2. 기존 코드에서 왜 이 구분이 필요한가

원본 코드 확인한 사실 새 계약에서의 처리
cleaning/BasicCleaningManager.java:254 상위 onStop 뒤 팬 OFF 요청 callback만으로 물리 정지 인정 금지
cleaning/MovingCleaningManager.java:785 상위 onStop 뒤 이동 정지 요청 팬과 구동부 정지 증거 분리
cleaning/CleaningTransaction.java:185 마지막 요청 문자열로 processing 판정 기존 호환 상태이며 실제 관측과 구분
device/item/mcu/Protocol.java:314,785 수신 배열 초기값 0, RPM getter는 두 바이트 합성 수신 전 0은 유효 관측이 아님
device/item/mcu/Protocol_v2.java:456,958 같은 초기 배열·RPM getter 구조 패킷 수신 여부·시각을 별도로 확보
device/item/mcu/MCUFanBLDC.java:410 stopCheck의 제한 횟수 선언을 반복문이 사용하지 않음 이 코드만으로 유한 정지 timeout 보장 주장 금지

위 제품 경로의 공통 접두사는 apps/DeviceAgent/app/src/main/java/com/sk/airbot/deviceagent/다. util/Utils.java:92의 byte 변환은 0..255이므로 두 Protocol 구현의 RPM 반환은 0..65535이다. 음수 RPM을 곧바로 정지로 처리하는 기존 분기는 이 두 getter의 정상 계산으로는 도달하지 않는다. 이는 정적 코드 분석이며 기기 장애를 재현한 결과가 아니다. 예약된 원시 값의 의미는 하위 프로토콜 확인이 더 필요하다.

3. API를 읽는 방법

원천 수신 흐름은 DeviceAgent.on_update_rs485_event → main/MainMcu.handleoRs485Event → device/item/mcu/MCUDataManager.setReceiveData → 선택된 Protocol의 수신 배열이다. MCUDataManager.java:108은 uptime 수신 시각을 고정 길이 복사 전에 갱신한다. isCommValid는 1초 이내의 시각만 확인하므로, 이 boolean과 별도로 읽은 RPM을 하나의 검증된 샘플이라고 볼 수 없다. Java 경로의 길이·checksum 검증은 확인되지 않았으며 하위에서 걸러 준다는 주석만으로 전체 무결성 검증 완료를 주장하지 않는다.

후속 수신 어댑터는 원천에서 검증된 패킷·시각·순번·연결 세대를 묶어야 한다. 이번에 그 어댑터를 활성화하지 않는 이유는 이 필수 정보가 기존 RPM getter에 없기 때문이다.

서비스 메서드 역할 하지 않는 일
openFanObserver() 현재 서비스 세대의 관측 작성 토큰 발급 제품 수신 연결을 자동 생성하지 않음
fanStateSnapshot(maxAgeMs) 최신성 기준으로 팬 상태 조회 캐시를 영구 실행 권한으로 만들지 않음
requestFanStop(expectedVersion) 새 정지 관측 경계와 토큰 기록 실제 팬 OFF 명령을 보내지 않음
observeFan(observer, expectedVersion, observation) 출처·버전·순번·시각을 검사해 관측 승인 현재 읽은 캐시를 자동으로 새 관측 처리하지 않음
fanStopEvidence(stop, maxAgeMs) 해당 요청 이후의 최신 STOPPED 근거 조회 Task 완료·Resource FREE·AMR 정지 처리 없음

내부 서비스 호출자가 토큰을 얻는 구조이지 외부 호출자 인증을 대체하는 보안 경계는 아니다. 실제 Binder 노출 시 권한 검사는 별도의 통신 경계에 필요하다.

4. 요청과 실제 관측은 독립적인 값

요청 축은 StopPhase.NONE / REQUESTED, 관측 축은 FanState.UNKNOWN / STOPPED / RUNNING / FAULT다. 청소 전체의 IDLE/PAUSED 상태를 이 값에 억지로 합치지 않는다.

입력·상황 팬 조회 상태 현재 정지 요청의 근거
시작 직후, 유효 수신 없음 UNKNOWN 없음
정지 요청 전에 수신한 RPM 0 STOPPED 아직 요청 이후 증거가 아님
새 정지 요청을 기록함 UNKNOWN, REQUESTED 없음 — 이전 0을 재사용하지 않음
요청 이후 새 순번의 정상 RPM 0 STOPPED TTL 내에서만 있음
요청 이후 RPM 양수 RUNNING 없음
센서·연결 불명 / 명시적 오류 UNKNOWN / FAULT 없음
TTL 만료·시계 역행 UNKNOWN 없음
재시작·명시적 서비스 종료 UNKNOWN, 토큰 폐기 이전 요청 토큰 거부

rpm(0, sourceSequence, observedAtMs)는 이미 수신 유효성이 검증된 입력이라는 내부 계약이다. 이 팩토리가 MCU 원시 수신을 검증하는 것은 아니다. 호출자가 수신 전 getter의 0을 포장하면 잘못된 입력이므로, 제품 어댑터를 연결하기 전에 반드시 원천 메타데이터가 필요하다.

5. 동시 접근과 오래된 수신을 막는 규칙

단일 Cleaning 잠금 안에서 lifecycle 스냅샷을 읽고 검증·갱신한다. 잠금 안에 다른 도메인의 실행 명령이나 하드웨어 대기를 넣지 않는다. 정지 요청은 매번 새 토큰으로 이전 요청을 대체한다. 저장해 둔 CleaningFanStopEvidence는 이후에도 유효한 권한증이 아니라 그 조회 시점에 확인한 과거 사실이다.

6. 이동 청소·TaskManager에 연결할 때

팬 정지 근거와 AMR 정지 근거를 상위 실행 조정자가 함께 확인해야 이동 청소를 정리할 수 있다. CleaningTransitionPort.StopConfirmation을 팬 근거 하나로 생성해서는 안 된다. 이후 Resource 재확인과 Task 완료는 각 소유자가 별도로 수행한다.

MR6 task_manager의 CleaningAmpTaskExecutor는 cleaning.started, stopped, stepComplete, reportDone과 dwell, 기본 timeout을 가진다. 이미 정지한 것으로 간주하는 호환 단축 경로에서 합성한 이벤트는 새 팬 관측과 다른 근거 등급으로 보존해야 한다. 이 문서는 그 executor의 전체 이식 완료를 뜻하지 않는다.

7. 검증과 남은 연결

구현 커밋은 bacc1e5이며 사양 검토와 후속 품질 검토를 각각 통과했다. 커밋된 서버 소스를 별도로 추출하여 미완 LLM/TTS 변경과 분리한 검증에서 서버 JVM 409개, SDK JVM 46개가 실패·오류·건너뜀 없이 통과했다. Cleaning 팬 전용 테스트는 12개다. 릴리스 Java 컴파일과 폴더·레이어·API·Bundle 경계 검사도 통과했다. SDK 테스트 작업은 변경이 없어 Gradle의 기존 성공 결과를 재사용했다.

품질 검토의 비차단 보강 항목은 정지 요청끼리 및 정지 요청·관측 사이의 경합 테스트 추가다. 현재 두 관측의 동일 버전 경합, 정지 토큰 교체, 오래된 정지 버전 거부는 검증했지만 이 조합의 직접 경합 테스트는 아직 없다.

제품 콜백, APK 설치, 실제 팬 정지, 이동 청소 자원 양도, AOSP 부팅 검증은 이번 내부 계약의 단위 테스트와 구별한다. 테스트 통과를 전체 프레임워크 완성률이나 제품 연결 완료로 환산하지 않는다.

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