취소와 정지 연결
100분 안팎
학습 목표
Python 가상 액션 취소를 C HAL 정지 요청으로 연결합니다.
개념
취소 버튼에서 C 출력까지 추적합니다
관제 화면에 CANCELED가 떠도 C HAL의 left_pwm과 right_pwm이 0.2로 남아 있으면 구동 정지를 확인한 것이 아닙니다. 이번 레슨은 취소 접수 뒤 다음 가상 tick에서 C의 set_wheels를 호출하고 두 출력이 0인지 확인한 뒤 종료 결과를 기록합니다. 성공 기준은 상태 문자열과 실제 호출된 C 모델의 출력이 같은 계약을 만족하는 것입니다.
독립 레슨 lab은 앞 프로젝트의 hal.h와 hal_sim.c를 가져옵니다. 미션 lab은 m04 solution 전체를 복사한 출발점에 액션 기능을 추가합니다. 이전 센서 처리, 좌표 변환, C 회귀, 토픽 재발행과 진단 검사를 유지합니다. 기존 정상 측정 두 점과 결함 행 표시가 그대로 남아야 새 기능이 이전 기능을 깨지 않았다는 근거가 됩니다.
ctypes 경계는 작은 C 함수로 고정합니다
action_bridge.c는 static Hal device를 보유하고 bridge_init, bridge_set, bridge_left, bridge_right, bridge_fault를 제공합니다. Python이 C 구조체 메모리 배치를 직접 추측하지 않게 접근 함수를 둡니다. action_hal.py는 ctypes.CDLL로 임시 공유 라이브러리를 읽고 인자와 반환 타입을 선언합니다. bridge_set 인자는 double 두 개이고 반환은 상태 코드 int입니다.
C의 double 반환을 restype 없이 읽으면 기본 정수 해석 때문에 출력이 틀어질 수 있습니다. PWM을 읽는 두 함수에는 c_double을 지정합니다. HAL_LIBRARY는 check.sh가 빌드한 파일 경로입니다. 환경 변수 없이 action_demo.py를 직접 실행해 KeyError가 나면 먼저 bash action_demo.sh를 사용합니다. 이 셸은 빌드·환경 설정·정리를 함께 수행합니다.
한 라이브러리에 한 가상 장치가 있습니다
이 bridge는 C static 객체 한 개를 사용하므로 같은 라이브러리 안에서 Hal 인스턴스를 여러 개 만들면 상태를 공유합니다. 테스트는 매 사례의 setUp에서 초기화하고 한 목표 관리자와 한 HAL을 연결합니다. 다중 로봇 장치를 만들려면 장치 핸들을 나눠야 합니다. 현재 코드는 한 로봇 통신 계약을 시험하는 범위이며 여러 장치의 독립성을 보장하지 않습니다.
실행 중 PWM 0.2는 통신 연결을 눈으로 확인하기 위한 고정 입력입니다. max_speed_mps가 양수이면 두 바퀴에 0.2, 0이면 0을 넣습니다. PWM 0.2를 0.2m/s로 해석하면 단위를 섞은 것입니다. 목표 거리는 외부 주입 map 위치로 계산하고 실제 운동학과 속도 제어는 다음 모듈에서 구현합니다.
취소 처리를 tick의 앞쪽에 둡니다
cancel은 ACCEPTED나 EXECUTING인 목표만 CANCELING으로 바꿉니다. 이 호출 자체는 HAL을 정지시키지 않으며 접수 표시만 반환합니다. tick은 활성 목표가 CANCELING인지 먼저 검사하고 _finish를 호출합니다. 도착 거리 판정은 그 뒤에 있으므로 같은 tick에서 목표 위치에 도달했더라도 이미 접수된 취소가 우선합니다.
가상 tick 간격은 0.05초이며 취소 뒤 한 번의 tick 안에 출력이 0이 되는 것을 테스트합니다. 실제 sleep이나 벽시계 지연을 사용하지 않습니다. 호출 일정이 중단된 경우까지 실제 50ms 상한을 보장하지 않으며 네트워크 지연과 스레드 스케줄도 측정하지 않습니다. 결과의 tick은 정수 단계 번호이고 tick_s를 곱하면 가상 경과 시간으로 읽을 수 있습니다.
정지 요청의 반환과 출력 확인을 함께 사용합니다
_finish는 hal.set(0.0,0.0)의 반환 상태를 읽고 hal.outputs가 (0.0,0.0)인지 검사합니다. 정상 상태이면 CANCELED와 USER_CANCEL을 기록합니다. C HAL의 결함 상태가 이미 래치되어 있으면 set_wheels가 비정상 상태를 반환하며 출력은 안전값에 남습니다. 이 경우 종료 결과는 ABORTED와 HAL_FAULT로 기록해 정상 취소로 오인하지 않게 합니다.
출력 확인이 실패하면 STOP_NOT_CONFIRMED 예외를 발생시키고 성공 종료를 기록하지 않습니다. PC 오류를 드러내기 위한 처리이며 예외만으로 실물 안전을 보장하지 않습니다. 제품에서는 정지 재시도 기한과 독립 차단 경로를 설계해야 합니다. 그 설계를 했다고 가정하지 않고 ACTION-CONTRACT.md에 후속 확인으로 남깁니다.
성공과 실패에서도 출력 계약을 유지합니다
목표 거리가 0.05m 이하이면 성공 종료 경로로 들어가며 그때도 먼저 HAL을 0으로 만듭니다. 위치가 NaN이거나 유한하지 않으면 BAD_POSE로 실패 종료합니다. 좌표 자체는 유한하지만 거리 계산이 넘쳐 유한하지 않으면 BAD_DISTANCE입니다. 통신 취소만 고치고 성공·실패 경로에 이전 PWM을 남기지 않도록 공통 종료 함수를 사용합니다.
종료 뒤 active를 비워 새 id의 목표를 받을 수 있습니다. 과거 결과는 jobs에 남고 snapshot은 깊은 복사를 돌려줍니다. 조회한 딕셔너리를 수정해도 원래 목표와 결과가 바뀌지 않는지 확인합니다. 완료 뒤 취소는 TOO_LATE, 없는 id 취소는 UNKNOWN_GOAL, 반복 접수 중 취소는 CANCELING으로 반환합니다.
starter 실패를 읽고 TODO를 수정합니다
starter의 _finish는 상태만 바꾸고 실제 정지 호출과 확인을 생략합니다. test_cancel_next_tick과 test_bad_pose에서 기대 출력 0과 실제 0.2가 다르게 나옵니다. 이 실패는 공유 라이브러리 빌드가 안 된 오류와 다릅니다. C 컴파일 경고나 OSError가 나면 먼저 빌드 로그와 HAL_LIBRARY 경로를 확인하고, AssertionError라면 동작 계약을 비교합니다.
수정은 _finish의 TODO 두 곳입니다. status에 C 정지 호출 반환을 넣고, 출력 확인 조건을 활성화합니다. 미션 starter에는 동일 id 판정 TODO도 있어 DUPLICATE와 CONFLICT를 구별해야 합니다. 테스트 파일을 바꾸어 통과시키지 않습니다. check.sh의 임시 라이브러리는 종료 시 삭제되므로 저장소에 바이너리를 남길 필요가 없습니다.
회귀 검사의 범위를 확인합니다
레슨 lab은 액션 관련 unittest 20개를 실행합니다. 취소 전후 출력, 접수 전 취소, 도착과 취소의 순서, HAL 결함, 종료 불변, 거리 두 축, 범위 오류, 복사와 중복 정책을 확인합니다. 미션은 기존 C 44개 검사와 Python 61개 회귀를 먼저 돌린 뒤 전체 Python 81개를 다시 실행합니다. 기존 회귀와 신규 액션 20개가 모두 포함되는지 로그를 읽습니다.
check_previous.sh는 이전 검사를 보존한 스크립트이며 check.sh를 호출하지 않습니다. 두 스크립트가 서로를 다시 호출하면 반복 실행이 끝나지 않습니다. 셸을 수정할 때 호출 방향을 확인합니다. 정상 데모에서 tick=1은 EXECUTING과 PWM (0.2,0.2), 취소 다음 tick=2는 CANCELED와 PWM (0.0,0.0)인지 비교합니다.
실물 정지 시험의 경계를 문서화합니다
PC 출력 0은 모델에 전달한 명령의 증거이며 실제 바퀴의 속도가 0이라는 측정은 아닙니다. 관성, 경사, 브레이크, 전원 이상으로 정지 동작은 달라집니다. watchdog 참고 장은 실행 정지 때 소프트웨어가 함수를 호출하지 못하는 상황을 설명합니다. 여기서는 그 장의 예제를 복제하지 않고 독립 안전 경로를 후속으로 구분하는 근거로 읽습니다.
미션 제출은 ACTION-CONTRACT.md, 실행 로그, 변경 이유입니다. 실제 ROS 2 목표 핸들·취소 콜백·결과 전달과 구동기 정지 확인은 미실행으로 표시합니다. 현재 완료 증거는 서비스 조회, 목표 상태, C HAL 정지 연결과 기존 토픽 회귀가 지정 fixture에서 통과했다는 범위입니다. 이 범위를 정확히 전달해야 다음 제어 모듈 작성자가 출발점을 오해하지 않습니다.
따라하기
starter의 실패를 확인합니다
robotics-m05-cancel starter에서 bash check.sh를 실행합니다. 20개 중 정지 관련 4개 실패를 찾아 실제 (0.2, 0.2)와 기대 (0.0, 0.0)을 비교합니다. 테스트를 고치지 않습니다.
정지 호출과 확인을 연결합니다
action_runtime.py의 _finish에서 status에 self.hal.set(0.0, 0.0)을 대입하고 확인 조건을 self.hal.outputs() != (0.0, 0.0)으로 바꿉니다. 출력 확인 뒤에만 결과를 기록하는 기존 순서를 유지합니다.
실제 C bridge 데모를 실행합니다
수정한 폴더에서 bash action_demo.sh를 실행합니다. 다음은 solution에서 직접 실행한 출력입니다. tick=1과 tick=2의 PWM 및 상태를 비교합니다.
실행 결과
{'id': 'r1', 'ok': True, 'code': 'OK', 'value': 0.05}
submit=ACCEPTED
tick=1 state=EXECUTING pwm=(0.2, 0.2)
cancel=CANCELING
tick=2 state=CANCELED pwm=(0.0, 0.0)
{'id': 'g1', 'status': 'CANCELED', 'reason': 'USER_CANCEL', 'tick': 2}
이전 프로젝트와 함께 검증합니다
미션 starter의 동일 id TODO도 고친 뒤 bash check.sh로 C 44개, 이전 Python 61개, 전체 Python 81개 검사가 통과하는지 확인합니다. ACTION-CONTRACT.md에 실제 ROS 2 통신·물리 정지 후속 시험을 남깁니다.
확인 문제
실습
action_runtime.py의 _finish TODO 두 곳을 고칩니다. 실제 C HAL에 정지를 요청하고 출력 0을 확인한 뒤 결과를 남깁니다. 접수 전·실행 중 취소, 도착과 취소 우선순위, HAL 결함, 성공·실패 종료 출력, 종료 불변을 포함한 20개 검사를 통과합니다. bash action_demo.sh의 PWM 전후 로그와 수정 이유를 제출합니다. 미션은 별도 robotics-m05-service-action starter에서 m04 회귀를 유지하고 동일 id 중복·충돌 TODO까지 고칩니다.
실행 명령
bash check.sh
기대 결과
Python unittest 20개 OK; 데모 tick=2 CANCELED, pwm=(0.0, 0.0)
모범 답안
모범 답안 내려받기더 읽기
면접 질문
- ROS 2 토픽과 서비스, 액션의 선택 기준을 설명해 주시면 됩니다.