Un second Stratum 1 GNSS avec Galileo : notre journal
Depuis le 27 octobre 2026, nous avons deux serveurs Stratum 1 côte à côte : ntp-0 (renommé ntp-0-gps le 27), le Raspberry Pi
GPS réinstallé le 23 octobre, et ntp-0-gnss, un second
Raspberry Pi avec un récepteur u-blox qui reçoit le GPS, le GLONASS et Galileo, le système européen. Le récepteur prévu
pour lui n'avait pas fonctionné le 23 ; remonté correctement le 27, il a répondu du premier coup.
Comme pour la réinstallation, cette page reprend le journal tenu pendant l'intervention : commandes, sorties et mesures, heures en UTC. Les coordonnées GPS, les adresses internes et les secrets sont retirés.
Le matériel
| Élément | ntp-0 | ntp-0-gnss |
|---|---|---|
| Carte | Raspberry Pi 3 Model B Rev 1.2, Raspberry Pi OS Lite Trixie (Debian 13), noyau 6.18.50 | |
| Récepteur | carte 52Pi, Quectel L80 (MediaTek MT3339) | HAT Uputronics GPS/RTC, u-blox MAX-M8Q |
| Constellations | GPS | GPS, Galileo, GLONASS (+ SBAS) |
| Liaison NMEA | 9600 bauds | 115200 bauds |
| PPS | GPIO4 | GPIO18 |
| Horloge sauvegardée | aucune (fake-hwclock) | RTC RV-3028 sur le HAT |
| Antenne | OTB extérieure | antenne distincte, provisoire |
| Refid annoncé | GPS | GNSS |
Les deux machines n'ont en commun que le modèle de Raspberry Pi et le réseau : récepteurs de marques et de générations différentes, antennes séparées. Une panne de récepteur ou d'antenne sur l'une ne touche pas l'autre ; elles partagent en revanche la baie, l'alimentation, le réseau et la box.
Premiers relevés : I²C, série, PPS
Les deux lignes de l'I²C sont au repos à l'état haut, et le bus répond instantanément avec ses deux composants : le récepteur u-blox en 0x42 et la RTC RV-3028 en 0x52.
$ pinctrl get 3,15,18
3: a0 -- | hi // GPIO3 = SCL1
15: a0 -- | hi // GPIO15 = RXD0
18: ip -- | lo // GPIO18 = input
$ i2cdetect -y 1
40: -- -- 42 -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- 52 -- -- -- -- -- -- -- -- -- -- -- -- --
À 9600 bauds, le débit du L80 de ntp-0, la liaison série ne donne que des caractères illisibles : ce module émet à 115200 bauds. Au bon débit, les trames arrivent, avec un fix 3D corrigé par SBAS sur 12 satellites (GPS et GLONASS) et une dilution de précision horizontale de 0,90. Le PPS arrive aussi, une impulsion par seconde, lu par le pilote noyau :
$ ppstest /dev/pps0
source 0 - assert 1793115109.000209308, sequence: 304
source 0 - assert 1793115110.000210946, sequence: 305
source 0 - assert 1793115111.000211714, sequence: 306
Le firmware, lu avec ubxtool :
$ ubxtool -f /dev/serial0 -s 115200 -p MON-VER
swVersion ROM CORE 3.01 (107888)
extension FWVER=SPG 3.01
extension PROTVER=18.00
extension GPS;GLO;GAL;BDS
ROM CORE : le firmware est en mémoire morte, et le module n'a pas de mémoire flash. Sa configuration
ne survit à une coupure que dans la mémoire que maintiennent les deux piles de sauvegarde du HAT. C'est ce qui va compter pour
Galileo.
Galileo
Galileo est le système de positionnement de l'Union européenne. Reçu en plus du GPS et du GLONASS, il apporte plus de satellites, donc un fix plus robuste, et surtout une heure qui ne dépend plus d'un seul système : si l'un se dégrade, le récepteur garde les autres. Le M8 en reçoit jusqu'à trois à la fois. En sortie d'usine, le nôtre recevait le GPS, le GLONASS, le SBAS et le QZSS, mais pas Galileo :
$ ubxtool -P 18 -p CFG-GNSS
gnssId 0 ... GPS L1C/A enabled
gnssId 2 ... Galileo E1
gnssId 6 ... GLONASS L1 enabled
$ ubxtool -P 18 -e GALILEO
UBX-ACK-ACK:
ACK to Class x06 (CFG) ID x3e (GNSS)
$ ubxtool -P 18 -p CFG-GNSS
gnssId 2 ... Galileo E1 enabled
Le module accepte le réglage. Mais au déménagement du Pi, quelques heures plus tard, une coupure de courant l'a
effacé : Galileo était de nouveau désactivé, et le récepteur ne voyait aucun satellite Galileo. Le réglage
n'avait pas été enregistré dans la mémoire sauvegardée. Plutôt que de dépendre d'elle, un petit service,
gnss-constellations, réactive Galileo à chaque démarrage s'il ne l'est pas, et se relance si le
récepteur n'est pas encore prêt. Vérifié deux fois, après un redémarrage puis après une coupure de courant :
Galileo réactivé, et 5 satellites Galileo utilisés dans la solution.
chrony : le même schéma que ntp-0
La configuration reprend celle de ntp-0, décrite dans le journal
de la réinstallation : PPS numéroté par le NMEA (lock NMEA), tenu en noselect au
démarrage par un verrou logique tant que trois sources NTS n'ont pas confirmé la seconde NMEA, et serveurs de notre
parc vus en strate 8 au moins. ntp-0 et ntp-0-gnss s'interrogent l'un l'autre en direct sur le réseau local :
chacun est témoin de l'autre.
# /etc/chrony/conf.d/refclock.conf
refclock SHM 0 refid NMEA noselect poll 4 offset 0.05 precision 1e-1 delay 0.2
refclock PPS /dev/pps0 lock NMEA refid GNSS poll 4 noselect
Le retard de la trame NMEA sur la seconde qu'elle annonce est d'environ 56 ms à 115200 bauds, contre 160 ms
pour le L80 à 9600 bauds : offset 0.05 le compense. Une fois le verrou levé, vers 15 h 50 :
$ chronyc sources
#? NMEA 0 4 377 17 -45ms[ -45ms] +/- 200ms
#* GNSS 0 4 377 16 +4554ns[ +225us] +/- 572ns
Le NMEA à −45 ms est celui de la première configuration, avec un offset 0.1 : c'est cette mesure qui a
fixé la valeur de 0.05. Après correction, il est à quelques millisecondes.
Le déménagement, une coupure de courant
Pour rejoindre ntp-0 dans la baie, le Pi a été éteint, débranché, déplacé et rebranché. Trois constats :
- Le PPS est revenu quelques secondes après le retour du courant. Les piles de sauvegarde du HAT ont gardé les données de navigation, et le récepteur a fait un démarrage à chaud. ntp-0, avec son L80, met environ une minute.
- Galileo a été perdu, comme décrit plus haut.
- L'horloge du Pi est repartie avec 2341 s de retard, recalée ensuite par le réseau. La RTC n'a pas été relue au démarrage (voir plus bas).
Le verrou logique, lui, a fait son travail : pendant ces minutes, le Pi s'est calé sur ntp-0, en Stratum 2, sans jamais servir le PPS avant confirmation.
La RTC : présente, mais pas sauvegardée
Le HAT porte une horloge sauvegardée, une RV-3028, et le pilote la voit dès qu'on ajoute
dtoverlay=i2c-rtc,rv3028 au config.txt : /dev/rtc0 apparaît, et
rtcsync, déjà présent dans la configuration chrony de Debian, la tient à l'heure. Pourtant, à chaque
démarrage, le Pi est reparti avec une heure fausse : 889 s de retard au premier, 2341 s après le
déménagement, 7708 s après une coupure d'une minute, rattrapés à chaque fois par chrony en un seul pas.
Une fausse piste d'abord
Au démarrage, le noyau écrivait :
kernel: rtc-rv3028 1-0052: hctosys: unable to read the hardware clock
Nous l'avons lu comme un problème d'ordre : le pilote, chargé par l'overlay, arriverait trop tard pour que le
noyau relise la RTC. Nous avons donc ajouté une unité qui lance hwclock --hctosys avant chrony (sous
Debian 13, hwclock est dans le paquet util-linux-extra). Au démarrage suivant, elle a
attendu /dev/rtc0 90 s puis abandonné, en retardant d'autant chrony et gpsd : udev ne signale pas
les RTC à systemd. Une règle d'une ligne a fait disparaître l'attente :
# /etc/udev/rules.d/70-rtc-systemd.rules
SUBSYSTEM=="rtc", KERNEL=="rtc0", TAG+="systemd"
Mais hwclock échouait à son tour, comme le noyau : la RTC elle-même refusait d'être lue.
hwclock: ioctl(RTC_RD_NAME) to /dev/rtc0 to read the time failed: Invalid argument
La vraie cause : la bascule sur pile, désactivée
Lu par l'interface du noyau (ioctl RTC_PARAM_GET, en lecture seule), le mode de bascule de la RV-3028 était
celui de la sortie d'usine :
BACKUP_SWITCH_MODE 0 → RTC_BSM_DISABLED
La RTC ne se servait donc jamais des piles du HAT. Privée d'alimentation, elle perd son heure et lève son indicateur
de coupure, et le pilote refuse de la lire tant qu'on ne l'a pas remise à l'heure. Quelques minutes après chaque
démarrage, rtcsync la réécrivait, et elle redevenait lisible : c'est ce qui rendait le défaut si
trompeur. Le correctif tient en un paramètre de l'overlay, qui demande le mode « level switching » :
# /boot/firmware/config.txt
dtoverlay=i2c-rtc,rv3028,backup-switchover-mode=3
Au redémarrage suivant, le pilote rapporte le mode 2, et le noyau relit lui-même la RTC dès son enregistrement, sans aucune unité ; chrony démarre sans écart à rattraper :
The RTC parameter 0x2 is set to 0x2.
kernel: rtc-rv3028 1-0052: setting system clock to 2026-10-27T17:59:20 UTC
Le message du début ne venait donc pas d'un pilote chargé trop tard : le noyau refusait une RTC invalide. L'unité
hwclock est devenue redondante ; elle ne gêne pas. Deux précautions : ne pas activer le chargeur
de la RTC avec des piles qui ne se rechargent pas, et ne pas confondre les numérotations, puisque le même mode vaut 3
pour l'overlay, qui reprend les bits de la puce, et 2 pour le noyau (RTC_BSM_LEVEL).
Ce redémarrage n'avait pas coupé le courant. La preuve est venue d'une vraie coupure, d'environ une minute : la RTC a tenu sur ses piles, le noyau l'a relue environ 12 s après le démarrage, et chrony n'a eu aucun écart à rattraper. Le PPS, lui, est revenu en 4 s.
# après la coupure (bascule par niveau)
kernel: rtc-rv3028 1-0052: setting system clock to 2026-10-27T18:04:40 UTC
$ chronyc tracking
System time : 0.000000000 seconds slow of NTP time
| Après une coupure | Bascule désactivée | Bascule par niveau |
|---|---|---|
| Noyau | hctosys: unable to read | setting system clock to … |
hwclock | Invalid argument | sans erreur |
| Écart rattrapé par chrony | 7708 s | aucun |
Vu de l'extérieur
Au premier test, ntp-0-gnss n'était joignable de l'extérieur qu'en IPv6 : l'unique adresse IPv4 publique du site servait encore ntp-0-gps.
Depuis un de nos serveurs à Paris, une requête NTP ordinaire reçoit la réponse d'un Stratum 1 de refid
GNSS, et chronyd -Q mesure un écart de 0,18 ms. Le soir même, le NTS était actif : un
certificat commun aux deux machines, émis le 27, porte leurs noms publics et locaux, et une heure authentifiée
s'obtient par le nom ntp-0-gnss-hq.rdem-systems.com, à 0,15 ms près depuis Paris :
$ chronyd -Q -6 'server ntp-0-gnss-hq.rdem-systems.com iburst nts'
System clock wrong by -0.000145 seconds (ignored)
Dans les sources du parc
Depuis le 27 octobre au soir, les serveurs Stratum 2 de notre parc interrogent ntp-0-gnss à côté de ntp-0-gps,
en NTS, avec des sessions établies vers les deux machines. Le même soir, l'unique adresse IPv4 publique du site,
celle de ntp-0-hq, a été redirigée vers ntp-0-gnss. Sur l'un d'eux, à Paris, il était déjà la
source retenue :
$ chronyc -n sources
^* 2a01:e0a:808:97c4::123 1 10 317 805 +290us[ +398us] +/- 1970us
^- 2a01:e0a:808:97c4::123:123 1 10 377 164 +441us[ +441us] +/- 2015us
::123 est ntp-0-gnss, ::123:123 ntp-0-gps : les deux répondent en Stratum 1. Sur la plupart
de nos serveurs, le GNSS est la source préférée et le GPS un témoin ; sur les autres, chrony les départage sur
ses seules mesures. À ce jour, rien ne reste ouvert sur cette machine.
Sources
- Galileo — Agence de l'Union européenne pour le programme spatial (EUSPA)
- ubxtool — exemples de configuration des récepteurs u-blox (gpsd)
- Documentation de chrony.conf (4.6.1) — directives
refclock(optionslock,offset),stratumweight - Interne — la réinstallation de ntp-0, nos serveurs Stratum 1, notre infrastructure