Devin.KR

결함 상태와 정지

150분 안팎

학습 목표

센서 읽기 실패를 주입하면 가상 모터 출력을 0으로 바꿉니다.

개념

센서 실패가 출력에 미치는 영향을 봅니다

장애물 거리 읽기에 실패했는데 이전 모터 출력이 남아 있으면 제어기가 더 이상 유효하지 않은 판단으로 계속 운전할 수 있습니다. 이번 레슨은 정상 출력이 저장된 상태에서 실패를 주입하고 read_range가 반환하기 전에 양쪽 출력을 0으로 바꾸는 규칙을 구현합니다. 오류 코드만 남기는 것으로 끝내지 않고 실패 후 새 요청까지 보내 정지 상태가 유지되는지 확인합니다.

결함 진입 동작을 한곳에 모읍니다

stop 함수는 left_pwm와 right_pwm를 0으로 저장하고 status를 이유 코드로 갱신한 뒤 같은 코드를 반환합니다. 센서 범위 오류, 비유한 측정, 잘못된 시간, 입력 계약 위반과 fixture 종료가 이 경계를 사용합니다. 상태 변경과 출력 변경을 서로 다른 호출로 흩어 놓으면 한 경로에서 정지를 빠뜨리기 쉽습니다. 함수 이름은 실제 물리 제동이 아니라 PC 상태의 정지 요청을 뜻합니다.

starter에서는 stop의 왼쪽 출력 대입이 빠져 있습니다. 센서 읽기가 HAL_SENSOR를 반환해도 left_pwm는 직전 요청 0.6으로 남고 right_pwm만 0이 됩니다. 상태 코드만 검사하는 테스트는 이 결함을 놓칩니다. 같은 시점의 반환 상태, 두 출력, 출력 포인터 보존을 함께 검사합니다. 여러 호출 경로가 stop을 공유하므로 한 수정이 시간 오류와 범위 오류에도 영향을 주는지 확인합니다.

실패 주입과 결함 상태를 구분합니다

injected는 이번 읽기에 장치 실패가 발생하도록 테스트가 켜는 입력입니다. status는 이미 관찰된 결과가 장치에 남는 상태입니다. injected를 true로 만든 순간 자동으로 함수가 실행되지는 않습니다. 다음 read_range가 주입을 관찰하고 HAL_SENSOR로 전이하며 출력을 끕니다. 이 실습에서 실패 직후라는 말은 실패를 관찰한 읽기 함수가 반환한 경계를 뜻합니다.

센서 실패를 관찰한 뒤 injected를 false로 바꿔도 status는 HAL_SENSOR로 남습니다. 오류가 사라진 것처럼 보이는 한 번의 입력으로 자동 재시작하지 않도록 잠근 정책입니다. read_range, set_wheels, step은 기존 상태가 HAL_OK가 아니면 그 이유를 먼저 반환합니다. 고장이 유지되는 동안 논리 시각과 샘플 인덱스도 진행하지 않습니다. 재현할 때에는 같은 시각의 정지 상태를 비교할 수 있습니다.

실패한 출력 칸을 새 측정으로 쓰지 않습니다

read_range의 출력 포인터는 성공 경로에서만 바뀝니다. 실패 전 range_m가 0.05였다면 실패 후에도 같은 숫자가 남을 수 있습니다. 이는 현재 거리가 0.05라는 증거가 아니라 마지막 성공 값이 보존됐다는 뜻입니다. 반환 코드를 무시하면 오래된 값을 새 좌표로 변환하게 됩니다. 로깅할 때도 값의 유효성을 함께 기록하고 후속 처리에서 그 표시를 해석해야 합니다.

미션 JSONL의 정상 행은 sample_valid=true, status=0입니다. 실패 행은 sample_valid=false, status=2이며 range_m에는 마지막 정상값을 남깁니다. m01의 validate_message는 기본 필드와 유한 범위만 검사하므로 실패 진단 행도 형태 검사를 통과합니다. 따라서 기본 계약 통과와 새 측정 사용 가능 여부는 별개입니다. verify_hal.py가 상태와 유효 표시를 추가 검사한 뒤 정상 행만 m02의 message_point로 넘깁니다.

실험 시각은 반복 입력의 일부입니다

고정 샘플은 1.0, 0.05, 8.0m이고 논리 간격은 0.1초입니다. seq 0과 1은 정상 읽기이며 세 번째 시각 0.2초에 injected를 켜므로 8.0m를 새 측정으로 반환하지 않습니다. 이때 로그의 range_m는 이전 0.05로 유지됩니다. step은 시간을 실제로 기다리지 않고 다음 샘플과 논리 시각만 진행합니다. 실행 컴퓨터의 처리 속도를 측정 시각으로 사용하지 않습니다.

step에 0, 음수, 1초 초과, NaN, Infinity를 주면 HAL_INVALID로 정지하고 index와 stamp_s를 유지합니다. 잘못된 시간으로 진행한 뒤 오류를 기록하면 다음 로그가 이미 잘못된 시각을 포함하게 됩니다. 다음 샘플이 없는 경우 HAL_END도 두 출력을 끕니다. 데이터 끝은 센서 고장과 원인이 다르므로 로그에서는 구분하고 출력 중단 정책만 공유합니다.

전이 후에 요청을 다시 보냅니다

고장 주입 테스트는 정상 출력 설정, 실패 읽기, 주입 해제, 다시 읽기, 모터 요청, 시간 진행 순서로 실행합니다. 처음 정지만 시험하면 다음 요청으로 재시작하는 결함을 놓칠 수 있습니다. 새로운 모터 요청 (1,1)은 여전히 HAL_SENSOR를 반환하고 출력 0을 유지해야 합니다. 정상 복귀가 필요하다면 이 모델의 범위를 넘어 복귀 조건과 별도 승인 경로를 설계해야 합니다.

hal_init을 호출하면 새 실험 초기 상태를 만듭니다. 이를 운전 중 고장 자동 해제 함수로 호출하지 않습니다. 실제 장치의 복귀에는 원인 제거 확인, 운영 상태, 재시작 명령 등 추가 조건이 필요할 수 있습니다. 여기의 잠금 정책은 학습용 결정이며 실물 안전 수준을 인증하지 않습니다. 완료 증거에는 이 정책과 검증한 함수 경계를 명시해 다음 담당자가 자동 복귀를 임의로 추가하지 않도록 합니다.

C 로그를 기존 프로젝트로 연결합니다

미션 starter는 m02 solution의 sensor.py, frames.py, 설정, fixture, run.sh와 기존 테스트 28개를 유지합니다. C 검사 후 시뮬레이터를 빌드해 JSONL 세 행을 임시 파일에 저장합니다. Python 검증기는 seq, stamp_s, frame_id, range_m 기본 계약과 추가 상태 필드를 확인합니다. 정상 두 행의 sensor 전방 점만 기존 프레임 트리로 map에 옮겨 결과를 비교합니다.

앞 모듈의 고정 자세는 odom에서 로봇 위치 (1,2)m, 반시계 90도 회전, 센서 장착 전방 0.2m입니다. 그래서 거리 1.0m는 map (1,3.2)m, 거리 0.05m는 map (1,2.25)m가 됩니다. 실패 행은 map 점을 만들지 않으므로 points=2, faults=1입니다. range_m를 map 원점까지의 거리로 덮어쓰지 않고 앞 모듈의 측정 의미와 변환 결과 필드를 유지합니다.

출력으로 두 종류의 실패를 읽습니다

FAIL immediate stop이면 센서 실패 직후의 저장 출력을 봅니다. PASS fault 뒤 bridge 검증이 실패하면 C 메모리 정지 규칙은 맞지만 메시지 형식이나 시각, 좌표 연결이 다를 수 있습니다. C가 stdout에 안내 문구를 출력하면 JSONL 파서가 첫 줄부터 실패할 수 있으므로 진단 문구는 stderr로 보냅니다. 수정 시 어느 층의 계약이 깨졌는지 먼저 분리합니다.

레슨 실습의 PASS fault checks=16과 미션의 PASS all checks=44는 서로 다른 검사 범위입니다. 미션은 기존 28개에 연결 검증 6개를 더한 Python 테스트 34개도 통과해야 합니다. 마지막에는 실제 C JSONL에서 PASS bridge rows=3 points=2 faults=1과 알려진 map 점 두 줄을 확인합니다. 테스트 fixture만 Python에 손으로 넣어 통과한 결과로 C 출력 연결 검증을 대신하지 않습니다.

인계에는 실패 증거도 포함합니다

제출물은 변경한 hal_sim.c, 실행 명령, 성공과 실패 로그, 유효 샘플 사용 규칙, 실물 시험에서 추가할 조건입니다. 정상 주행 영상만으로 고장 정지와 재시작 금지 여부를 증명할 수 없습니다. 이번 결과는 PC에서 상태와 요청 값을 검증한 범위로 설명합니다. 더 읽기의 상태 기계 장에서 복귀 조건 설계를 확장하고 다음 모듈에서는 이 메시지를 노드와 토픽 계약으로 이어 갑니다.

호출 경계를 코드로 확인합니다

초기화한 h의 실패 입력을 켠 뒤 읽기 결과에 따라 후속 처리를 분기합니다. stop 동작은 read_range 내부에서 완료되어 반환된 경계에 출력 0이 저장됩니다.

h.injected = true;
HalStatus status = read_range(&h, &range_m);
if (status != HAL_OK) {
    /* range_m는 새 측정이 아니므로 좌표 변환을 하지 않습니다. */
}

따라하기

시작 코드의 실패를 확인합니다

starter ZIP을 푼 폴더에서 실행합니다. stdout 요약과 별도로 stderr의 FAIL 항목이 어느 계약을 가리키는지 읽습니다. 종료 상태 1은 이번 TODO에서 기대한 실패입니다.

bash check.sh

실행 결과

FAIL fault checks=16 failures=11

계약에 맞는 대입을 작성합니다

hal_sim.c의 해당 TODO를 다음 코드로 고칩니다. 아래는 편집할 C 조각이며 셸에서 직접 실행하는 명령이 아닙니다. 헤더와 테스트는 유지합니다.

/* stop 함수에서 상태를 기록하기 전에 양쪽을 정지합니다. */
hal->left_pwm = 0.0;
hal->right_pwm = 0.0;
hal->status = reason;

같은 검사를 다시 실행합니다

수정한 starter 폴더에서 다시 검사합니다. 성공 요약과 종료 상태 0을 확인합니다. 이 출력은 solution 구현에서 실행해 확인한 값입니다.

bash check.sh

실행 결과

PASS fault checks=16

저장된 결과를 관찰합니다

수정 후 관찰 실행 파일로 값과 상태를 확인합니다. 아래 명령은 임시 디렉터리에서 빌드하고 정리합니다.

bash demo.sh

실행 결과

{"seq":0,"stamp_s":0.000,"frame_id":"sensor","range_m":1.000,"sample_valid":true,"status":0,"left_pwm":0.250,"right_pwm":0.500}
{"seq":1,"stamp_s":0.100,"frame_id":"sensor","range_m":0.050,"sample_valid":true,"status":0,"left_pwm":-1.000,"right_pwm":1.000}
{"seq":2,"stamp_s":0.200,"frame_id":"sensor","range_m":0.050,"sample_valid":false,"status":2,"left_pwm":0.000,"right_pwm":0.000}
restart status=2 left=0.000 right=0.000

확인 문제

실습

stop의 누락된 대입을 채워 센서 및 시간 실패에서 두 출력이 0이 되도록 합니다. 주입 해제 후에도 오류 상태를 유지하고 출력 칸과 논리 시각을 보존하는 검사 16개를 통과합니다. 이어 모듈 미션 ZIP에서 C와 Python 연결을 완성합니다.

시작 코드·테스트 내려받기

실행 명령

bash check.sh

기대 결과

PASS fault checks=16

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 센서 읽기에 실패한 뒤 이전 출력과 이전 측정값을 어떻게 처리합니까?
  • 실험 기록을 재생할 때 함께 남겨야 할 정보를 설명해 주시면 됩니다.