# Unreal 차량 ↔ 웹 MQTT 컨트롤 가이드

## 목표

웹의 키보드/터치 WASD로 Unreal 차량을 조작하고, Unreal 창의 WASD 상태를 웹에 표시한다. 현재는 실제 로버 없이 같은 Mac과 같은 Wi-Fi에서만 실행한다.

## 환경 선택 규칙

AI는 작업 전 반드시 현재 운영체제를 확인한다.

- **공통 기준**: Unreal 프로젝트 구조, MQTT 토픽, JSON 형식, 차량 제어 규칙
- **macOS 기준**: Homebrew, `python3`, `ipconfig getifaddr en0`, 현재 프로젝트의 기준 환경
- **Windows 기준**: Mosquitto 설치본, `py -m http.server`, `ipconfig`, Windows Defender 방화벽

운영체제별 명령을 섞어 쓰지 않는다. 예를 들어 Windows에서 `brew`, macOS에서 `py -m`을 안내하지 않는다.

## 공통 구성

```text
웹 또는 휴대폰 브라우저
  └─ MQTT WebSocket :9001
       └─ Mosquitto broker
            ├─ MQTT TCP :1883 ─ Unreal 차량
            └─ MQTT WebSocket :9001 ─ 웹 컨트롤러
```

| 구성 요소 | 위치 | 역할 |
| --- | --- | --- |
| Unreal 프로젝트 | `testVi/` | UE 5.7 Vehicle Template 차량 씬 |
| 웹 컨트롤러 | `web-controller/index.html` | 키보드·터치 WASD와 MQTT 상태 표시 |
| 브로커 설정 | `web-controller/mosquitto.local.conf` | MQTT TCP 1883, WebSocket 9001 |
| Unreal 브리지 | `Source/testVi/MqttVehicleBridgeSubsystem.cpp` | 웹 토픽 구독, 차량 제어, UE 키 상태 발행 |

## MQTT 규약

| 방향 | 토픽 | 페이로드 |
| --- | --- | --- |
| 웹 → Unreal | `testvi/web/input` | `{"source":"web","keys":{"w":true,"a":false,"s":false,"d":false}}` |
| Unreal → 웹 | `testvi/unreal/input` | `{"source":"unreal","keys":{"w":true,"a":false,"s":false,"d":false}}` |

웹은 0.1초마다 현재 키 상태를 발행한다. Unreal은 웹 메시지가 0.35초 넘게 끊기면 차량 입력을 0으로 해제한다.

## 참고 파일과 사용 기준

AI는 아래 파일을 **설계 참고용**으로 사용한다. 교재 예제를 그대로 실행 대상으로 복사하지 말고, 현재의 웹/Unreal 토픽 구조에 맞춰 적용한다.

| 참고 파일 | 용도 | AI 적용 기준 |
| --- | --- | --- |
| `/Volumes/T7 Shield/ch12_mqtt.pdf` | MQTT의 Broker·Publish·Subscribe 개념과 디지털 트윈 흐름 | 전체 통신 구조 설명과 학습용 기준으로 읽는다. |
| `/Volumes/T7 Shield/mqtt_1_publish.py` | 기본 publish 예제 | 웹 또는 Unreal이 토픽에 상태를 발행하는 흐름의 기준으로 쓴다. |
| `/Volumes/T7 Shield/mqtt_2_subscribe.py` | 기본 subscribe·명령 분기 예제 | 수신 토픽, 콜백, 명령 처리 흐름의 기준으로 쓴다. |
| `/Volumes/T7 Shield/mqtt_3_subscribe_try.py` | 재연결 시 subscribe 재등록 예제 | 연결이 복구될 때 구독을 다시 설정해야 한다는 기준으로 쓴다. |
| `/Volumes/T7 Shield/mqtt_4_motor.py` | `FORWARD/BACKWARD/LEFT/RIGHT/STOP` 명령 매핑 | 실제 로버 확장 시 명령 변환 기준으로만 쓴다. 현재 웹 테스트에서는 발행하지 않는다. |
| `testVi/testVi.uproject` | Unreal 플러그인/모듈 활성화 | `MQTT`, `ChaosVehiclesPlugin`, `UnrealClaude` 활성화 상태를 확인한다. |
| `testVi/Source/testVi/MqttVehicleBridgeSubsystem.h` | Unreal 브리지 상태 정의 | MQTT client, 키 상태, 타임아웃 상태를 관리하는 기준 파일이다. |
| `testVi/Source/testVi/MqttVehicleBridgeSubsystem.cpp` | 핵심 Unreal MQTT 구현 | 토픽 구독, JSON 파싱, 차량 입력, Unreal 입력 발행의 기준 파일이다. |
| `testVi/web-controller/index.html` | 웹/모바일 컨트롤 UI | 터치·키보드 입력, MQTT 발행, 수신 상태 표시를 수정하는 기준 파일이다. |
| `testVi/web-controller/package.json` | 웹 MQTT 라이브러리 | `mqtt` 의존성을 설치하는 기준 파일이다. |
| `testVi/web-controller/mosquitto.local.conf` | macOS 현재 브로커 설정 | Mac 로컬/같은 Wi-Fi 테스트 기준이다. |
| `testVi/web-controller/mosquitto.windows.conf.template` | Windows 브로커 설정 템플릿 | Windows IP를 넣어 복사본을 만드는 기준 파일이다. |

### 파일 선택 규칙

1. **통신 개념을 물으면** PDF와 Python 예제를 먼저 참고한다.
2. **Unreal 동작을 바꾸면** `MqttVehicleBridgeSubsystem.cpp/.h`를 수정 대상으로 삼는다.
3. **웹 UI·모바일 입력을 바꾸면** `web-controller/index.html`만 수정 대상으로 삼는다.
4. **브로커/IP 문제면** 현재 OS에 맞는 Mosquitto 설정 파일을 수정한다.
5. **실제 로버를 붙일 때만** `mqtt_4_motor.py`의 명령 매핑을 참고하고, 테스트 토픽과 실제 장비 토픽을 분리한다.

## 공통 AI 작업 순서

### 1. Vehicle Template 생성

1. UE 5.7에서 **Games → Vehicle** 템플릿으로 프로젝트를 생성한다.
2. Chaos Vehicles 플러그인을 활성화한다.
3. 기본 차량 Pawn이 `ChaosWheeledVehicleMovementComponent`를 사용하는지 확인한다.
4. Play에서 기본 WASD 차량 제어가 되는지 먼저 검증한다.

### 2. Unreal MQTT 브리지 구현

1. `MQTT` 플러그인을 활성화한다.
2. Runtime C++ 모듈에 `MQTTCore`, `ChaosVehicles`, `Json`, `JsonUtilities` 의존성을 추가한다.
3. `UTickableWorldSubsystem`을 만든다.
4. 게임 월드에서만 `127.0.0.1:1883`으로 MQTT/TCP 연결한다.
5. `testvi/web/input` JSON을 구독한다.
6. `W-S`를 throttle, `D-A`를 steering으로 계산해 차량 movement component에 적용한다.
7. Unreal의 `IsInputKeyDown(W/A/S/D)` 상태를 `testvi/unreal/input`으로 0.1초마다 발행한다.

### 3. 웹 컨트롤러 구현

1. MQTT.js를 프로젝트 안에 설치한다: `npm install`.
2. 키보드 WASD와 모바일 pointer/touch 버튼을 모두 지원한다.
3. 현재 누르는 키, 마지막 송신 명령과 시간, Unreal 수신 키를 표시한다.
4. blur/pointercancel에서 키 상태를 해제하고 STOP을 발행한다.

## AI 구현 규칙

- C++에서는 MQTT Blueprint wrapper 객체를 직접 호출하지 않는다. 엔진 바이너리에서 심볼이 노출되지 않을 수 있다.
- `IMQTTCoreModule`, `IMQTTClient` 인터페이스를 사용한다.
- Editor 월드에서 MQTT에 연결하지 않는다. `GetWorld()->IsGameWorld()`를 확인한다.
- 신호 유실 시 차량이 계속 움직이지 않도록 타임아웃을 필수로 둔다.
- 실제 로버를 추가하기 전까지 `rover/command/move` 같은 실제 장비 토픽을 사용하지 않는다.

## 운영체제별 실행 기준

### macOS 기준

| 항목 | 기준 |
| --- | --- |
| Mosquitto 설치 | `brew install mosquitto` |
| 브로커 실행 | `/opt/homebrew/opt/mosquitto/sbin/mosquitto -c web-controller/mosquitto.local.conf -v` |
| 웹 서버 | `python3 -m http.server 5173` |
| Wi-Fi IP 확인 | `ipconfig getifaddr en0` |
| 휴대폰 웹 주소 예시 | `http://192.168.0.21:5173` |
| 방화벽 | macOS 방화벽에서 Python 수신 연결을 허용 |

### Windows 기준

| 항목 | 기준 |
| --- | --- |
| Mosquitto 설치 | 공식 Mosquitto Windows 설치본으로 설치 |
| 브로커 실행 | `mosquitto -c web-controller\\mosquitto.windows.conf -v` |
| 웹 서버 | `py -m http.server 5173` |
| Wi-Fi IP 확인 | `ipconfig` 후 `IPv4 Address` 확인 |
| 휴대폰 웹 주소 예시 | `http://192.168.x.x:5173` |
| 방화벽 | Windows Defender Firewall에서 Python과 Mosquitto의 사설 네트워크 수신 연결 허용 |

Windows용 Mosquitto 설정 예시:

```conf
persistence false
allow_anonymous true

listener 1883 127.0.0.1
protocol mqtt

# 실제 Windows Wi-Fi IPv4로 교체
listener 9001 192.168.x.x
protocol websockets
```

### 사람이 해야 하는 부분

| 시점 | 사람이 확인/수행할 일 |
| --- | --- |
| Unreal 최초 실행 | C++ 프로젝트 변환·컴파일 승인 |
| 차량 준비 후 | Editor에서 ▶ Play 버튼 클릭 |
| 브로커 준비 | Homebrew 설치 승인: `brew install mosquitto` |
| 휴대폰 테스트 | Mac과 휴대폰이 같은 Wi-Fi인지 확인 |
| 휴대폰 접속 실패 | Mac 방화벽에서 Python 웹 서버 수신 연결 허용 |
| Wi-Fi 변경 후 | `ipconfig getifaddr en0`으로 Mac IP 재확인 |

## macOS 실행 순서

```bash
# 1. MQTT 브로커
/opt/homebrew/opt/mosquitto/sbin/mosquitto -c web-controller/mosquitto.local.conf -v

# 2. 웹 서버
cd web-controller
npm install
python3 -m http.server 5173
```

| 기기 | 웹 주소 | MQTT WebSocket 주소 |
| --- | --- | --- |
| Mac | `http://localhost:5173` | `ws://192.168.0.21:9001` |
| 같은 Wi-Fi 휴대폰 | `http://192.168.0.21:5173` | `ws://192.168.0.21:9001` |

## Windows 실행 순서

```bat
REM 1. Mosquitto 브로커
mosquitto -c web-controller\mosquitto.windows.conf -v

REM 2. 웹 서버
cd web-controller
npm install
py -m http.server 5173
```

Windows에서는 `ipconfig`로 확인한 Wi-Fi IPv4를 웹 URL과 MQTT WebSocket URL에 사용한다.

## 검증 체크리스트

1. Mosquitto가 1883과 9001 포트에서 실행된다.
2. 웹에서 `MQTT 연결됨` 상태가 표시된다.
3. Unreal Play를 시작한다.
4. Output Log에 `MQTT vehicle bridge subscribed to testvi/web/input`가 보인다.
5. 웹 키보드/터치 W/A/S/D로 차량이 움직인다.
6. Unreal Play 창의 WASD가 웹에 표시된다.
7. 웹을 닫거나 연결을 끊으면 0.35초 안에 차량이 멈춘다.

## 문제 해결

| 증상 | 점검 |
| --- | --- |
| 웹 MQTT 연결 실패 | 9001 포트, 브로커 실행, 페이지의 IP 확인 |
| 웹 입력으로 차량이 안 움직임 | Play 상태, Unreal Output Log의 구독 메시지, 토픽 이름 확인 |
| 휴대폰 페이지가 안 열림 | 동일 Wi-Fi, Mac 방화벽, `192.168.0.21:5173` 주소 확인 |
| Unreal WASD가 웹에 안 보임 | Play 창을 클릭해 입력 포커스를 준다 |
| 차량이 계속 움직임 | blur/pointercancel, 0.35초 웹 타임아웃 구현 확인 |

## 이후 확장

- 실제 로버 연결 시 테스트 토픽과 장비 토픽을 분리한다.
- 인증, TLS(`wss`/`mqtts`), 사용자별 토픽·권한을 추가한다.
- Wi-Fi IP 하드코딩 대신 mDNS 또는 고정 DHCP lease를 고려한다.
- 여러 웹 클라이언트 제어 시 소유권과 우선순위 정책을 만든다.
