# RTK(UM982) ROS 2 녹화 명세서

> 대상: UM982(WTRTK-982) RTK 수신 데이터를 ROS 2 로 발행·**녹화**하기 위한 이관/구성 명세.
> 구현체: `um982_driver` (C++ ROS 2 노드). 근거 문서: `rtk/UM982-명령어검토-및-바이너리포팅.md`.
> 검증: 국가 통합기준점 U0373/U0374 대비 수평 1.4~2.6cm (rtk/Log.md).

---

## 1. 개요 & 데이터 흐름

```
NTRIP(VRS,KGD2002) ──RTCM3──▶ UM982 ──BESTNAV(binary)+GGA(ascii)──▶ um982_driver
      ▲                                                                  │
      └──────────────── GGA(VRS 유지, 10s) ◀───────────────────────────┘
                                                                         │ GnssPVTSolnMsg (10Hz)
                                                                         ▼
                                                          /ublox_driver/receiver_pvt
                                                                         │
                                                                  ros2 bag record ──▶ .db3/.mcap
```

- 시리얼 포트는 **한 프로세스만 점유** → RTCM 주입과 BESTNAV 파싱이 **한 노드**(`um982_driver`)에 통합.
- 녹화는 이 노드가 발행하는 토픽을 `ros2 bag record` 로 담는다.

---

## 2. 이관 대상 & 워크스페이스 구성 (무엇을 어떻게 옮기나)

**옮길 패키지 2개** (그 외 의존 없음 — 시리얼=termios, NTRIP=POSIX 소켓):

| 패키지 | 역할 | 위치(원본) |
|---|---|---|
| `gnss_comm` | `GnssPVTSolnMsg`/`GnssTimeMsg` 메시지 정의 | `FAST-LIVO2-RTK-ROS2/thirdparty/gnss_comm` |
| `um982_driver` | 드라이버 노드(파싱+NTRIP+발행) | `FAST-LIVO2-RTK-ROS2/src/um982_driver` |

**녹화 전용 최소 워크스페이스** (전체 FAST-LIVO2 빌드 불필요):
```
fhd_rtk_ws/
└── src/
    ├── gnss_comm/       ← thirdparty/gnss_comm 복사
    └── um982_driver/    ← src/um982_driver 복사
```
```bash
cd fhd_rtk_ws
colcon build --packages-select gnss_comm um982_driver
source install/setup.bash
```
> ROS 2 Humble, C++17. `um982_driver` 는 `rclcpp`, `gnss_comm` 만 의존.

---

## 3. 사전 조건 — 수신기 설정 (필수)

드라이버 실행 전 UM982 가 아래를 **저장(SAVECONFIG)** 하고 있어야 한다. 리포 `rtk/config_all.py` 한 번 실행:

| 항목 | 값 | 이유 |
|---|---|---|
| `BESTNAVB COM3 0.1` | 10Hz 바이너리 | **파싱 소스** |
| `GPGGA COM3 1` | 1Hz | NTRIP VRS 업링크 |
| COM3 baud | `460800` | RTCM+로그 대역 |
| `MODE ROVER` | — | 로버 측위 |
| `CONFIG RTK RELIABILITY 3 1` | 기본 | 오확정 방지 |

> ⚠️ `FRESET` 시 baud 가 115200 으로 초기화됨 → `rtk/set_baud.py` 재실행 필요.

---

## 4. 토픽 & 메시지 명세

### 4.1 토픽

| 항목 | 값 |
|---|---|
| 토픽명 | `/ublox_driver/receiver_pvt` (파라미터 `gps_topic`) |
| 타입 | `gnss_comm/msg/GnssPVTSolnMsg` |
| 발행 주기 | BESTNAV 주기 = **10 Hz** (0.1s) |
| QoS | `KeepLast(2000)`, 기본 신뢰성(reliable) |
| 프레임/스탬프 | **메시지에 std_msgs/Header 없음.** 시간은 메시지 내부 `time.week/tow` |

### 4.2 `GnssPVTSolnMsg` 필드 (드라이버가 채우는 값)

| 필드 | 타입/단위 | 채움 | BESTNAV 소스 |
|---|---|---|---|
| `time.week` | uint32 (GPS week) | ✅ | 헤더 Wn |
| `time.tow` | float64 (초) | ✅ | 헤더 Ms÷1000 |
| `latitude` | float64 (deg) | ✅ | lat |
| `longitude` | float64 (deg) | ✅ | lon |
| `altitude` | float64 (m, **타원체고**) | ✅ | hgt(MSL)+undulation |
| `height_msl` | float64 (m) | ✅ | hgt |
| `h_acc` | float64 (m) | ✅ | √(latσ²+lonσ²) |
| `v_acc` | float64 (m) | ✅ | hgtσ |
| `vel_n/vel_e/vel_d` | float64 (m/s, NED) | ✅ | hor·cos(trk)/hor·sin(trk)/−vert |
| `vel_acc` | float64 (m/s) | ✅ | Horspd std |
| `num_sv` | uint8 | ✅ | #solnSVs |
| `fix_type` | uint8 (0=no,3=3D) | ✅ | pos_type |
| `valid_fix` | bool | ✅ | pos_type≠NONE |
| `diff_soln` | bool | ✅ | pos_type∉{NONE,SINGLE} |
| `carr_soln` | uint8 (0/1/2) | ✅ | 48/49/50→2, 32/33/34→1, else 0 |
| `p_dop` | float64 | ➖ 0 | BESTNAV 미제공(필요시 PVTSLN) |

> `carr_soln`: **2=RTK Fixed(NARROW_INT 등), 1=RTK Float, 0=단독/DGPS**.

---

## 5. 드라이버 파라미터 명세 (`config/um982.yaml`)

| 파라미터 | 기본값 | 설명 |
|---|---|---|
| `serial_port` | `/dev/ttyUSB0` | UM982 COM3(USB). 리눅스 `/dev/ttyUSB*` |
| `serial_baud` | `460800` | 수신기 저장값과 일치 |
| `gps_topic` | `/ublox_driver/receiver_pvt` | 발행 토픽(백엔드 `gps.gps_topic` 와 일치) |
| `use_ntrip` | `true` | false 면 RTCM 주입 안 함(외부 보정 시) |
| `ntrip_host/port/mountpoint` | `RTS1.ngii.go.kr` / `2101` / `VRS-RTCM34` | NTRIP 접속 |
| `ntrip_user/pass` | `""` | ⚠️ 리포 평문 커밋 지양 — yaml/CLI/환경변수로 주입 |
| `gga_send_period` | `10.0` | VRS 유지용 GGA 재전송 주기(s) |
| `publish_fixed_only` | `false` | true=RTK Fixed 해만 발행 |
| `inflate_cov_when_not_fixed` | `true` | fix 아님/코스팅 시 h_acc·v_acc 부풀림 |
| `max_corr_age` | `30.0` | diff_age 초과 시 코스팅 간주(공분산↑). 0=비활성 |

> NTRIP 끊겨도 UM982 는 `RTK TIMEOUT`(기본 600s) 동안 Fixed 를 코스팅 유지 → `max_corr_age` 로 신뢰 낮춤.

---

## 6. 시간 의미 (중요)

- 메시지에 Header/stamp 가 **없다.** 시간은 `time.week`(GPS week) + `time.tow`(주 내 초).
- UNIX 복원: `unix = week·604800 + tow + 315964800 − 18(leap)` (백엔드 `optimization.cpp::gpsHandler` 와 동일).
- 유효 조건: 헤더 TimeStatus=`FINE` & week>1 (fix 전에는 week=1/UNKNOWN).
- `ros2 bag` 은 **수신 시각(bag timestamp)** 을 별도로 기록하지만, **권위 시각은 메시지 내부 week/tow**. 센서와의 시각 동기는 이 값 기준.

---

## 7. 녹화 절차

### 7.1 실행
```bash
source install/setup.bash
ros2 launch um982_driver um982_driver.launch.py            # 또는 아래 run
# 자격증명을 CLI 로 주입:
ros2 run um982_driver um982_driver_node --ros-args \
    --params-file src/um982_driver/config/um982.yaml \
    -p ntrip_user:=<id> -p ntrip_pass:=<pw>
```
콘솔에 `RTK FIXED` 전환 로그가 뜨고, `ros2 topic hz /ublox_driver/receiver_pvt` ≈ 10Hz 확인.

### 7.2 GNSS 단독 녹화
```bash
ros2 bag record -o rtk_$(date +%F_%H%M) /ublox_driver/receiver_pvt
```

### 7.3 센서 + GNSS 동시 녹화 (FAST-LIVO2 후처리용)
```bash
ros2 bag record -o run_$(date +%F_%H%M) \
    /livox/lidar /livox/imu /left_camera/image \
    /ublox_driver/receiver_pvt \
    --storage mcap        # 없으면 sqlite3(기본)
```
> 초기 수십 초 **RTK Fixed + 충분한 이동** 후 녹화(백엔드 SVD 정합 전제). `rtk/UM982-녹화-및-연동-구현.md` §7 참조.

### 7.4 (권장) 원시 BESTNAV 동시 녹화 — 재처리 대비
현재 드라이버는 파싱 결과만 발행한다. 파싱 로직을 나중에 고쳐 재생성하려면 **원시 바이트도 함께** 남기는 것을 권장(별도 옵션 필요 — §10).

---

## 8. 녹화 데이터 포맷 & 재생

- 저장: ROS 2 bag (`sqlite3` `.db3` 기본, 또는 `mcap` `.mcap`).
- 확인: `ros2 bag info run_YYYY-MM-DD_HHMM` → 토픽/메시지 수/기간.
- 재생: `ros2 bag play run_YYYY-MM-DD_HHMM` → `/ublox_driver/receiver_pvt` 재발행.
- FAST-LIVO2 백엔드는 이 토픽을 구독해 첫 fix 를 ENU 원점으로 잡고 GTSAM 융합(`gps.gps_topic` 일치 필요).

---

## 9. 품질/필터 (녹화되는 데이터의 신뢰)

- 백엔드에 fix 품질 필터가 **없으므로** 드라이버 단에서 처리:
  - `publish_fixed_only=true` → RTK Fixed 해만 녹화(가장 보수적).
  - `inflate_cov_when_not_fixed=true` + `max_corr_age` → fix 아님/코스팅 시 `h_acc/v_acc` 를 키워 후처리 가중 약화.
- CRC(Unicore Appendix 1) 통과 프레임만 발행 → 손상 프레임 녹화 안 됨.

---

## 10. 확장(옵션) — 원시 스트림 동시 녹화

재처리·디버깅을 위해 원시 BESTNAV/GGA 바이트를 별도 토픽으로 녹화하려면 드라이버에
`std_msgs/msg/ByteMultiArray`(또는 String) 발행부를 추가하면 된다(예: `/um982/raw`).
필요 시 요청 → 드라이버에 `publish_raw` 파라미터로 추가.

---

## 11. 검증 체크리스트

- [ ] `rtk/config_all.py` 로 수신기 설정 저장(BESTNAVB/GPGGA, 460800, MODE ROVER)
- [ ] `colcon build --packages-select gnss_comm um982_driver` 성공
- [ ] `serial_port` / NTRIP 자격증명(config or CLI) 확인
- [ ] `ros2 topic hz /ublox_driver/receiver_pvt` ≈ 10Hz, 콘솔 `RTK FIXED`
- [ ] `ros2 topic echo` 로 lat/lon/altitude, time.week(>1)/tow, carr_soln=2 확인
- [ ] `ros2 bag record` → `ros2 bag info` 로 토픽·기간 확인
- [ ] (동시녹화) 센서 4종 + GNSS 토픽 모두 bag 에 존재
```
