Loading...
 
Skip to main content

Servidor de hora Stratum 1 (NTP con GPS) — NOC Fratec


Servidor de hora propio con GPS que da estrato 1 real (± cientos de nanosegundos), reemplazando la dependencia de servidores públicos de hora para la red de Fratec.


-

Ficha técnica

CampoValor
Nombrestratum1
IPv4200.59.16.12/28 (gw .16.33)
IPv62800:640:bb:1::12/64
UbicaciónNOC Curridabat
PlacaIntel DG41TY (LGA775)
CPUIntel Core2 Duo E7500 @ 2.93 GHz
RAM8 GB
SODebian 13 «trixie», kernel 6.12.116
Consumo~45–55 W (≈ ₡3.500–4.000/mes)
Receptor GPSu-blox GT-U7 (chip NEO-7M, GPS L1) + antena de parche
Interfaz GPSFTDI FT232RL genuino (0403:6001, serial A900D43Z) por USB
PPSpuerto paralelo (pps_parport, IRQ 5) + respaldo por DCD del FTDI (sin uso)
Servicioschrony 4.6.1, gpsd 3.25, sshd
PuertosUDP/123 (NTP), TCP/22 (SSH)
Nota sobre la placa: la DG41TY tiene LPT y no COM física, así que el PPS va por el puerto paralelo, que es el mejor camino disponible (~1–5 µs).
----
!!Arquitectura: qué viaja y qué no
Un receptor GPS entrega dos señales que hay que tratar por separado:
Señal|Qué es|Exactitud|¿Se estira lejos?

|NMEA|texto serial ($GPGGA, $GPZDA…)|±10–100 ms|sí, pero inútil solo para estrato 1
|PPS / 1PPS|pulso eléctrico en el segundo exacto|20–60 ns|no

El estrato 1 nace de que el kernel marca la hora en el flanco del PPS y usa el NMEA solo para saber cuál segundo es. Por eso el receptor va lo más cerca posible del cielo con su antena de parche, y lo que se estira son los 15 m de cable UTP que llevan NMEA y PPS hasta el servidor. No hay coaxil ni antena externa en este nodo.

Cableado

SeñalCamino
NMEAGPS TX → 15 m de UTP → FTDI RX → /dev/ttyUSB0 → gpsd
PPSGPS PPS → pin 10 (ACK) del DB25 → pps_parport → /dev/pps0
GNDGPS GND → pines 18–25 del DB25 y GND del FTDI
El pin 10 del DB25 es el décimo de la fila de arriba. El ACK es activo-bajo: con un pulso de 100 ms el flanco que cuenta el kernel cae al final del pulso, así que el timepulse del módulo debe ir activo-bajo o con ancho de 1 µs.
----
!!Configuración
!!!/etc/default/gpsd
Copy to clipboard
START_DAEMON="true" USBAUTO="true" DEVICES="/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_A900D43Z-if00-port0" GPSD_OPTIONS="-n -s 9600"
-n = leer sin esperar clientes. -s 9600 = fijar la velocidad. Se quitó -b (modo read-only) porque impedía que gpsctl y ubxtool le hablaran al receptor.
!!!/etc/chrony/chrony.conf (parte relevante)
Copy to clipboard
refclock SHM 0 refid NMEA offset 0.0 delay 0.2 precision 1e-3 refclock PPS /dev/pps0 refid PPS lock NMEA prefer precision 1e-7 server 200.59.16.61 iburst server 200.59.19.5 iburst makestep 1.0 3 driftfile /var/lib/chrony/chrony.drift rtcsync allow 192.168.11.0/24 allow 200.59.16.0/27 logdir /var/log/chrony log tracking statistics refclocks
lock NMEA es obligatorio: le dice a chrony que use el NMEA para saber cuál segundo es y el PPS para la hora fina. Sin eso el PPS queda inservible.
Los dos server van sin prefer: si el PPS se cae, el nodo baja a estrato 2 en vez de caer a estrato 16 y arrastrar a todos sus clientes.
----
!!Estado verificado
Copy to clipboard
$ chronyc tracking Reference ID : 50505300 (PPS) Stratum : 1 System time : 0.000000018 seconds fast of NTP time RMS offset : 0.000061142 seconds Frequency : 1.475 ppm fast Leap status : Normal $ chronyc sources #x NMEA 0 4 377 20 +142ms[ +142ms] +/- 101ms #* PPS 0 4 377 20 +169ns[ +289ns] +/- 146ns ^- 200.59.16.61 3 6 377 50 -795us +/- 97ms ^- 200.59.19.5 2 6 377 112 +1590us +/- 87ms
Métrica|Valor

|Estrato|1
|Desviación del reloj|18 ns
|PPS|+169 ns ± 146 ns
|RMS offset|61 µs
|Fix GPS|3D, 12 satélites usados
|Posición|9.92432 N, 84.03598 W, 1238 m
|PPS del paralelo|exactamente 1/s, jitter entre pulsos ~0.3 µs

La x en NMEA significa falseticker: el NMEA llega 142 ms tarde porque el enlace serie está saturado (ver más abajo). Eso no afecta la hora servida: chrony lo usa solo para el lock y marca la hora con el PPS. Mientras PPS esté en #* y el estrato sea 1, el reloj es correcto.


-

Verificación y diagnóstico

¿Está el PPS vivo?

Copy to clipboard
sudo ppstest /dev/pps0 cat /sys/class/pps/pps0/name # debe decir: pps_parport cat /sys/class/pps/pps0/assert # el contador debe subir 1 por segundo grep parport0 /proc/interrupts # el IRQ cuenta los pulsos desde el arranque

¿Tiene fix el GPS?

Copy to clipboard
gpspipe -w -n 5 | grep -E '"TPV"|"SKY"' gpspipe -r | head # sentencias $GP... crudas


Si ppstest no da eventos, revisar pps0/assert antes de culpar al cableado: pps0/assert también marca 0 cuando el módulo no tiene fix.

¿Qué segmento SHM usa gpsd?


No deducirlo de la versión. El índice cambia según cómo se compiló gpsd. Medirlo leyendo los segmentos:

Copy to clipboard
python3 - <<'PY' import ctypes, struct libc = ctypes.CDLL("libc.so.6"); libc.shmat.restype = ctypes.c_void_p for n in range(8): shmid = libc.shmget(0x4e545030 + n, 96, 0) if shmid < 0: continue addr = libc.shmat(shmid, None, 0o10000) d = ctypes.string_at(addr, 96) mode, count, cts, rts = struct.unpack("iiii", d[0:16]) print("SHM %d: mode=%d count=%d clockTime=%d" % (n, mode, count, cts)) libc.shmdt(ctypes.c_void_p(addr)) PY


El que escribe gpsd es el que tiene mode=1 y count subiendo. En este host es el SHM 0.


-

Lecciones aprendidas (importante)

1. El índice SHM hay que medirlo, no adivinarlo


Se asumió que gpsd 3.25 escribía en SHM 2 y se cambió el refclock. Resultado: chrony quedó desconectado del NMEA que sí estaba llegando y el nodo perdió el estrato 1 durante varios minutos. Se revirtió a SHM 0, que es el correcto acá. Regla: leer mode/count de los segmentos antes de tocar el refclock.

2. No compensar el NMEA con offset


Probado y descartado: poner offset 0.135 alineó el NMEA con el borde del segundo, chrony perdió el lock, se saltó un segundo y el nodo cayó a estrato 0.

Lo correcto es atacar la causa: el enlace serie. A 9600 con 2 Hz y 7 sentencias por ciclo el enlace va al 94 % (900 B/s de 960) y el NMEA se retrasa 142 ms. Subiendo a 38400 baja al ~22 %, el retraso a ~83 ms y la x desaparece sola.

La x es jitter, no offset medio. Un NMEA con 83 ms constante se acepta (#-); el mismo con ±1 ms de variación se marca falseticker. Compensar el offset mueve el promedio, no la dispersión — y encima rompe el lock.

3. El retardo del cable es ruido


Los ~76 ns de los 15 m de UTP quedan sepultados por la incertidumbre del propio PPS (±146 ns). No se corrige: no se puede afinar un sesgo menor que la barra de error de la fuente. Además el offset de un refclock en chrony se resta, así que el signo es fácil de equivocar y la ganancia es nula.

4. -b en gpsd bloquea toda comunicación con el receptor


-b (modo read-only) hace que gpsd no escriba al dispositivo. Eso está bien para un nodo en producción, pero rompe gpsctl -s y ubxtool con "Attempted to write to a read-only device". Para cambiar la config del receptor hay que quitarlo temporalmente.

5. Con u-blox 7 (protocolo 14) las herramientas modernas mienten


El GT-U7 lleva un chip u-blox 7, que habla la versión 14 del protocolo. Con ese receptor:

  • u-center 26.x muestra valores que no son los del chip: decía Measurement Period 1000 ms cuando el módulo estaba en 2 Hz, y GSV = 0 en UART1 cuando la sentencia seguía saliendo. Las columnas de puerto no mapean al puerto real.
  • ubxtool -d NMEA,GSV (probado con y sin -P 14.00) no aplicó el cambio: el conteo de sentencias quedó idéntico.
  • Mandar los frames UBX a mano por el UART tampoco funcionó (sin ACK, sin datos de vuelta).


La palanca que sí funciona es una sola: gpsctl -s 38400. Cambia la velocidad del puerto del receptor y gpsd reabre a la nueva velocidad sin reiniciar nada.

Ojo: el cambio queda en RAM. Si el módulo pierde la pila de respaldo vuelve a 9600 y la x reaparece; gpsd lo autodetecta igual, pero hay que repetir el comando.

6. El PPS por paralelo es el camino bueno en x86

Copy to clipboard
parport0: PC-style at 0x378 (0x778), irq 5 [PCSPP,TRISTATE,EPP] pps_parport: attached to parport0 5: 0 703 IO-APIC 5-edge parport0


IRQ real, sin transceptor que invierta, /dev/pps0 aparece solo (sin ldattach ni reglas udev). Da 1–5 µs: el mejor camino en un servidor x86 sin GPIO.

Dos falsos negativos que hacen creer que el host no sirve:

  • /proc/parport no existe en kernel 6.12, ni como root. La fuente autoritativa es dmesg y /proc/interrupts.
  • ldattach "no existe" es un falso negativo de PATH: vive en /usr/sbin, que no está en el PATH de un shell no-root.

7. Un nodo de hora que deja de sincronizar no avisa solo


Si se va la antena o el PPS, chrony baja a estrato 2 silenciosamente. Pendiente: watchdog que alerte si stratum ≥ 2, Leap status ≠ Normal, o si se congela el contador de pps0/assert.


-

Pendientes

  • (x) Nodo en estrato 1 verificado
  • ( ) Watchdog silencioso (estrato, leap alarm, PPS congelado)
  • ( ) Probar desde un tercer host que sirve hora correcta a la /27
  • ( ) Apuntar ntp1–ntp4 a este nodo y eliminar la dependencia de servidores públicos
  • ( ) Guardar la config del receptor en la EEPROM (CFG-CFG) para que los 38400 sobrevivan a un corte de la pila de respaldo
  • ( ) Publicar la IP al pool (solo después de verificar PPS estable; una entrada por equipo)


-

Referencias

  • Skill interna: gps-stratum1-time-server (arquitectura, presupuesto de RF, recetas de PPS)
  • Skill interna: ntp-pool-operations (membresía, scores, migración de IPs)