21.4.3. 커스텀 명령어 라우터(Router) 구현
앞선 21.4.2 단원들을 통해 우리는 단지 3개의 기본 명령어(start, stop, status)만 처리할 줄 아는 평범한 표준 모듈을 완성했다.
ModuleBase가 지원하는 이 3가지 기본 어휘만 알아들어도 모듈은 충분히 훌륭하게 돌아간다.
하지만 비행 제어기(Flight Controller)라는 복잡한 세계에서, 모듈은 단순히 켜고 끄는 스위치가 아니라 살아 숨 쉬는 하나의 거대한 설정 메뉴판과 같다.
- “지금 당장 2번 모터의 방향을 역회전으로 바꿔라”
- “EKF 필터 내부의 자이로 바이어스(Bias) 값을 강제로 0으로 초기화해라”
- “테스트 버저(Buzzer)를 3초간 울려라”
이처럼 표준 생명주기를 벗어나는 고차원적인 런타임 제어 명령어를 NSH 터미널에서 실시간으로 쏘아 보내려면, 여러분은 **custom_command()**라는 메서드를 오버라이드하여 나만의 강력한 ’공유기(Router)’를 구축해야만 한다.
1. ModuleBase의 남은 찌꺼기 짬처리장: custom_command()
ModuleBase 시스템은 내부적으로 task_spawn이라는 거대한 교통정찰수를 두고 있다. 사용자가 터미널에 custom_app 어쩌구 저쩌구... 라고 치면, 일단 task_spawn이 제일 앞의 단어를 검사한다.
- 그 단어가
start,stop,status중 하나인가? -> 자기가 알아서 처리한다. - 그 단어가 저 3개 중 아무것도 아닌 처음 보는 단어인가? (예:
reset_sensor,test_motor)
-> 그 남은 단어들(argv)을 몽땅 싸잡아서 가차 없이 자식 클래스의custom_command(argc, argv)메서드 안으로 던져버린다.
여러분이 이 사실을 모르고 custom_command()를 오버라이드하지 않은 채 custom_app test_motor 라고 쳤다면, ModuleBase의 기본 구현체는 냉정하게 print_usage() (도움말)를 화면에 띄우고 커맨드를 무시해 버릴 것이다.
2. 커스텀 옵션의 인터페이스
여러분의 모듈이 이 ’찌꺼기 명령어’들을 이쁘게 소화해 내려면 다음과 같은 시그니처 형식을 준수해야 한다.
// CustomApp.h (클래스 선언부)
class CustomApp : public ModuleBase<CustomApp> {
// ...
public:
// 반드시 static으로 선언되어야 한다!
// (모듈 객체가 메모리에 뜨기 전에도 사용자가 칠 수 있어야 하므로)
static int custom_command(int argc, char *argv[]);
};
이 함수가 static으로 선언되어 있다는 점은 C++ 개발자에게 극도의 짜릿함과 고통을 동시에 선사한다.
static 함수이므로 여러분은 이 함수 안에서 this-> 포인터를 쓸 수 없으며, 모듈의 멤버 변수를 직접 주무를 수도 없다. 왜냐하면 터미널 커맨드가 입력되는 그 찰나의 순간에 모듈 객체가 아직 힙 메모리에 할당(instantiate)되지 않았을 수도 있기 때문이다.
그렇다면 어떻게 이 static 영역에서 런루프 속의 변수들을 원격으로 조종할 수 있을까? 이를 해결하기 위한 문자열 파싱 최적화와 싱글톤 포인터 접근 흑마술을 다음 단원(21.4.3.1)에서 낱낱이 해부해 보자.