Autonomiczny serwer NTP Stratum 1 na Raspberry Pi: chrony z DCF77 i GPS PPS w 2026 roku
Na Raspberry Pi OS Bookworm/Trixie stare poradniki dotyczące serwerów NTP nie działają z powodu libgpiod v2, przejścia na chrony oraz zmian w układach GPIO. Implementacja w C++20 wykorzystuje odbiornik DCF77 RC8000 na GPIO 17 oraz moduł GPS ublox NEO-M8T z PPS na GPIO 18. Serwer zapewnia dokładność <1 µs bez internetu, z niezależną weryfikacją źródeł czasu.
Problemy przestarzałych implementacji
libgpiod v2 psuje kod GPIO
libgpiod v2 usunął API v1 bez zachowania kompatybilności. Stary kod z gpiod_chip_open_by_name i gpiod_chip_get_line nie kompiluje się:
// libgpiod v1 — NIE KOMPILUJE SIĘ z v2
struct gpiod_chip *chip = gpiod_chip_open_by_name("gpiochip0");
struct gpiod_line *line = gpiod_chip_get_line(chip, 17);
gpiod_line_request_both_edges_events(line, "dcf77");
Nowe API wymaga gpiod_line_settings_new() i gpiod_line_config_new():
// libgpiod v2 — właściwe podejście
const SettingsPtr settings(gpiod_line_settings_new());
gpiod_line_settings_set_edge_detection(settings.get(), GPIOD_LINE_EDGE_BOTH);
gpiod_line_settings_set_event_clock(settings.get(), GPIOD_LINE_CLOCK_REALTIME);
const LineCfgPtr line_cfg(gpiod_line_config_new());
gpiod_line_config_add_line_settings(line_cfg.get(), &offset, 1, settings.get());
const RequestPtr request(gpiod_chip_request_lines(chip.get(), req_cfg.get(), line_cfg.get()));
Kluczowa zaleta — GPIOD_LINE_CLOCK_REALTIME: zdarzenia otrzymują znaczniki czasu od jądra w przerwaniu, jitter spada z 40 ms do <5 ms.
Przejście z ntpd na chrony
ntpd został zastąpiony przez chrony w Bookworm. Porównanie:
| Parametr | ntpd | chrony |
|----------|------|--------|
| Zbieżność | minuty | sekundy (makestep 0.5 3) |
| Utrata sygnału | degradacja | holdover (local stratum 10) |
| PPS | łaty | natywny refclock PPS |
| Dokładność PPS | ~100 µs | <1 µs |
Oba wspierają NTP SHM do wymiany danych przez pamięć współdzieloną.
Układ GPIO na RPi 5
gpiochip0 jest przestarzały. Na RPi 5/Trixie potrzebny jest gpiochip4 (sprawdzić gpiodetect).
Architektura systemu
Dwa źródła czasu:
- GPS NEO-M8T: PPS (/dev/pps0) + NMEA przez gpsd (NTP SHM #0)
- DCF77 RC8000: GPIO 17 → dekoder → NTP SHM #2
- chrony: stratum 1 z prefer PPS
- Monitor WWW: chronyc + journald na :8080
Konfiguracja chrony:
refclock PPS /dev/pps0 refid PPS precision 1e-9 poll 0 dpoll -2 lock NMEA prefer
refclock SHM 0 refid NMEA precision 1e-3 poll 0 dpoll -2 offset 0.0 delay 0.2 noselect
refclock SHM 2 refid DCF7 precision 1e-2 offset 0.0 delay 0.008 poll 6 dpoll 5
local stratum 10
DCF77 służy jako zapasowe źródło i detektor spoofingu GPS (rozbieżność >kilku sekund).
Dekoder DCF77
Timestamp od jądra
GPIOD_LINE_CLOCK_REALTIME daje znacznik przerwania, a nie userspace. StdDev w SHM: <5 ms.
NTP SHM dla 64-bit
Struktura z dopełnieniem:
struct NtpShmTime {
int32_t mode;
int32_t count;
int64_t clockTimeStampSec;
int32_t clockTimeStampUSec;
int32_t _pad;
int64_t receiveTimeStampSec;
int32_t receiveTimeStampUSec;
int32_t leap;
int32_t precision;
int32_t nsamples;
int32_t valid;
};
volatile NtpShmTime *shm_ = nullptr;
Protokół DCF77
59 bitów/minutę w BCD z parzystością:
- 100 ms HIGH → bit 0
- 200 ms HIGH → bit 1
- Pauza >1.5 s → znacznik minuty
Walidacja: 3 grupy parzystości + timegm(). Bootstrap: 2 ramki z różnicą 60 s.
Konfiguracja NEO-M8T
- Oprogramowanie TIM: RAIM, niski jitter PPS
- dynModel=Stationary: jitter 5–10 ns
- antCableDelay=50 ns: kompensacja kabla
- 4 konstelacje: 20–30 satelitów
- lpMode=Continuous: bez oszczędzania energii
Monitor WWW i wdrożenie
dcf77_web: HTTP :8080, chronyc w JSON, UI w pliku binarnym. API: /api/status, /api/prefer (tylko localhost).
Kompilacja vcpkg + CMake:
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
Wdrożenie Ansible z hardeningiem systemd.
Wyniki
chronyc tracking:
- Stratum: 1
- Czas systemowy: +42 ns
- RMS offset: 51 ns
- Root dispersion: 15 ns
DCF77: ~3 ms.
Co jest ważne
- libgpiod v2 wymaga nowego modelu obiektowego z
GPIOD_LINE_CLOCK_REALTIME - chrony przewyższa ntpd w dokładności PPS (<1 µs) i holdover
- NTP SHM na 64-bit wymaga jawnego dopełnienia pod
int64_t - NEO-M8T TIM + Stationary: jitter PPS 5–10 ns
- DCF77 weryfikuje GPS przed spoofingiem
— Editorial Team
Brak komentarzy.