호스트의 요청과 장치의 상태를 따로 기록하는 이유
AI 호스트가 속도 명령을 보냈지만 응답이 사라졌다. 장치가 못 받았을 수도, 접수한 뒤 응답만 잃었을 수도 있다. 응답이 없다는 이유로 새 ID의 명령을 반복하면 이미 실행한 행동을 다시 수행할 수 있다. 반대로 ACK를 받았다고 목표 속도나 물리 정지에 도달했다고 기록하면 관측 근거가 사라진다.
M04.04에서 버스 ACK와 관측/설정 유효성을 나눴다. 여기서는 현재 상태 + 사건 + 조건 → 다음 상태/처리로 판단을 정리한다. 상태는 지금 어떤 요청을 허용하는지, 사건은 프레임 수신·시간 만료·고장 보고처럼 무엇이 일어났는지다. 조건 없이 사건 이름만 나열하면 같은 입력을 다른 시점에 어떻게 처리하는지 알 수 없다.
아래는 코드로 실행하지 않는 교육용 계약이다. 실제 장치/표준의 공통 상태표나 검증된 안전 장치가 아니다. 실제 적용에는 전력/기구·독립 보호와 감시, 통신 실패·최대 지연·재부팅 검증이 별도로 필요하다.
네 상태에서 먼저 출력 허용 여부를 정한다
장치의 논리 상태를 IDLE, ACTIVE, INHIBITED, FAULT로 나눈다. ACTIVE만 새로 접수된 설정값에 따른 출력 갱신을 허용한다. 다른 상태에서는 교육 계약상 정상 동작 명령을 차단한다. 이 차단은 소프트웨어 판단이며 실제 핀/전력·브레이크나 로봇의 정지 관측을 뜻하지 않는다.
그림 M04.05-1. 각 칸은 서로 다른 역할/근거다. 아래 방향은 자료/처리의 연결이며 실제 성공을 자동으로 보장하지 않는다. 직접 제작한 개념도이며 본문의 조건과 함께 읽는다.
| 현재/사건 | 조건과 처리 |
|---|---|
| 부팅 | 새 epoch의 IDLE. 확인 전 출력 불허·과거 명령 복원 금지 |
| IDLE의 새 명령 | 현재 epoch·새 seq·내용/권한·사전 확인 충족 시 ACTIVE. 접수/만료 기록 |
| ACTIVE의 중복 | 같은 epoch/seq/내용이면 기존 접수 결과만 재응답. 재적용/연장 없음 |
| ACTIVE의 새 명령 | 만료 전·새 seq·현재 의도/내용·권한 충족 시 ACTIVE. 새 설정/만료 기록 |
| 명령 만료 | 장치 시각이 만료 이상이면 INHIBITED. 정상 출력 명령 차단 |
| 고장 사건 | 어느 상태든 제공 고장 조건 충족 시 FAULT. 일반 명령 거부 |
| 차단 중 일반 명령 | INHIBITED/FAULT에서는 heartbeat·새 seq에도 상태 유지·거부 |
| 명시 회복 | 원인 해소·별도 확인/권한 충족 시 IDLE까지만 복귀. 새 유효 명령 필요 |
같은 tick에 사건이 겹치면 이 계약은 고장 → 만료 → 수신 명령 순서로 판정한다. 이 우선순위를 명시해야 만료 시각의 패킷으로 의도치 않게 계속 ACTIVE가 되는 것을 막을 수 있다. 실제 구현의 ISR/큐·원자적 상태 갱신을 표만으로 검증한 것은 아니다.
명시 회복의 조건은 별도 감독 절차이며 이 단위에서 실제 reset/재가동 버튼을 누르지 않는다. 부팅 후 과거 EEPROM 설정을 읽을 수 있어도 과거 출력 권한과 장치 상태까지 복구된 것으로 간주하지 않는다.
명령 lease와 heartbeat를 구분한다
lease는 수락한 명령이 출력 갱신에 유효한 제한 구간이다. 예제 장치의 현재 epoch는7이고 장치 자체의 단조 시간축을 사용한다. 수락 시각100ms, 유효 기간50ms면 만료150ms다. 유효 구간은100ms 이상150ms 미만이며 정확히150ms에서는 만료가 먼저다.
| 장치 시각 | 제공 사건 | 판단 |
|---|---|---|
| 100ms | epoch7/seq41, 설정값0.2를 처음 접수 | ACTIVE, 만료150ms |
| 120ms | 같은 epoch7/seq41/내용을 재수신 | 중복 응답, 적용1회·만료150ms 유지 |
| 140ms | 통신 heartbeat | 연결 보고만 갱신, 명령 만료 유지 |
| 150ms | 만료와 seq42 새 일반 명령이 같은 tick | 먼저 INHIBITED, seq42 거부 |
| 160ms | 이전 epoch6/seq99 도착 | 오래된 세션 거부, 정상 출력 허용 안 됨 |
heartbeat는 상대 프로세스가 통신한다는 한정된 증거다. 새 센서·명령 의도나 물리 정상의 증거가 아니다. 오래된 명령이 중복 패킷/heartbeat로 무한히 연장되지 않도록 별도 유효성을 둔다. 다른 정책을 쓰려면 누가 어떤 조건으로 lease를 갱신하는지 계약을 다시 명시해야 한다.
epoch는 재부팅/세션을 구분하는 식별자다. 예제에서는 구간 내 seq가 증가하고 wrap이 없다고 준다. 수신 시각만으로 오래된 대기 명령의 의도가 새로워지지는 않는다. 새 seq라도 현재 의도/관측·권한을 확인할 수 없으면 접수하지 않는다. 같은 epoch/seq에 다른 내용이 오면 충돌 오류로 거부한다. 낮은 seq는 오래된 요청으로 거부하며, ID만 안다고 인증된 송신자가 되는 것은 아니다. 인증/권한 정책과 세션 식별을 혼동하지 않는다.
UART는 프레임과 메시지의 의미까지 정해 주지 않는다
M04.02의 UART 바이트 스트림 위에 요청 경계와 의미를 정의해야 한다. 가상 프레임은 sync·전체 길이·epoch/seq·종류/내용·검사값을 포함하며 전체 최대24B라고 준다. CRC 결과는 제공 입력이다. 실제 바이트 배치/CRC 알고리즘/파서를 구현한 것은 아니다.
제공된 부분 수신에서는 먼저 sync/길이를 확인하고 길이 상한을 넘으면 거부한다. 프레임을 덜 받았거나 검사 실패이면 정상 명령으로 접수하지 않는다. 예제의 바이트 사이 대기가10ms 이상이면 미완성 후보를 폐기하고 정해진 재동기화 절차로 돌아간다. 무한 버퍼 증가나 끝없는 대기 대신 길이/시간·재시도 상한이 있어야 한다. 폐기만으로 다음 바이트의 동기화가 보장되지는 않으므로 실제 파서의 복구 규칙/시험은 별도다.
프레임 검사가 맞아도 단위·범위·지원 버전, epoch/seq·현재 상태·권한 검사를 거쳐야 한다. CRC 통과는 인증·새 관측·동작 허가를 대신하지 않는다. 내용의 단위와 오류 계약은 M04.06에서 구체화한다.
ACK 대기 시간에는 선로 전송도 들어간다
별도의 선로 예산 예제다. 9600baud/8N1, 전체 요청24B·응답8B가 순차로 전송되고 응답은 요청 전체를 받은 뒤 시작한다고 준다. byte당 start1/data8/stop1로10bit이므로 요청25ms, 응답 약8.333ms, 합계 약33.333ms다. 장치 처리·USB/OS/큐·간격은 아직 더하지 않았다. 실제 UART 분주 오차도 별도다.
호스트가 요청의 첫 바이트를 내보낸 시점부터20ms만 ACK를 기다리면 이상적인 선로 합계보다도 짧다. 항상20ms에 재전송하는 설정은 이 계약에서 합리적인 응답 완료를 기다리지 못한다. timeout을 무한히 늘리는 대신 전송량/baud·기산점·처리 예산과 상한을 함께 조정해야 한다. 이 계산은 실제 지연 측정이나 보편적인 timeout 추천값이 아니다.
ACK 대기는 호스트가 응답을 얼마나 기다릴지, 명령 lease는 장치가 접수한 출력을 언제까지 허용할지를 뜻한다. 앞의10ms 바이트 사이 대기와50ms 명령 lease도 목적과 시작 사건이 다르다. 모두 'timeout'이라고 같은 숫자로 설정하지 않는다.
서로 다른 clock의 숫자를 바로 빼지 않는다
호스트의 wall clock은 사람이 읽는 날짜/시각과 관련되고, 단조 clock은 같은 시간원에서 경과 시간을 계산하는 데 쓴다. Python3.12 공식 문서는 단조 시간의 기준점이 정해져 있지 않아 두 호출의 차이를 사용한다고 설명한다. 이것이 호스트와 MCU의 기준점/clock rate가 같다는 뜻은 아니다.
호스트 tick500과 장치 tick100을 빼서400ms 통신 지연이라고 쓰지 않는다. clock ID/epoch·단위·동기화 관계/오차가 필요하다. 장치의100→150ms는 같은 장치 시간축 안의 차이이고, 호스트의500→520ms는 같은 호스트 시간축 안의 대기다. 재부팅 전후 tick과 짧은 정수 timer의 wrap 처리도 실제 계약에 포함해야 한다. 예제는 wrap 없는 구간만 사용한다.
접수 기록·늦은 응답·물리 결과를 분리한다
독립된 호스트 기록 예제에서 요청 전송을 host500ms에 시작하고 대기 마감520ms에 아직 응답이 없다면 결과를 OUTCOME_UNKNOWN으로 남긴다. 실패한 것은 대기 조건이며 장치의 미수신/미접수/미실행까지 확정된 것은 아니다.
늦은 ACK가 host527ms에 도착하면520ms보다7ms 늦은 접수 증거를 추가한다. 과거 timeout 기록을 지우거나 명령의 현재 lease·출력/물리 완료를 자동 갱신하지 않는다. 지금의 장치 상태/명령 유효성·관측을 다시 확인한다. 이 호스트 예제의 숫자는 앞 장치 시간표와 동기화됐다고 주지 않았다.
INHIBITED/FAULT에서 과거 접수 기록을 조회하더라도 현재 상태/만료를 함께 돌려줘야 한다. 과거 ADMITTED를 현재 출력 허가로 재사용하지 않는다.
이 계약에서 같은 epoch/seq/내용 재시도는 한 번 적용한 설정값을 다시 적용하지 않고 기존 접수 결과를 돌려주는 중복 처리를 전제로 한다. 중복 기록을 잃었거나 새 epoch라면 임의로 동일 실행이라고 가정하지 않는다. 특히 '위치를1만큼 증가' 같은 상대 명령은 새 ID로 다시 보내면 누적될 수 있다. 재시도 횟수/간격·전체 마감과 결과 조회/감독을 정하고 무제한 반복하지 않는다.
정지 요청도 요청 전송 → 접수 → 소프트웨어 상태 → 실제 관측을 나눠 적는다. ROS2 Jazzy CancelGoal 원본의 성공 응답도 목표가 CANCELING 상태로 전이했다는 의미다. 이 예제는 ROS 서비스를 실행하지 않으며 ACK나 취소 접수를 물리 정지 완료로 바꾸지 않는다.
다음 드라이버 계약으로 넘길 기록
요청 ID/epoch·seq/내용·단위/버전, 프레임/접수/거부 이유, host/장치 시각과 clock, 만료 기산점/기간, 상태 전이 이유·미확인 결과를 남긴다. 물리 관측은 별도 출처/시각/유효성으로 기록한다. 기존 4.4 센서 시간 정합의 획득/수신 차이와 연결한다.
기본 완료는 제공된 표에서 허용/거부·중복/만료·미확인을 일관되게 설명한 상태다. 실제 상태기계 코드/통신/재부팅·고장 주입이나 제어 안정성·물리 정지를 실증한 것이 아니다. 다음 M04.06에서 이러한 판단에 필요한 값/단위·범위/오류 필드를 정의한다.