복구와 안전한 상태 표시
120분 안팎
학습 목표
센서 분리 때 측정값 전송을 중지하고 복구 이벤트 뒤 새 샘플로 재개합니다.
개념
확정한 새 샘플만 공개합니다
복구의 목표는 오류 문자열을 없애는 일이 아니라 정상 데이터를 다시 안전하게 공개하는 일입니다. 이전 온도 프레임이 남아 있고 센서가 재연결되었다고 바로 그 프레임을 다시 보내면 오래된 값이 새 측정처럼 보입니다. 이번 미션은 m06의 버튼·타이머·큐·통신·저장 코드를 이어받아 Recovery 계층을 추가합니다. 실패 시 어떤 메모리를 보존하고 어떤 유효성을 지울지 구분하여 테스트합니다.
다운로드에는 embedded-sensor-buses의 solution 소스와 회귀가 포함되어 있습니다. buses.c의 주소·바이트 변환·XOR·CS 처리와 events.c의 큐 계약은 그대로 유지합니다. 기존 bus_consume은 이전 회귀용이며 새 메인 경로는 recovery_tick입니다. 한 이벤트를 두 소비 함수에 동시에 넘기지 않습니다. 같은 데이터를 두 번 Buffer에 넣는 문제가 생길 수 있습니다. 추가 파일은 recovery.h·recovery.c·retry.c와 시험·데모입니다.
통합 루프의 호출 순서는 timed_button, timer_poll, recovery_tick입니다. 버튼의 정지 의도를 먼저 반영하고 타이머가 이벤트를 넣은 다음 상태를 진행합니다. 가상 ISR은 큐에 예정 시각만 넣고 I2C·UART·SPI는 메인에서 호출합니다. recovery_tick은 현재 상태 분기 하나만 처리하므로 IDLE→MEASURE→TRANSMIT→IDLE에 여러 호출이 필요합니다. 이 구조는 단계 사이에 분리나 정지를 주입하여 취소 정책을 검사할 수 있게 합니다.
IDLE은 큐에서 이벤트 하나를 꺼내 시도 번호 1과 시작 시각을 설정합니다. 예정 due는 요청 계기가 되지만 새 데이터의 timestamp가 아닙니다. MEASURE는 retry_decide로 응답 준비를 판단하고 HAL 성공 뒤 온도·ADC를 변환합니다. pending.timestamp_ms는 측정 확정 tick입니다. 미션의 fixture는 현재 word가 성공 때 읽히는 단순 모델이며 요청 순간 바이트를 고정하거나 센서 내부 변환 지연을 재현하지 않습니다.
성공 MEASURE는 최근 세 값 평균과 원시 ADC를 pending에 저장하고 valid를 켠 뒤 TRANSMIT으로 갑니다. 다음 호출에서 pending 나이가 300ms 미만인지 확인합니다. 299면 공개 가능하고 300이면 BUS_TIMEOUT으로 ERROR입니다. 이 오류는 응답 제한 20ms 실패와 같은 코드라도 발생 상태가 다릅니다. 로그에는 상태와 시각을 함께 적어 요청 무응답과 공개 지연을 구별합니다. 시간 초과 코드 하나로 원인을 확정하지 않습니다.
전송 전 UART 공간 13바이트와 Buffer 용량 16을 확인합니다. 지역 frame을 직렬화하고 SPI select→write→release를 수행합니다. write가 full로 실패해도 release합니다. SPI 성공 뒤 Buffer에 ADC raw를 추가하고 UART 배열과 uart_n을 공개합니다. 저장에는 ADC 원시값, 프레임에는 adc_mv라는 기존 계약을 유지합니다. 이 PC 경로는 RAM 복사 순서를 검증하며 실물 두 장치의 원자적 완료를 보장하지 않습니다.
오류가 발생하면 valid와 평균 개수·cursor를 지우고 대기 이벤트를 비웁니다. Buffer의 과거 기록, SpiStore의 과거 바이트, 마지막 UART 배열은 보존합니다. 과거 증거는 분석에 필요하지만 새 공개로 표시하지 않습니다. uart_n도 마지막 정상 길이를 유지하므로 n이 13이라는 사실은 이번 tick 전송 신호가 아닙니다. 메인 호출 전후 Buffer 길이 차이로 새 공개 여부를 확인합니다. 재전송이 필요하면 이후 별도 식별자 정책이 필요합니다.
시간 초과와 NACK는 남은 총 시도 예산이 있으면 RECOVER로 가며 실패 시각부터 100ms를 기다립니다. 휴지 중 들어온 이벤트는 버립니다. 100ms에 다음 요청을 시작하고 세 번째 실패는 ERROR에 고정합니다. fixture가 정상으로 바뀌어도 ERROR를 자동 해제하지 않습니다. ADC 오류·UART 공간 부족·SPI full은 같은 요청을 다시 보내도 해결되지 않으므로 바로 ERROR입니다. 원인에 맞는 수동 점검을 요구합니다.
분리 fixture는 현재 상태가 ERROR가 아니면 RECOVER로 이동하며 detached를 설정합니다. 케이블 연결 상태가 다시 1이어도 자동 요청하지 않습니다. recovery_reconnect가 호출되어야 IDLE로 돌아가고 큐·필터·valid·예산을 초기화합니다. 이 함수는 센서 연결을 직접 확인하지 않으므로 호출자가 연결 fixture를 준비합니다. 다시 분리 상태로 tick을 호출하면 즉시 복구 대기로 돌아갑니다. 실물 연결 탐지와 재초기화는 보드 HAL에서 추가해야 합니다.
재연결 직전에 큐에 남은 예정 이벤트도 비웁니다. 오래된 due를 재연결 순간 소비하면 이전 주기의 작업을 성공으로 착각할 수 있습니다. 데모에서는 400ms에 reconnect 뒤 그 시각의 새 timer_poll이 이벤트를 만듭니다. 410에 새 값을 확정하고 420에 공개합니다. 이전 두 값의 평균을 섞지 않아 첫 복구 온도는 새 10000밀리도입니다. 역사 기록은 두 개를 유지하고 세 번째부터 새 데이터가 들어갑니다.
정지는 진행 중 측정과 전송을 취소하고 큐·평균·valid를 비웁니다. ERROR는 정지로 해제하지 않고 분리 RECOVER도 유지합니다. 다시 기록을 켜더라도 오류를 숨기지 않으려는 정책입니다. 단순 시간 초과 휴지는 정지하면 IDLE로 취소할 수 있습니다. 이 차이는 초기 Python 표의 복구 단순화에서 추가된 보호 조건입니다. RECOVERY-SPEC.md에서 실제 미션 정책을 확인하고 전이표에 detached 조건을 붙입니다.
LED는 유효한 최근 샘플이 있고 기록 중이면 켜집니다. IDLE에서도 마지막 확정 값이 300ms 미만이면 켜진 채 유지합니다. 아직 샘플이 없거나 오래되거나 RECOVER면 꺼집니다. ERROR는 now/100의 짝홀에 따라 번갈아 표시합니다. GPIO 출력은 recovery_tick의 마지막에 적용해 timed_button이 기록 의도로 쓴 값보다 상태 정책이 우선합니다. 실물에서 정확한 깜박임 주기를 보장하려면 루프 호출 간격을 따로 측정합니다.
starter에는 앞 단계와 복구 흐름이 있고 TRANSMIT에서 나이 검사만 빠져 있습니다. 따라서 일반 수집은 성공하지만 300ms 지연 샘플 공개 금지 테스트가 실패합니다. recovery.c의 freshness TODO를 채우고 test_recovery.c의 299와 300 사례를 대조합니다. 경계 비교를 고쳤는데 Buffer가 늘어나면 오류 처리 뒤 계속 공개하는 경로가 있는지 확인합니다. return이나 break 위치와 부작용 순서를 함께 읽습니다.
상태 검사는 정상 평균 네 값, 20ms 응답, 총 세 번 실패, 두 번째 시도 성공, NACK, 분리 전송 취소, 명시 재연결, 정지, SPI full, UART 공간, ADC 범위를 포함합니다. uint32 시계 순환과 LED 나이 경계도 검사합니다. CHECK는 실패 식과 행을 출력하므로 make의 마지막 줄만 보지 않습니다. 앞 모듈 회귀가 실패하면 새 기능 문제인지 기존 소스를 바꾼 문제인지 차이를 먼저 확인합니다.
통합 데모는 실제 timed_button과 timer_poll을 통해 수집합니다. 100·200ms 예정 이벤트는 120·220에 기록되고 250에 분리되어 두 기록을 유지합니다. 400의 명시 재연결 뒤 420·520·620에 세 기록을 더해 총 다섯 개, SPI 65바이트가 됩니다. 이것은 가상 시간 fixture의 결과이며 PC 실행 시간이나 보드 응답 시간 측정이 아닙니다. 각 출력의 valid와 led가 기대 정책에 맞는지 기록 수와 함께 읽습니다.
제출에는 수정 코드, 전이 조건 표, make test 실제 요약과 RECOVERY-EVIDENCE.md를 포함합니다. 분리 전후 길이 보존, 재연결 첫 값, 시도 상한, 경계 차단을 근거로 적습니다. 서재 디버깅 장에서 관찰 비용을 더 읽고 이번 로그는 시뮬레이션 증거로 한정합니다. 다음 동시성 모듈은 이 Recovery 계층과 큐를 이어받습니다. 재시도와 공개 경계를 유지한 채 공유 데이터의 소유권을 확장할 수 있도록 함수 계약을 문서에 남깁니다.
따라하기
공개 지연 실패 찾기
starter에서 300ms 나이 샘플의 Buffer 길이·상태 FAIL을 읽고 recovery.c의 freshness TODO를 채웁니다. test_recovery.c는 보존합니다.
make -s recovery-test오류 주입과 경계 통과
수정 뒤 동일한 검사를 실행합니다. solution에서 얻은 실제 요약과 비교하고 분리·full·순환 경계의 조건식을 확인합니다.
make -s recovery-test실행 결과
state-recovery: 126 cases, 0 failures
버튼부터 복구까지 실행
recovery_demo.c가 timed_button → timer_poll → recovery_tick을 호출합니다. 분리 후 이전 두 기록을 유지하고 재연결 뒤 새 온도로 재개하는 출력입니다.
make -s recovery-demo-test실행 결과
120 state=IDLE records=1 temp_mc=1000 valid=1 led=1 220 state=IDLE records=2 temp_mc=1000 valid=1 led=1 250 state=RECOVER records=2 temp_mc=1000 valid=0 led=0 400 state=MEASURE records=2 temp_mc=0 valid=0 led=0 420 state=IDLE records=3 temp_mc=10000 valid=1 led=1 620 state=IDLE records=5 temp_mc=10000 valid=1 led=1 recovery-demo: records=5 spi=65 reconnect=fresh
이전 회귀까지 제출
전체 검사를 실행하고 RECOVERY-EVIDENCE.md에 입력·상태·기록 증분·마지막 오류를 적습니다. BUS-SPEC.md는 m06의 명세로 함께 보존합니다.
make test확인 문제
실습
m06 solution을 포함한 시작 코드에서 recovery.c의 공개 신선도 TODO를 완성합니다. 나이 299/300 경계·총 3회 실패·분리와 명시 복구·정지·용량 오류를 같은 테스트로 검사합니다. make test로 앞 단계 회귀도 통과시키고 RECOVERY-EVIDENCE.md에 실제 결과와 실물 후속 항목을 기록합니다.
실행 명령
make test
기대 결과
이전 회귀·retry-budget·state-recovery 실패 0, recovery-demo records=5 spi=65; 종료 코드 0
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- 센서가 응답하지 않을 때 점검할 순서를 설명해 주시면 됩니다.
- 센서 기록기의 상태 머신을 설명해 주시면 됩니다.