Post

Forter — ELM327 에뮬레이터 무응답·지연 시나리오와 앱 타임아웃 기준점

Forter — ELM327 에뮬레이터 무응답·지연 시나리오와 앱 타임아웃 기준점

This post was compiled by Claude.

보완할 점

  1. 실차 출퇴근 기록 22분 중 20분 공백 (신호 대기 중 시동 끔 → 어댑터 전원 차단)
  2. 에뮬레이터는 항상 즉시 정상 응답 → 무응답·지연 재현 불가, 검증하려면 차에 가야 함

목표

  1. 지연·무응답·복구 시나리오를 옵션으로 재현
  2. 현재 앱(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 = 계속)
  1. sys.argv 직접 비교 → argparse (숫자 변환·오류·--help 자동)
  2. AT 명령은 세지 않음 → 초기화는 항상 통과, 폴링 단계 실패를 재현
  3. 무응답 중에는 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 = NoneNone 시작이라 타입 추론 불가
  • 이름은 타입이 아닌 역할로 (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은 경고 없이 실행

다음 단계

  1. 에뮬레이터 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 로그가 리팩터링 전과 동일
  2. v0.1.6
    • send(): 전송 전 입력 버퍼 비우기, 타임아웃 → null
    • rpm()/speed(): 응답이 "41" + PID로 시작하는지 확인
    • 1초 연속 폴링, 성공·실패 횟수 표시
    • 검증: --hang-after 5 --hang-for 10, --delay 3500
  3. 실차: 0100 응답 시간 (SEARCHING...), ATDP, ATRV
This post is licensed under CC BY 4.0 by the author.