Files
fhd_rtk_ws/docs/NTRIP-단일기준국-전환-조사.md
T
Dongubak 11fa444ce7 upload
2026-08-22 02:54:26 +09:00

13 KiB

VRS(가상 기지국) → 단일기준국(실제 상시관측소) 전환

배경: GNSS 운용 담당자로부터 "가상 기지국(VRS) 말고 주변의 진짜 기지국을 우선으로 잡아라"는 조언을 받았고, 실제로 RTK Fixed 도달 자체가 안 되는 상황이라 VRS 사용을 중단하기로 결정. 조사·구현일 2026-08-22. 캐스터는 직접 조회해 확인했고, 코드는 로컬에서 컴파일·단위검증까지 마쳤다. 하드웨어 검증은 §7 절차로 로봇에서 해야 한다.


1. 결론 먼저

UM982 모듈 설정 문제가 아니다. 수신기에는 "어느 기지국을 잡을지" 고르는 명령이 없다 (Unicore N4 매뉴얼 §4.6 CONFIG RTK 확인). 기지국 선택은 전적으로 NTRIP 마운트포인트가 결정한다.

그리고 조사 중에 Fixed 가 안 되는 유력한 원인을 따로 찾았다 — §3. 마운트포인트를 바꿔야 하는 이유가 "기준 프레임 일관성" 뿐만이 아니게 됐다.


2. Fixed 실패의 유력 원인 — VRS 스트림과 UM982 의 위성군 불일치

2.1 두 자료를 대조하면 나온다

(A) VRS 스트림에 실제로 들어있는 것진단기록 §2.5 에서 파이썬으로 직접 접속해 파싱한 결과:

1005/1007          기준국 좌표/안테나
1030~1033          네트워크 RTK 보조
1075 / 1085 / 1095 / 1115    GPS / GLONASS / Galileo / QZSS  MSM5
※ 북두(BeiDou) 보정 메시지는 이 마운트포인트에 없음

(B) UM982 가 지원한다고 매뉴얼에 적힌 것 — Appendix 2 "Supported RTCM V3 messages":

위성군 매뉴얼 기재 VRS 가 보내는 것 판정
GPS 1001~1004, 1074/1075 1075 쓸 수 있음
GLONASS 1009~1012, 1084/1085 1085 쓸 수 있음
BeiDou 1104, 1123~1127 없음 지원하는데 안 옴
Galileo 1091~1097 없음 (1094 만 base 출력 예시에 등장) 1095 올 수 있는데 못 씀
QZSS 1111~1117 아예 없음 1115 올 수 있는데 못 씀

2.2 무슨 뜻인가

VRS 는 4개 위성군 보정을 보내지만, 그중 UM982 가 RTK 에 실제로 쓸 수 있는 건 GPS 와 GLONASS 둘뿐일 가능성이 높다. 동시에 UM982 가 잘 지원하는 BeiDou 는 이 VRS 가 아예 안 보낸다 — 한국에서 북두는 가시위성이 많은데 통째로 놓치는 셈이다.

진단기록에서 "위성 31개 잡히는데 Fixed 가 안 된다"고 했던 것과 앞뒤가 맞는다. 수신기가 추적하는 위성 수(nSV)와, 기지국 보정이 있어서 RTK 모호성 해석에 실제로 기여하는 위성 수는 다르다. CONFIG SIGNALGROUP 4 5 로 5개 위성군을 추적해도, 보정이 안 오거나 못 읽는 위성군은 RTK 에 기여하지 못한다.

⚠️ 이건 가설이다. 매뉴얼 Appendix 2 가 불완전할 가능성도 있다(1094 가 다른 절에는 나오는 걸 보면 목록 관리가 엉성하다). §7.1 로 확정할 것.

2.3 그래서 기준국 선택 기준이 바뀐다

소스테이블의 위성군 문자열(GPS+GLO+GAL+BDS+QZS)이 아니라 실제로 오는 메시지 ID 로 판단해야 한다. BeiDou MSM(1124/1125)을 보내는 관측소가 진짜 이득이고, Galileo/QZSS 는 있으나 마나다.


3. VRS 를 그만 써야 하는 나머지 이유

3.1 기준 프레임이 움직인다

VRS 는 GGA 로 보고한 위치 근처에 가상 기지국을 만들고, 로버가 임계거리를 넘으면 가상 기지국을 새 위치로 재생성한다. 그때마다 기준점이 바뀌어 해에 불연속이 생기고 RTK 재초기화가 일어날 수 있다. FAST-LIVO2-RTK 백엔드는 GNSS 해를 GTSAM 팩터로 넣으므로, 궤적 도중의 기준 프레임 변경은 그대로 제약조건 불일치가 된다. 한 스캔 세션 동안 기준점은 고정돼야 한다.

3.2 사후처리(PPK)가 불가능하다

VRS 스트림은 서버가 그 순간 합성한 것이라 재현이 안 된다. 실제 관측소를 쓰면 gnssdata.or.kr 에서 해당 시간대 RINEX 를 받아 재처리할 수 있다.

3.3 대가 — 거리 의존성

단일기준국은 베이스라인이 멀수록 나빠진다(대략 1km당 1mm + 전리층 비상관). 실무상 10~20km 이내가 좋고, 30km 넘어가면 Fixed 가 눈에 띄게 어려워진다. → 드라이버가 베이스라인을 로깅하고 임계 초과 시 경고하도록 했다(§5).


4. 캐스터 실측 조사

tools/ntrip_probe.py table 로 소스테이블을 직접 받아 확인(조회는 인증 불필요).

캐스터 서버 결과
RTS1.ngii.go.kr:2101 Trimble Pivot 5.2 STR 5개 전부 solution=1(네트워크). VRS-RTCM23/31/34, VRS-CMRx, RTK-RTCM32
RTS2.ngii.go.kr:2101 Geo++ GNSMART FKP-RTCM31, VRS-RTCM31/32, SSR-SSRG — 전부 네트워크
gnssdata.or.kr:2101 NTRIP Caster 1.1 STR 562개 전부 solution=0(단일기준국), nmea=0, 고유 관측소 176곳
gnss.eseoul.go.kr:2101 Trimble Pivot 4.3 VRS 외에 서울권 관측소별 solution=0 스트림 제공 (대안)

RTS1/RTS2 에는 단일기준국이 아예 없다. 진짜 기준국은 GNSS 데이터 통합센터에 있다. 마운트포인트 명명 = <4자리 관측소코드>-<포맷> (예: DBON-RTCM32). 포맷은 -RTCM32 를 쓴다.

RTS1 의 RTK-RTCM32 는 이름상 단일기준국 서비스일 수 있으나 소스테이블은 네트워크로 신고한다. 표기는 못 믿는다 — VRS-RTCM34 도 1004,1014/1015/1016 을 신고하지만 실제로는 1030~1033, MSM5 가 온다. 계정을 그대로 쓸 수 있어 제일 간단한 선택지이므로 §7.2 로 정체를 확인할 가치가 있다.

⚠️ 인증: gnssdata.or.kr 은 무인증 시 401. RTS1 계정(hyuk6578)이 여기서도 되는지 미확인 이고, 이게 최우선 확인 항목이다. 안 되면 별도 가입 필요(위치기준과 031-210-2655).


5. 구현 완료 내역

5.1 설정 — config/um982.yaml

ntrip_host: "gnssdata.or.kr"
ntrip_mountpoint: "AUTO"        # 최근접 단일기준국 자동 선택 (또는 "DBON-RTCM32" 처럼 고정)
ntrip_auto_format: "RTCM 3.2"
max_baseline_km: 30.0
gga_send_period: 0.0            # 단일기준국은 GGA 업링크 불필요

수정 후 반드시 리빌드. install/ 은 복사본이라 리빌드 없이는 반영 안 된다 (진단기록 §2.3 에서 이것 때문에 NTRIP 이 빈 계정으로 붙던 전례). colcon build --packages-select um982_driver 또는 --symlink-install.

5.2 드라이버 — um982_driver_node.cpp

① AUTO 최근접 기준국 선택 소스테이블을 받아 solution=0 인 STR 만 후보로 두고 현재 위치에서 거리순 정렬, 가장 가까운 곳에 붙는다. 접속 실패 시 다음 후보로 자동 전환(관측소 점검/장애 대비, 최대 5곳). "주변에 있는 진짜 기지국을 우선으로 잡는다"를 그대로 구현한 것.

② GGA 대기 제거 기존에는 첫 유효 GGA 가 나올 때까지 NTRIP 접속을 막았다. VRS 는 그래야 하지만 단일기준국은 nmea=0 이라 불필요하고, 이 대기 때문에 수신기가 자력으로 fix 를 얻기 전까지 RTCM 이 한 바이트도 안 들어갔다. 조건이 나쁜 곳에서 콜드스타트할 때 수렴이 늦어지는 원인. 이제 gga_send_period <= 0 이면 즉시 접속한다(AUTO 는 위치가 필요해 첫 GGA 까지만 기다림).

③ RTCM 스니퍼 — 캐스터가 보낸 것 주입 경로에서 RTCM3 프레임을 뜯어 30초마다 요약한다:

  • 메시지 종류별 카운트
  • 보정이 오는 위성군
  • 네트워크 RTK 메시지(1014/1015/1016/1030~1035) 발견 시 경고 → 단일기준국이 아님
  • 매뉴얼 미기재 메시지 발견 시 경고 (§2 의 Galileo/QZSS 문제를 런타임에 바로 드러냄)
  • 1005/1006 에서 기지국 ID·좌표·베이스라인 km 를 로깅, 좌표가 움직이면 VRS 로 판정해 경고

④ RTCMSTATUS — 수신기가 받아들인 것 기동 시 RTCMSTATUSA ONCHANGED 를 RAM 에만 설정(SAVECONFIG 안 함)하고, 수신기가 뱉는 #RTCMSTATUSA 를 파싱해 로깅한다. 매뉴얼 §7.3.56 기준 <MsgID>,<MsgNum>,<BaseID>,<SatsNum>,<L1~L6>. ③과 ④를 대조하면 "캐스터는 보냈는데 수신기가 안 쓴" 위성군이 그대로 드러난다 — §2 가설의 결정적 검증 수단.

⑤ 접속 거부 사유 로깅 401(계정)과 404(마운트포인트 오타/미개방)를 구분해 찍는다.

5.3 발견해 고친 버그 — RTCM 파서 스톨

프레임 추출에서 0xD3 만 보고 동기를 잡으면, 관측치 페이로드 안의 우연한 0xD3 을 프레임 시작으로 착각한다. 그 가짜 길이(최대 1023B)만큼 데이터가 더 쌓일 때까지 파서가 멈춘다. RTCM3 는 길이 상위 바이트의 reserved 6bit 가 항상 0 이므로 이걸로 걸러내도록 수정 ((buf[j+1] & 0xFC) != 0 이면 오동기). 파이썬 도구에도 동일 수정 적용.

5.4 로컬 검증

ROS 없는 맥에서 스텁 헤더로 -Wall -Wextra 컴파일 통과. RTCM 헬퍼는 단위테스트로 검증: 1005/1006 파싱·ECEF→위경도 왕복(1e-7도), 안테나고, 베이스라인 거리(파이썬 참조구현과 일치), 프레임 추출(연접/1바이트 분할/선행 쓰레기/가짜 0xD3/CRC 깨짐 복구), 메시지 분류 함수 — 전부 통과. ※ 이건 로컬 검증이고, 실제 하드웨어·실제 스트림 검증은 안 됐다(§7).


6. 조사 도구 — tools/ntrip_probe.py

표준 라이브러리만 쓰는 단독 실행 스크립트. 드라이버를 안 건드리고 캐스터를 조사할 때.

python3 ntrip_probe.py table gnssdata.or.kr 2101 --filter DBON
python3 ntrip_probe.py near  gnssdata.or.kr 2101 <위도> <경도> --format RTCM3.2
python3 ntrip_probe.py sniff gnssdata.or.kr 2101 DBON-RTCM32 --user <ID> --pass <PW> --secs 60

sniff 판정 기준:

관찰 의미
1014/1015/1016/1030~1035 존재 네트워크 해 (MAC/FKP/VRS)
1005/1006 좌표가 관측 중 이동 VRS
네트워크 메시지 없음 + 1005/1006 고정 단일기준국
1075/1085/1095/1115/1125 구성 어느 위성군 보정이 오는지 — 1124/1125(BDS) 유무가 핵심

7. 로봇에서 할 검증 (순서대로)

7.1 §2 가설 확정 — 먼저 이것부터

아직 VRS 인 상태로 드라이버를 새로 빌드해 띄우고 로그를 본다.

[um982_driver]: RTCM 누적 ... | 메시지: 1005x.. 1030x.. 1075x.. 1085x.. 1095x.. 1115x..
[um982_driver]:   ⚠️ 매뉴얼 미기재 메시지: 1095(Galileo) 1115(QZSS) — 이 위성군은 RTK 에 안 쓰일 수 있다
[um982_driver]: 수신기 RTCM 수용: 1075,1234,0,9,...

수신기 RTCM 수용: 줄에 1095/1115 가 안 나오면 §2 가설이 맞다. 나오면 가설은 틀렸고 원인을 다시 찾아야 한다. 어느 쪽이든 이 로그 한 번이면 결판난다.

7.2 계정 확인

python3 tools/ntrip_probe.py sniff gnssdata.or.kr 2101 DBON-RTCM32 --user hyuk6578 --pass <PW> --secs 60

401 이면 별도 가입 필요. 그동안의 대안으로 RTS1 RTK-RTCM32 정체도 확인:

python3 tools/ntrip_probe.py sniff RTS1.ngii.go.kr 2101 RTK-RTCM32 \
    --user hyuk6578 --pass <PW> --gga-from-latlon <위도> <경도> --secs 60

7.3 관측소 선정

near 로 작업 위치 기준 후보를 뽑고, sniffBDS(1124/1125)를 보내는 곳을 고른다. 소스테이블 좌표는 대부분 소수점 2자리(약 1km)이고 4개 그룹은 여러 관측소가 같은 좌표로 잘못 적혀 있으니(GGEO/JJHG/SGWI/YNGU 가 모두 38.29,128.14), 실제 거리는 sniff 의 1005/1006 으로 확인.

7.4 전환 후 A/B

난간 위치(진단기록에서 Fixed 나왔던 조건)에서 VRS vs 단일기준국으로 Fixed 수렴시간·h_acc 비교. 안테나 위치가 여전히 지배적 변수이므로(진단기록 §3) 같은 위치에서 비교해야 의미가 있다.


8. 미확정 사항

  • §2 가설 — 수신기가 Galileo/QZSS MSM 을 실제로 버리는가 (7.1 로 확정)
  • RTS1 계정이 gnssdata.or.kr 에서 통하는지
  • RTS1 RTK-RTCM32 가 단일기준국인지
  • 작업 위치 기준 최근접 관측소 확정
  • AUTO 선택 로직의 실스트림 동작 (로컬 컴파일·단위테스트만 함)

9. 관련 파일

파일 내용
tools/ntrip_probe.py 소스테이블 조회 / 최근접 기준국 / 스트림 VRS 판정
src/um982_driver/config/um982.yaml 캐스터·마운트포인트·GGA 설정
src/um982_driver/src/um982_driver_node.cpp AUTO 선택, RTCM 스니퍼, RTCMSTATUS 로깅
docs/UM982-RTK-Fixed-진단기록_2026-07-30.md VRS 스트림 실제 구성(§2.5), 안테나 위치가 지배적(§3), 리빌드 함정(§2.3)
docs/Unicore Reference Commands Manual ... R1.4.pdf Appendix 2 지원 RTCM 목록, §4.6 CONFIG RTK, §7.3.56 RTCMSTATUS