Un second Stratum 1 GNSS avec Galileo : notre journal

Par Richard DEMONGEOT | Publié le 27 octobre 2026 | Temps de lecture : 8 min

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.

Deux Raspberry Pi dans une baie : à gauche ntp-0-gnss et son HAT GNSS, à droite ntp-0 dans son boîtier rouge
Les deux Stratum 1 dans la baie : ntp-0-gnss à gauche, ntp-0 à droite.

Le matériel

Élémentntp-0ntp-0-gnss
CarteRaspberry Pi 3 Model B Rev 1.2, Raspberry Pi OS Lite Trixie (Debian 13), noyau 6.18.50
Récepteurcarte 52Pi, Quectel L80 (MediaTek MT3339)HAT Uputronics GPS/RTC, u-blox MAX-M8Q
ConstellationsGPSGPS, Galileo, GLONASS (+ SBAS)
Liaison NMEA9600 bauds115200 bauds
PPSGPIO4GPIO18
Horloge sauvegardéeaucune (fake-hwclock)RTC RV-3028 sur le HAT
AntenneOTB extérieureantenne distincte, provisoire
Refid annoncéGPSGNSS

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.

HAT Uputronics GPS/RTC monté sur un Raspberry Pi, câble d'antenne et voyant PPS allumé
Le HAT Uputronics sur ntp-0-gnss : câble d'antenne à gauche, voyant PPS allumé.
ntp-0-gnss vu de profil dans la baie, câble réseau et alimentation branchés
De profil, dans la baie.

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 coupureBascule désactivéeBascule par niveau
Noyauhctosys: unable to readsetting system clock to …
hwclockInvalid argumentsans erreur
Écart rattrapé par chrony7708 saucun

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

Questions fréquentes

Pourquoi un second Stratum 1 à côté du premier ?

Pour qu'aucune panne de récepteur, d'antenne ou de carte ne fasse perdre le Stratum 1. Les deux machines n'ont rien en commun côté temps : récepteurs de marques et de générations différentes (Quectel L80 en GPS seul, u-blox M8 en GPS, Galileo et GLONASS), antennes distinctes, et chacune sert de témoin à l'autre sur le réseau local.

Pourquoi activer Galileo sur le récepteur ?

Galileo est le système de positionnement de l'Union européenne. Le recevoir en plus du GPS et du GLONASS donne plus de satellites, donc un fix plus robuste, et une heure qui ne dépend plus d'un seul système. Sur notre u-blox M8, Galileo est désactivé en sortie d'usine : il s'active avec ubxtool, mais ce module n'a pas de mémoire flash, et le réglage se perd à une coupure de courant ; nous le réappliquons donc à chaque démarrage.

Que veut dire le refid GNSS de ntp-0-gnss ?

C'est l'identifiant de référence qu'annonce un serveur Stratum 1 : il nomme sa source de temps. ntp-0 annonce GPS, car son récepteur ne reçoit que le GPS. ntp-0-gnss annonce GNSS, le terme générique pour l'ensemble des systèmes de navigation par satellite, car le sien combine plusieurs constellations.