Forter — ELM327 에뮬레이터 무응답·지연 시나리오와 앱 타임아웃 기준점
Forter — ELM327 에뮬레이터 무응답·지연 시나리오와 앱 타임아웃 기준점
This post was compiled by Claude.
보완할 점
- 실차 출퇴근 기록 22분 중 20분 공백 (신호 대기 중 시동 끔 → 어댑터 전원 차단)
- 에뮬레이터는 항상 즉시 정상 응답 → 무응답·지연 재현 불가, 검증하려면 차에 가야 함
목표
- 지연·무응답·복구 시나리오를 옵션으로 재현
- 현재 앱(v0.1.5)이 무응답에 어떻게 반응하는지 기준점 기록
개념 정리
무응답의 두 종류
1
2
ECU 무응답 → 어댑터가 NO DATA 응답 → 앱은 응답을 받음 (값만 없음)
어댑터 무응답 → 아무것도 안 옴 (시동 끔 등) → 앱 타임아웃
- 블루투스 링크는 살아 있으므로 소켓 오류가 아닌 타임아웃으로만 감지 가능
- 에뮬레이터로는 모르는 PID →
NO DATA,--hang-after→ 무응답
타임아웃 = 상한선
send("ATZ", timeoutMs = 5000): 5초 대기가 아니라 최대 5초,>오면 즉시 반환- 명령은 하나씩 주고받음:
>= 다음 명령 받을 준비 신호
에뮬레이터 변경
| 옵션 | 흉내 내는 상황 | 동작 |
|---|---|---|
--delay MS | 느린 어댑터 | 응답 전 지연 |
--hang-after N | 시동 끔 | OBD 명령 N개 응답 후 무응답 (0 = 끔) |
--hang-for SEC | 시동 다시 켬 | SEC초 후 복구, 다시 N개 후 무응답 (0 = 계속) |
sys.argv직접 비교 →argparse(숫자 변환·오류·--help자동)- AT 명령은 세지 않음 → 초기화는 항상 통과, 폴링 단계 실패를 재현
- 무응답 중에는 AT도 무시 → 전원 꺼진 어댑터와 동일
리팩터링
가드 절
1
2
3
4
5
6
7
8
9
10
11
12
13
# 전: 짝이 30줄 넘게 떨어짐
if data == b"\r":
cmd = ...
... # 명령 처리 30줄
else:
buf += data
# 후: 짧은 경우 먼저 처리하고 빠져나감
if data != b"\r":
buf += data
continue
cmd = ...
... # 명령 처리, 들여쓰기 한 단계 감소
read(1)과buf += data가 붙어 있어 한눈에 보임- 루프 전체가 “수신 없음 → 조립 중 → 빈 명령 → 무응답 판단 → 응답” 한 방향으로 읽힘
처리 순서
1
2
[상태: 무응답] 시간 됐으면 복귀, 아니면 무시
[상태: 정상] OBD N개 초과면 무응답으로 전환
- 순서를 바꾸면 무응답 중 OBD 명령마다 시작 시각이 갱신 →
--hang-for가 안 끝남 - 상태 확인 → 상태 전이 순서 (상태 기계 기본)
이름·타입 힌트
| 대상 | 전 | 후 | 근거 |
|---|---|---|---|
| 옵션 함수 | parse_args() | get_options() | 내부의 p.parse_args()와 혼동 방지 |
| 함수 시그니처 | 없음 | def handle_obd(cmd: str) -> str: | IDE 경고·자동완성 |
hang_start | = None | : float \| None = None | None 시작이라 타입 추론 불가 |
- 이름은 타입이 아닌 역할로 (
buf,cmd유지), 지역 변수는 추론되면 힌트 생략 (Python 관례) - 함수·변수는 snake_case (PEP 8)
결과
1
2
3
4
5
6
7
$ python -u main.py --hang-after 2
받음: ATZ → 보냄: ELM327 v2.3
...
받음: 0100 → 보냄: 410018198000
받음: 010C → 보냄: 410C0AE0
--- 무응답 시작: OBD 2개 응답 후 ---
받음: 010D → (무응답)
1
2
Forter v0.1.5-b03c1cb
실패: Timed out waiting for 3000 ms
기준점 (v0.1.5)
| 항목 | 결과 |
|---|---|
| 타임아웃 | 있음 (ATZ 5000ms, 나머지 3000ms) |
| 실패 1회의 영향 | 연결 흐름 전체 중단, 이미 받은 RPM도 버리고 기기 목록으로 복귀 |
rpm()/speed() | Int?지만 null은 해석 실패만, 타임아웃은 예외로 전파 |
문제 해결
| 증상 | 원인 | 해결 |
|---|---|---|
TypeError: unexpected keyword 'metaver' | metavar 오타, 문법 오류가 아니라 실행 시 발견 | 수정 |
TypeError: serve() takes 0 positional arguments | 호출은 serve(args), 정의는 def serve(): | def serve(args): |
| 에러 없이 응답을 안 보냄 | 가드 절 변경 후 아래 블록이 continue 뒤에 남음 | 내어쓰기 |
- 마지막 건: Java는 unreachable 컴파일 에러, Python은 경고 없이 실행
다음 단계
- 에뮬레이터
serve()역할 분리- 원인: 한 루프에서 명령 조립·무응답 판단·응답 전송을 모두 처리
- 명령 읽기를 제너레이터
read_commands()로 분리 →for cmd in read_commands(port): obd_count·hang_start를Hang클래스로 묶기 →if hang.should_ignore(cmd):- 무응답 판단을 긍정형으로 (
still_hanging), 무시하는 경우 먼저continue - 검증:
--hang-after 2로그가 리팩터링 전과 동일
- v0.1.6
send(): 전송 전 입력 버퍼 비우기, 타임아웃 →nullrpm()/speed(): 응답이"41" + PID로 시작하는지 확인- 1초 연속 폴링, 성공·실패 횟수 표시
- 검증:
--hang-after 5 --hang-for 10,--delay 3500
- 실차:
0100응답 시간 (SEARCHING...),ATDP,ATRV
This post is licensed under CC BY 4.0 by the author.