mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-27 11:13:02 +02:00
Merge pull request #1399 from kernelkit/gps
This commit is contained in:
@@ -42,6 +42,9 @@ BR2_PACKAGE_DBUS_GLIB=y
|
||||
BR2_PACKAGE_DBUS_TRIGGERD=y
|
||||
BR2_PACKAGE_EUDEV_RULES_GEN=y
|
||||
# BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set
|
||||
BR2_PACKAGE_GPSD_DEVICES="/dev/gps0"
|
||||
BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2
|
||||
BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1
|
||||
BR2_PACKAGE_GPTFDISK=y
|
||||
BR2_PACKAGE_GPTFDISK_SGDISK=y
|
||||
BR2_PACKAGE_MDIO_TOOLS=y
|
||||
@@ -146,6 +149,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_FEATURE_GPS=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_REALTEK=y
|
||||
BR2_PACKAGE_CONFD=y
|
||||
|
||||
@@ -42,6 +42,9 @@ BR2_PACKAGE_DBUS_GLIB=y
|
||||
BR2_PACKAGE_DBUS_TRIGGERD=y
|
||||
BR2_PACKAGE_EUDEV_RULES_GEN=y
|
||||
# BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set
|
||||
BR2_PACKAGE_GPSD_DEVICES="/dev/gps0"
|
||||
BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2
|
||||
BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1
|
||||
BR2_PACKAGE_GPTFDISK=y
|
||||
BR2_PACKAGE_GPTFDISK_SGDISK=y
|
||||
BR2_PACKAGE_MDIO_TOOLS=y
|
||||
@@ -124,6 +127,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_FEATURE_GPS=y
|
||||
BR2_PACKAGE_CONFD=y
|
||||
BR2_PACKAGE_CONFD_TEST_MODE=y
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
|
||||
@@ -44,6 +44,9 @@ BR2_PACKAGE_DBUS_GLIB=y
|
||||
BR2_PACKAGE_DBUS_TRIGGERD=y
|
||||
BR2_PACKAGE_EUDEV_RULES_GEN=y
|
||||
# BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set
|
||||
BR2_PACKAGE_GPSD_DEVICES="/dev/gps0"
|
||||
BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2
|
||||
BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1
|
||||
BR2_PACKAGE_GPTFDISK=y
|
||||
BR2_PACKAGE_GPTFDISK_SGDISK=y
|
||||
BR2_PACKAGE_MDIO_TOOLS=y
|
||||
@@ -142,6 +145,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_FEATURE_GPS=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_REALTEK=y
|
||||
BR2_PACKAGE_CONFD=y
|
||||
|
||||
@@ -44,6 +44,9 @@ BR2_PACKAGE_DBUS_GLIB=y
|
||||
BR2_PACKAGE_DBUS_TRIGGERD=y
|
||||
BR2_PACKAGE_EUDEV_RULES_GEN=y
|
||||
# BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set
|
||||
BR2_PACKAGE_GPSD_DEVICES="/dev/gps0"
|
||||
BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2
|
||||
BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1
|
||||
BR2_PACKAGE_GPTFDISK=y
|
||||
BR2_PACKAGE_GPTFDISK_SGDISK=y
|
||||
BR2_PACKAGE_MDIO_TOOLS=y
|
||||
@@ -124,6 +127,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_FEATURE_GPS=y
|
||||
BR2_PACKAGE_CONFD=y
|
||||
BR2_PACKAGE_CONFD_TEST_MODE=y
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
|
||||
@@ -53,6 +53,9 @@ BR2_PACKAGE_DBUS_GLIB=y
|
||||
BR2_PACKAGE_DBUS_TRIGGERD=y
|
||||
BR2_PACKAGE_EUDEV_RULES_GEN=y
|
||||
# BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set
|
||||
BR2_PACKAGE_GPSD_DEVICES="/dev/gps0"
|
||||
BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2
|
||||
BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1
|
||||
BR2_PACKAGE_GPTFDISK=y
|
||||
BR2_PACKAGE_GPTFDISK_SGDISK=y
|
||||
BR2_PACKAGE_MDIO_TOOLS=y
|
||||
@@ -173,6 +176,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_FEATURE_GPS=y
|
||||
BR2_PACKAGE_FEATURE_WIFI=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_REALTEK=y
|
||||
|
||||
@@ -42,6 +42,9 @@ BR2_PACKAGE_DBUS_GLIB=y
|
||||
BR2_PACKAGE_DBUS_TRIGGERD=y
|
||||
BR2_PACKAGE_EUDEV_RULES_GEN=y
|
||||
# BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set
|
||||
BR2_PACKAGE_GPSD_DEVICES="/dev/gps0"
|
||||
BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2
|
||||
BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1
|
||||
BR2_PACKAGE_GPTFDISK=y
|
||||
BR2_PACKAGE_GPTFDISK_SGDISK=y
|
||||
BR2_PACKAGE_RNG_TOOLS=y
|
||||
@@ -145,6 +148,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_FEATURE_GPS=y
|
||||
BR2_PACKAGE_FEATURE_WIFI=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_MEDIATEK=y
|
||||
BR2_PACKAGE_FEATURE_WIFI_REALTEK=y
|
||||
|
||||
@@ -42,6 +42,9 @@ BR2_PACKAGE_DBUS_GLIB=y
|
||||
BR2_PACKAGE_DBUS_TRIGGERD=y
|
||||
BR2_PACKAGE_EUDEV_RULES_GEN=y
|
||||
# BR2_PACKAGE_EUDEV_ENABLE_HWDB is not set
|
||||
BR2_PACKAGE_GPSD_DEVICES="/dev/gps0"
|
||||
BR2_PACKAGE_GPSD_MAX_CLIENT_VALUE=2
|
||||
BR2_PACKAGE_GPSD_MAX_DEV_VALUE=1
|
||||
BR2_PACKAGE_GPTFDISK=y
|
||||
BR2_PACKAGE_GPTFDISK_SGDISK=y
|
||||
BR2_PACKAGE_RNG_TOOLS=y
|
||||
@@ -123,6 +126,7 @@ INFIX_DESC="Infix is an immutable, friendly, and secure operating system that tu
|
||||
INFIX_HOME="https://github.com/kernelkit/infix/"
|
||||
INFIX_DOC="https://kernelkit.org/infix/"
|
||||
INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_FEATURE_GPS=y
|
||||
BR2_PACKAGE_CONFD=y
|
||||
BR2_PACKAGE_CONFD_TEST_MODE=y
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
|
||||
@@ -10,6 +10,7 @@ All notable changes to the project are documented in this file.
|
||||
|
||||
- Upgrade Linux kernel to 6.18.9 (LTS)
|
||||
- Add support for Microchip SAMA7G54-EK Evaluation Kit, Arm Cortex-A7
|
||||
- Add GPS/GNSS receiver support with NTP reference clock integration
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
@@ -3,6 +3,56 @@
|
||||
The hardware infomation and status is handled by the YANG model [IETF
|
||||
hardware][1], with deviations and augmentations in _infix-hardware_.
|
||||
|
||||
## GPS/GNSS Receivers
|
||||
|
||||
Infix supports GPS/GNSS receivers for hardware status monitoring and NTP
|
||||
time synchronization. USB GPS receivers using the USB ACM interface are
|
||||
supported (e.g., u-blox). When connected, devices are automatically
|
||||
discovered and named `gps0`, `gps1`, etc.
|
||||
|
||||
### Current status
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>show hardware</b>
|
||||
<span class="header">HARDWARE COMPONENTS </span>
|
||||
──────────────────────────────────────────────────────────────
|
||||
<span class="title">GPS/GNSS Receivers </span>
|
||||
Name : gps0
|
||||
Device : /dev/gps0
|
||||
Driver : u-blox
|
||||
Status : Active
|
||||
Fix : 3D
|
||||
Satellites : 10/15 (used/visible)
|
||||
Position : 59.334567N 18.063456E 42.3m
|
||||
PPS : Available
|
||||
</code></pre>
|
||||
|
||||
Available information per receiver:
|
||||
|
||||
| Field | Description |
|
||||
|------------|---------------------------------------------------|
|
||||
| Name | Component name (`gps0`, `gps1`, ...) |
|
||||
| Device | Device path (`/dev/gps0`) |
|
||||
| Driver | Protocol driver (e.g., `u-blox`, `NMEA`, `SiRF`) |
|
||||
| Status | `Active` or `Inactive` |
|
||||
| Fix | `NONE`, `2D`, or `3D` |
|
||||
| Satellites | Used/visible count |
|
||||
| Position | Latitude, longitude, altitude (when fix acquired) |
|
||||
| PPS | Pulse Per Second signal availability |
|
||||
|
||||
### Configure GPS receiver
|
||||
|
||||
GPS receivers are hardware components with class `gps`. The class is
|
||||
auto-inferred from the component name, similar to WiFi radios (`radioN`):
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>configure</b>
|
||||
admin@example:/config/> <b>set hardware component gps0</b>
|
||||
admin@example:/config/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
To use a GPS receiver as an NTP reference clock source, see
|
||||
[NTP — GPS Reference Clock](ntp.md#gps-reference-clock).
|
||||
|
||||
|
||||
## USB Ports
|
||||
|
||||
For Infix to be able to control USB port(s), a device tree modification
|
||||
|
||||
+64
@@ -28,6 +28,70 @@ admin@example:/config/ntp/> <b>set refclock-master master-stratum 10</b>
|
||||
admin@example:/config/ntp/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
## GPS Reference Clock
|
||||
|
||||
A GPS/GNSS receiver can be used as an NTP reference clock source,
|
||||
providing stratum 1 time derived from the GPS satellite constellation.
|
||||
This requires a GPS hardware component to be configured first, see
|
||||
[Hardware — GPS/GNSS Receivers](hardware.md#gpsgnss-receivers).
|
||||
|
||||
### Basic setup
|
||||
|
||||
Add a GPS receiver as a reference clock source:
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/> <b>edit ntp</b>
|
||||
admin@example:/config/ntp/> <b>edit refclock-master source gps0</b>
|
||||
admin@example:/config/ntp/refclock-master/source/gps0/> <b>set poll 2</b>
|
||||
admin@example:/config/ntp/refclock-master/source/gps0/> <b>set precision 0.1</b>
|
||||
admin@example:/config/ntp/refclock-master/source/gps0/> <b>end</b>
|
||||
admin@example:/config/ntp/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
Tunable parameters:
|
||||
|
||||
| Parameter | Default | Description |
|
||||
|-------------|--------:|----------------------------------------------------|
|
||||
| `poll` | `2` | Polling interval in log2 seconds (2 = 4s) |
|
||||
| `precision` | `0.1` | Assumed precision in seconds (0.1 = 100ms) |
|
||||
| `refid` | `"GPS"`| Reference identifier (e.g., `GPS`, `GNSS`, `GLO`) |
|
||||
| `prefer` | `false` | Prefer this source over other reference clocks |
|
||||
| `pps` | `false` | Enable PPS for microsecond-level accuracy |
|
||||
| `offset` | `0.0` | Constant offset correction in seconds |
|
||||
| `delay` | `0.0` | Assumed maximum delay from the receiver |
|
||||
|
||||
### PPS (Pulse Per Second)
|
||||
|
||||
When the GPS receiver provides a PPS signal, enable the `pps` option for
|
||||
microsecond-level accuracy. With PPS, the GPS time provides the initial
|
||||
lock and the PPS edges discipline the clock:
|
||||
|
||||
<pre class="cli"><code>admin@example:/config/ntp/> <b>edit refclock-master source gps0</b>
|
||||
admin@example:/config/ntp/refclock-master/source/gps0/> <b>set pps true</b>
|
||||
admin@example:/config/ntp/refclock-master/source/gps0/> <b>set precision 0.000001</b>
|
||||
admin@example:/config/ntp/refclock-master/source/gps0/> <b>end</b>
|
||||
admin@example:/config/ntp/> <b>leave</b>
|
||||
</code></pre>
|
||||
|
||||
### Monitoring
|
||||
|
||||
The `show ntp` command shows the GPS receiver as the reference clock source:
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>show ntp</b>
|
||||
Mode : Server (GPS reference clock: gps0)
|
||||
Port : 123
|
||||
Stratum : 1
|
||||
Ref time (UTC) : Sun Feb 08 19:44:36 2026
|
||||
</code></pre>
|
||||
|
||||
Use `show ntp source` to see GPS reference clock details:
|
||||
|
||||
<pre class="cli"><code>admin@example:/> <b>show ntp source</b>
|
||||
Reference Clock : gps0 (u-blox)
|
||||
Status : selected
|
||||
Fix Mode : 3D
|
||||
Satellites : 9/17 (used/visible)
|
||||
</code></pre>
|
||||
|
||||
## Server Mode
|
||||
|
||||
Synchronize from upstream NTP servers while serving time to clients:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
menu "Packages"
|
||||
|
||||
comment "Hardware Support"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/feature-gps/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/feature-wifi/Config.in"
|
||||
|
||||
comment "Software Packages"
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# Override udev rules for gpsd
|
||||
#
|
||||
# This file is Copyright 2010 by the GPSD project
|
||||
# SPDX-License-Identifier: BSD-2-clause
|
||||
|
||||
SUBSYSTEM!="tty", GOTO="gpsd_rules_end"
|
||||
|
||||
# Prolific Technology, Inc. PL2303 Serial Port [linux module: pl2303]
|
||||
# !!! rule disabled in Debian as it matches too many other devices
|
||||
# ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
# ATEN International Co., Ltd UC-232A Serial Port [linux module: pl2303]
|
||||
ATTRS{idVendor}=="0557", ATTRS{idProduct}=="2008", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# PS-360 OEM (GPS sold with MS Street and Trips 2005) [linux module: pl2303]
|
||||
ATTRS{idVendor}=="067b", ATTRS{idProduct}=="aaa0", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# FTDI 8U232AM / FT232 [linux module: ftdi_sio]
|
||||
# !!! rule disabled in Debian as it matches too many other devices
|
||||
# ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# Cypress M8/CY7C64013 (Delorme uses these) [linux module: cypress_m8]
|
||||
ATTRS{idVendor}=="1163", ATTRS{idProduct}=="0100", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# Cypress M8/CY7C64013 (DeLorme LT-40)
|
||||
ATTRS{idVendor}=="1163", ATTRS{idProduct}=="0200", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# Garmin International GPSmap, various models (tested with Garmin GPS 18 USB) [linux module: garmin_gps]
|
||||
ATTRS{idVendor}=="091e", ATTRS{idProduct}=="0003", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# Cygnal Integrated Products, Inc. CP210x Composite Device (Used by Holux m241 and Wintec grays2 wbt-201) [linux module: cp210x]
|
||||
# !!! rule disabled in Debian as it matches too many other devices
|
||||
#ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# Cygnal Integrated Products, Inc. [linux module: cp210x]
|
||||
# !!! rule disabled in Debian as it matches too many other devices
|
||||
#ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea71", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# u-blox AG, u-blox 5 (tested with Navilock NL-402U) [linux module: cdc_acm]
|
||||
ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a5", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# u-blox AG, u-blox 6 (tested with GNSS Evaluation Kit TCXO) [linux module: cdc_acm]
|
||||
ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a6", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# u-blox AG, u-blox 7 [linux module: cdc_acm]
|
||||
ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# u-blox AG, u-blox 8 (tested with GNSS Evaluation Kit EKV-M8N) [linux module: cdc_acm]
|
||||
ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a8", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# u-blox AG, u-blox 9 (tested with GNSS Evaluation Kit C099-F9P) [linux module: cdc_acm]
|
||||
ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a9", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# MediaTek (tested with HOLUX M-1200E) [linux module: cdc_acm]
|
||||
ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="3329", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# Telit wireless solutions (tested with HE910G) [linux module: cdc_acm]
|
||||
ATTRS{interface}=="Telit Wireless Module Port", ATTRS{bInterfaceNumber}=="06", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
# u-blox AG, u-blox 8 (tested with u-blox8 GNSS Mouse Receiver / GR-801) [linux module: cdc_acm]
|
||||
# !!! rule disabled in Debian as it matches too many other devices
|
||||
#ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
ACTION=="remove", RUN+="/usr/lib/udev/gpsd.hotplug"
|
||||
|
||||
LABEL="gpsd_rules_end"
|
||||
@@ -0,0 +1,34 @@
|
||||
config BR2_PACKAGE_FEATURE_GPS
|
||||
bool "Feature GPS/GNSS"
|
||||
select BR2_PACKAGE_GPSD
|
||||
select BR2_PACKAGE_GPSD_MAX_CLIENT
|
||||
select BR2_PACKAGE_GPSD_MAX_DEV
|
||||
select BR2_PACKAGE_GPSD_SQUELCH
|
||||
select BR2_PACKAGE_GPSD_ASHTECH
|
||||
select BR2_PACKAGE_GPSD_EARTHMATE
|
||||
select BR2_PACKAGE_GPSD_EVERMORE
|
||||
select BR2_PACKAGE_GPSD_FURY
|
||||
select BR2_PACKAGE_GPSD_FV18
|
||||
select BR2_PACKAGE_GPSD_GARMIN
|
||||
select BR2_PACKAGE_GPSD_GARMIN_SIMPLE_TXT
|
||||
select BR2_PACKAGE_GPSD_GEOSTAR
|
||||
select BR2_PACKAGE_GPSD_GPSCLOCK
|
||||
select BR2_PACKAGE_GPSD_GREIS
|
||||
select BR2_PACKAGE_GPSD_ISYNC
|
||||
select BR2_PACKAGE_GPSD_ITRAX
|
||||
select BR2_PACKAGE_GPSD_NMEA2000
|
||||
select BR2_PACKAGE_GPSD_OCEANSERVER
|
||||
select BR2_PACKAGE_GPSD_ONCORE
|
||||
select BR2_PACKAGE_GPSD_RTCM104V2
|
||||
select BR2_PACKAGE_GPSD_RTCM104V3
|
||||
select BR2_PACKAGE_GPSD_SIRF
|
||||
select BR2_PACKAGE_GPSD_SKYTRAQ
|
||||
select BR2_PACKAGE_GPSD_SUPERSTAR2
|
||||
select BR2_PACKAGE_GPSD_TRIMBLE_TSIP
|
||||
select BR2_PACKAGE_GPSD_TRIPMATE
|
||||
select BR2_PACKAGE_GPSD_TRUE_NORTH
|
||||
select BR2_PACKAGE_GPSD_UBX
|
||||
help
|
||||
Enables GPS/GNSS support in Infix. Includes gpsd with all
|
||||
receiver protocol drivers and kernel USB ACM support for
|
||||
common USB GPS receivers.
|
||||
@@ -0,0 +1,21 @@
|
||||
################################################################################
|
||||
#
|
||||
# GPS/GNSS support
|
||||
#
|
||||
################################################################################
|
||||
|
||||
FEATURE_GPS_PACKAGE_VERSION = 1.0
|
||||
FEATURE_GPS_PACKAGE_LICENSE = MIT
|
||||
|
||||
define FEATURE_GPS_INSTALL_TARGET_CMDS
|
||||
$(INSTALL) -D -m 0644 $(FEATURE_GPS_PKGDIR)/gpsd.default \
|
||||
$(TARGET_DIR)/etc/default/gpsd
|
||||
$(INSTALL) -D -m 0644 $(FEATURE_GPS_PKGDIR)/25-gpsd.rules \
|
||||
$(TARGET_DIR)/usr/lib/udev/rules.d/25-gpsd.rules
|
||||
endef
|
||||
|
||||
define FEATURE_GPS_LINUX_CONFIG_FIXUPS
|
||||
$(call KCONFIG_SET_OPT,CONFIG_USB_ACM,m)
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -0,0 +1 @@
|
||||
GPSD_OPTIONS="-n"
|
||||
@@ -106,6 +106,7 @@ def ntp(args: List[str]) -> None:
|
||||
# Fetch both client and server operational data
|
||||
client_data = get_json("/system-state/ntp")
|
||||
server_data = get_json("/ietf-ntp:ntp")
|
||||
hw_data = get_json("/ietf-hardware:hardware")
|
||||
|
||||
# Merge into single data structure
|
||||
data = {}
|
||||
@@ -113,6 +114,8 @@ def ntp(args: List[str]) -> None:
|
||||
data.update(client_data)
|
||||
if server_data:
|
||||
data.update(server_data)
|
||||
if hw_data:
|
||||
data.update(hw_data)
|
||||
|
||||
if RAW_OUTPUT:
|
||||
if not data:
|
||||
@@ -147,6 +150,11 @@ def ntp_source(args: List[str]) -> None:
|
||||
print("No ntp server data retrieved.")
|
||||
return
|
||||
|
||||
# Also fetch hardware data for GPS receiver info
|
||||
hw_data = get_json("/ietf-hardware:hardware")
|
||||
if hw_data:
|
||||
data.update(hw_data)
|
||||
|
||||
if RAW_OUTPUT:
|
||||
print(json.dumps(data, indent=2))
|
||||
return
|
||||
|
||||
@@ -150,6 +150,12 @@ static int hardware_cand_infer_class(json_t *root, sr_session_ctx_t *session, co
|
||||
inferred.data.string_val = "infix-hardware:wifi";
|
||||
err = srx_set_item(session, &inferred, 0, "%s/class", xpath);
|
||||
}
|
||||
|
||||
if (!fnmatch("gps+([0-9])", name, FNM_EXTMATCH)) {
|
||||
inferred.data.string_val = "infix-hardware:gps";
|
||||
err = srx_set_item(session, &inferred, 0, "%s/class", xpath);
|
||||
}
|
||||
|
||||
out_free_name:
|
||||
free(name);
|
||||
out_free_xpath:
|
||||
@@ -701,6 +707,11 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l
|
||||
free(wifi_iface_list);
|
||||
wifi_iface_list = NULL;
|
||||
wifi_iface_count = 0;
|
||||
} else if (!strcmp(class, "infix-hardware:gps")) {
|
||||
if (event != SR_EV_DONE)
|
||||
continue;
|
||||
|
||||
systemf("initctl -nbq touch statd");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+75
-2
@@ -134,12 +134,85 @@ static int change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd
|
||||
}
|
||||
fprintf(fp, "\n");
|
||||
|
||||
/* Reference clock (local stratum) - fallback time source */
|
||||
/* Reference clock configuration */
|
||||
refclock = lydx_get_child(ntp, "refclock-master");
|
||||
if (refclock) {
|
||||
struct lyd_node *source;
|
||||
int stratum = atoi(lydx_get_cattr(refclock, "master-stratum"));
|
||||
bool has_gps_sources = false;
|
||||
|
||||
/* Only configure local clock if stratum is valid (1-15) */
|
||||
/*
|
||||
* GPS/GNSS reference clock sources via gpsd SHM
|
||||
*
|
||||
* Each GPS hardware component (gps0, gps1, ...) maps to:
|
||||
* - SHM unit 2*N for GPS time (coarse, ~100ms)
|
||||
* - SHM unit 2*N+1 for PPS time (precise, ~1us) if enabled
|
||||
*
|
||||
* The mapping is based on gpsd's convention.
|
||||
*/
|
||||
LYX_LIST_FOR_EACH(lyd_child(refclock), source, "source") {
|
||||
const char *receiver, *refid, *poll_str, *precision_str;
|
||||
const char *offset_str, *delay_str;
|
||||
bool prefer, pps;
|
||||
int unit;
|
||||
|
||||
receiver = lydx_get_cattr(source, "receiver");
|
||||
if (!receiver)
|
||||
continue;
|
||||
|
||||
/* Extract unit number from receiver name (gps0 -> 0, gps1 -> 1, ...) */
|
||||
if (sscanf(receiver, "gps%d", &unit) != 1) {
|
||||
ERROR("Invalid GPS receiver name: %s (expected gpsN)", receiver);
|
||||
continue;
|
||||
}
|
||||
|
||||
refid = lydx_get_cattr(source, "refid");
|
||||
poll_str = lydx_get_cattr(source, "poll");
|
||||
precision_str = lydx_get_cattr(source, "precision");
|
||||
offset_str = lydx_get_cattr(source, "offset");
|
||||
delay_str = lydx_get_cattr(source, "delay");
|
||||
prefer = lydx_get_bool(source, "prefer");
|
||||
pps = lydx_get_bool(source, "pps");
|
||||
|
||||
if (!has_gps_sources) {
|
||||
fprintf(fp, "# GPS/GNSS reference clock sources (via gpsd SHM)\n");
|
||||
has_gps_sources = true;
|
||||
}
|
||||
|
||||
/* GPS time source: SHM unit 2*N */
|
||||
fprintf(fp, "refclock SHM %d", unit * 2);
|
||||
if (poll_str)
|
||||
fprintf(fp, " poll %s", poll_str);
|
||||
if (refid)
|
||||
fprintf(fp, " refid %s", refid);
|
||||
if (precision_str)
|
||||
fprintf(fp, " precision %s", precision_str);
|
||||
if (offset_str && atof(offset_str) != 0.0)
|
||||
fprintf(fp, " offset %s", offset_str);
|
||||
if (delay_str && atof(delay_str) != 0.0)
|
||||
fprintf(fp, " delay %s", delay_str);
|
||||
if (prefer)
|
||||
fprintf(fp, " prefer");
|
||||
fprintf(fp, "\n");
|
||||
|
||||
/* PPS time source: SHM unit 2*N+1 (if enabled) */
|
||||
if (pps) {
|
||||
fprintf(fp, "refclock SHM %d", unit * 2 + 1);
|
||||
fprintf(fp, " poll %s", poll_str ? poll_str : "-6");
|
||||
fprintf(fp, " refid PPS");
|
||||
fprintf(fp, " precision 0.000001");
|
||||
if (prefer)
|
||||
fprintf(fp, " prefer");
|
||||
/* PPS needs a time source for initial lock */
|
||||
fprintf(fp, " lock %s", refid ? refid : "GPS");
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (has_gps_sources)
|
||||
fprintf(fp, "\n");
|
||||
|
||||
/* Local clock fallback - only if stratum is valid (1-15) */
|
||||
if (stratum >= 1 && stratum <= 15) {
|
||||
fprintf(fp, "# Local reference clock - fallback stratum %d source\n", stratum);
|
||||
fprintf(fp, "local stratum %d orphan\n\n", stratum);
|
||||
|
||||
@@ -27,7 +27,7 @@ MODULES=(
|
||||
"infix-syslog@2025-11-17.yang"
|
||||
"iana-hardware@2018-03-13.yang"
|
||||
"ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor"
|
||||
"infix-hardware@2026-01-19.yang"
|
||||
"infix-hardware@2026-02-08.yang"
|
||||
"ieee802-dot1q-types@2022-10-29.yang"
|
||||
"infix-ip@2025-11-02.yang"
|
||||
"infix-if-type@2026-01-07.yang"
|
||||
@@ -51,6 +51,6 @@ MODULES=(
|
||||
"ietf-crypto-types -e cleartext-symmetric-keys"
|
||||
"infix-crypto-types@2025-11-09.yang"
|
||||
"ietf-keystore -e symmetric-keys"
|
||||
"infix-ntp@2025-12-03.yang"
|
||||
"infix-ntp@2026-02-08.yang"
|
||||
"infix-keystore@2025-12-17.yang"
|
||||
)
|
||||
|
||||
@@ -21,6 +21,10 @@ module infix-hardware {
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Vital Product Data augmentation of ieee-hardware and deviations.";
|
||||
|
||||
revision 2026-02-08 {
|
||||
description "Add GPS/GNSS receiver hardware class and container for time sync.";
|
||||
reference "internal";
|
||||
}
|
||||
revision 2026-01-19 {
|
||||
description "Add probe-timeout for slow USB WiFi dongles.";
|
||||
reference "internal";
|
||||
@@ -63,6 +67,15 @@ module infix-hardware {
|
||||
WiFi radios are hardware components with class 'ih:wifi'.";
|
||||
}
|
||||
|
||||
typedef gps-receiver-ref {
|
||||
type leafref {
|
||||
path "/iehw:hardware/iehw:component/iehw:name";
|
||||
}
|
||||
description
|
||||
"Reference to a GPS/GNSS receiver hardware component.
|
||||
GPS receivers are hardware components with class 'ih:gps'.";
|
||||
}
|
||||
|
||||
typedef wifi-band {
|
||||
type enumeration {
|
||||
enum "2.4GHz" {
|
||||
@@ -95,6 +108,11 @@ module infix-hardware {
|
||||
description "This identity is used to describe a WiFi radio/PHY";
|
||||
}
|
||||
|
||||
identity gps {
|
||||
base iahw:hardware-class;
|
||||
description "GPS/GNSS receiver for time synchronization";
|
||||
}
|
||||
|
||||
deviation "/iehw:hardware/iehw:component/iehw:state/iehw:admin-state" {
|
||||
deviate add {
|
||||
must ". = 'locked' or . = 'unlocked'" {
|
||||
@@ -510,5 +528,126 @@ module infix-hardware {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* GPS/GNSS Receiver configuration (when class = 'ih:gps')
|
||||
*/
|
||||
|
||||
container gps-receiver {
|
||||
when "derived-from-or-self(../iehw:class, 'ih:gps')";
|
||||
presence "GPS receiver configuration";
|
||||
description
|
||||
"GPS/GNSS receiver configuration and operational data.
|
||||
|
||||
This container is present when the hardware component represents
|
||||
a GPS/GNSS receiver (class 'ih:gps'). GPS receivers provide
|
||||
precision time via gpsd, which chronyd can use as a reference
|
||||
clock through shared memory (SHM).
|
||||
|
||||
The hardware component name (e.g., 'gps0') determines:
|
||||
- Device path: /dev/gps0 (symlink managed by udev)
|
||||
- SHM unit: 0 for GPS time, 1 for PPS (derived from gpsN index)
|
||||
|
||||
Supported GNSS constellations depend on the hardware:
|
||||
- GPS (US)
|
||||
- GLONASS (Russia)
|
||||
- Galileo (EU)
|
||||
- BeiDou (China)";
|
||||
|
||||
/*
|
||||
* Operational state from gpsd
|
||||
*/
|
||||
|
||||
leaf device {
|
||||
config false;
|
||||
type string;
|
||||
description
|
||||
"Actual device path being used (e.g., /dev/ttyUSB0, /dev/ttyACM0).
|
||||
This is the physical device that /dev/gpsN symlinks to.";
|
||||
}
|
||||
|
||||
leaf driver {
|
||||
config false;
|
||||
type string;
|
||||
description
|
||||
"GPS driver/protocol in use (e.g., 'u-blox', 'NMEA', 'SiRF').";
|
||||
}
|
||||
|
||||
leaf activated {
|
||||
config false;
|
||||
type boolean;
|
||||
description
|
||||
"Whether gpsd has successfully activated this GPS device.";
|
||||
}
|
||||
|
||||
leaf satellites-visible {
|
||||
config false;
|
||||
type uint8;
|
||||
description
|
||||
"Number of satellites currently visible to the receiver.";
|
||||
}
|
||||
|
||||
leaf satellites-used {
|
||||
config false;
|
||||
type uint8;
|
||||
description
|
||||
"Number of satellites being used for the fix.";
|
||||
}
|
||||
|
||||
leaf fix-mode {
|
||||
config false;
|
||||
type enumeration {
|
||||
enum "none" {
|
||||
description "No fix available";
|
||||
}
|
||||
enum "2d" {
|
||||
description "2D fix (latitude/longitude only)";
|
||||
}
|
||||
enum "3d" {
|
||||
description "3D fix (latitude/longitude/altitude)";
|
||||
}
|
||||
}
|
||||
description
|
||||
"Current GPS fix mode. A 3D fix is required for accurate time.";
|
||||
}
|
||||
|
||||
leaf latitude {
|
||||
config false;
|
||||
type decimal64 {
|
||||
fraction-digits 6;
|
||||
}
|
||||
units "degrees";
|
||||
description
|
||||
"Current latitude in decimal degrees. Negative values are south.";
|
||||
}
|
||||
|
||||
leaf longitude {
|
||||
config false;
|
||||
type decimal64 {
|
||||
fraction-digits 6;
|
||||
}
|
||||
units "degrees";
|
||||
description
|
||||
"Current longitude in decimal degrees. Negative values are west.";
|
||||
}
|
||||
|
||||
leaf altitude {
|
||||
config false;
|
||||
type decimal64 {
|
||||
fraction-digits 1;
|
||||
}
|
||||
units "meters";
|
||||
description
|
||||
"Current altitude above mean sea level in meters.";
|
||||
}
|
||||
|
||||
leaf pps-available {
|
||||
config false;
|
||||
type boolean;
|
||||
description
|
||||
"Whether this GPS device provides a PPS (Pulse Per Second) signal.
|
||||
PPS provides microsecond-level timing accuracy.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,30 @@ module infix-ntp {
|
||||
"RFC 6991: Common YANG Data Types";
|
||||
}
|
||||
|
||||
import ietf-hardware {
|
||||
prefix iehw;
|
||||
reference
|
||||
"RFC 8348: A YANG Data Model for Hardware Management";
|
||||
}
|
||||
|
||||
import infix-hardware {
|
||||
prefix ih;
|
||||
}
|
||||
|
||||
organization "KernelKit";
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Infix deviations and augments to ietf-ntp.";
|
||||
|
||||
revision 2026-02-08 {
|
||||
description
|
||||
"Add GPS/GNSS reference clock support.
|
||||
|
||||
Augments refclock-master with a list of hardware GPS sources that
|
||||
chronyd can use via gpsd's shared memory (SHM) interface. Supports
|
||||
multiple GNSS receivers for redundancy (GPS, GLONASS, Galileo, BeiDou).";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
revision 2025-12-03 {
|
||||
description
|
||||
"Initial release - NTP server support.
|
||||
@@ -70,6 +90,150 @@ module infix-ntp {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* GPS/GNSS reference clock sources
|
||||
*
|
||||
* Augments refclock-master with hardware GPS sources that chronyd
|
||||
* can use via gpsd's shared memory (SHM) interface.
|
||||
*/
|
||||
augment "/ntp:ntp/ntp:refclock-master" {
|
||||
list source {
|
||||
key "receiver";
|
||||
max-elements 2;
|
||||
description
|
||||
"GPS/GNSS hardware reference clock sources.
|
||||
|
||||
Each source references a GPS receiver hardware component and
|
||||
configures how chronyd should use it. The hardware component
|
||||
name (e.g., 'gps0') determines the SHM unit number:
|
||||
- gps0 -> SHM 0 (GPS time), SHM 1 (PPS if available)
|
||||
- gps1 -> SHM 2 (GPS time), SHM 3 (PPS if available)
|
||||
|
||||
Two sources can be configured for redundancy against jamming
|
||||
or hardware failure. The NTP server will evaluate all sources
|
||||
and select the best one.";
|
||||
|
||||
leaf receiver {
|
||||
type leafref {
|
||||
path "/iehw:hardware/iehw:component/iehw:name";
|
||||
}
|
||||
must "derived-from-or-self(/iehw:hardware/iehw:component[iehw:name=current()]/iehw:class, 'ih:gps')" {
|
||||
error-message "Referenced hardware component must be a GPS receiver (class 'ih:gps')";
|
||||
}
|
||||
description
|
||||
"Reference to a GPS receiver hardware component.
|
||||
|
||||
The component name determines the device path (/dev/gpsN)
|
||||
and the SHM unit number for chronyd.";
|
||||
}
|
||||
|
||||
leaf poll {
|
||||
type int8 {
|
||||
range "-6..10";
|
||||
}
|
||||
default "2";
|
||||
description
|
||||
"Polling interval in log2 seconds.
|
||||
|
||||
Common values:
|
||||
- -6 = 1/64 second (for hardware with PPS)
|
||||
- 0 = 1 second
|
||||
- 2 = 4 seconds (default, good for GPS without PPS)
|
||||
- 4 = 16 seconds
|
||||
|
||||
Lower values provide faster response but higher CPU usage.";
|
||||
}
|
||||
|
||||
leaf precision {
|
||||
type decimal64 {
|
||||
fraction-digits 9;
|
||||
range "0.000000001..1.0";
|
||||
}
|
||||
default "0.1";
|
||||
units "seconds";
|
||||
description
|
||||
"Assumed precision of the reference clock in seconds.
|
||||
|
||||
Typical values:
|
||||
- 0.1 (100ms) - GPS without PPS, default
|
||||
- 0.001 (1ms) - GPS with software PPS processing
|
||||
- 0.000001 (1us) - GPS with hardware PPS";
|
||||
}
|
||||
|
||||
leaf offset {
|
||||
type decimal64 {
|
||||
fraction-digits 9;
|
||||
}
|
||||
default "0.0";
|
||||
units "seconds";
|
||||
description
|
||||
"Constant offset correction in seconds.
|
||||
|
||||
Used to compensate for known systematic delays in the
|
||||
GPS receiver or signal path. Positive values mean the
|
||||
GPS time is ahead of true time.";
|
||||
}
|
||||
|
||||
leaf delay {
|
||||
type decimal64 {
|
||||
fraction-digits 9;
|
||||
range "0.0..1.0";
|
||||
}
|
||||
default "0.0";
|
||||
units "seconds";
|
||||
description
|
||||
"Assumed maximum delay from the GPS receiver in seconds.
|
||||
|
||||
This accounts for processing delays in gpsd and the
|
||||
GPS receiver. Default is 0 (let chronyd estimate).";
|
||||
}
|
||||
|
||||
leaf refid {
|
||||
type string {
|
||||
length "1..4";
|
||||
pattern "[A-Z0-9]+";
|
||||
}
|
||||
default "GPS";
|
||||
description
|
||||
"Reference identifier (refid) shown in chronyc tracking.
|
||||
|
||||
Common values:
|
||||
- GPS - Generic GPS
|
||||
- GLO - GLONASS
|
||||
- GAL - Galileo
|
||||
- BDS - BeiDou
|
||||
- PPS - Pulse Per Second (for PPS-only sources)
|
||||
- GNSS - Multi-constellation receiver";
|
||||
}
|
||||
|
||||
leaf prefer {
|
||||
type boolean;
|
||||
default false;
|
||||
description
|
||||
"Prefer this source over others when multiple are available.
|
||||
|
||||
When multiple reference clocks have similar quality, the
|
||||
preferred source will be selected. Useful for designating
|
||||
a primary GPS receiver.";
|
||||
}
|
||||
|
||||
leaf pps {
|
||||
type boolean;
|
||||
default false;
|
||||
description
|
||||
"Use PPS (Pulse Per Second) signal from this source.
|
||||
|
||||
When enabled, chronyd will also configure the PPS SHM
|
||||
unit (2*N+1) for microsecond-level accuracy. The GPS
|
||||
receiver must provide PPS and gpsd must be configured
|
||||
to expose it.
|
||||
|
||||
Note: PPS requires the GPS source for initial time lock,
|
||||
then provides precise time edges.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Additional operational state fields from chronyd
|
||||
* that are not part of the standard ietf-ntp model
|
||||
|
||||
@@ -2,7 +2,7 @@ DISTCLEANFILES = *~ *.d
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
|
||||
sbin_PROGRAMS = statd
|
||||
statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h
|
||||
statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h gpsd.c gpsd.h
|
||||
statd_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE
|
||||
statd_CFLAGS = -W -Wall -Wextra
|
||||
statd_CFLAGS += $(jansson_CFLAGS) $(libyang_CFLAGS) $(sysrepo_CFLAGS)
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
|
||||
/*
|
||||
* Background GPS monitor for statd.
|
||||
*
|
||||
* Maintains a persistent connection to gpsd (localhost:2947) and caches
|
||||
* GPS device status to /run/gps-status.json. The yanger ietf_hardware
|
||||
* module reads this cache instead of spawning gpspipe, avoiding blocking
|
||||
* the operational datastore.
|
||||
*
|
||||
* Activated on SIGHUP when sysrepo running config contains a hardware
|
||||
* component with class infix-hardware:gps, reconnects automatically
|
||||
* if gpsd restarts.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <ev.h>
|
||||
#include <jansson.h>
|
||||
|
||||
#include <srx/common.h>
|
||||
|
||||
#include "gpsd.h"
|
||||
|
||||
static int gps_device_present(void)
|
||||
{
|
||||
struct stat st;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
char path[32];
|
||||
|
||||
snprintf(path, sizeof(path), "/dev/gps%d", i);
|
||||
if (stat(path, &st) == 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cache_write(struct gpsd_ctx *ctx)
|
||||
{
|
||||
char tmp[] = GPSD_CACHE_FILE ".XXXXXX";
|
||||
int fd;
|
||||
|
||||
if (!ctx->cache)
|
||||
return;
|
||||
|
||||
fd = mkstemp(tmp);
|
||||
if (fd < 0) {
|
||||
ERROR("gpsd: failed to create temp file: %s", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
if (json_dumpfd(ctx->cache, fd, JSON_INDENT(2)) < 0) {
|
||||
ERROR("gpsd: failed to write cache");
|
||||
close(fd);
|
||||
unlink(tmp);
|
||||
return;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
if (rename(tmp, GPSD_CACHE_FILE) < 0) {
|
||||
ERROR("gpsd: failed to rename cache: %s", strerror(errno));
|
||||
unlink(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_devices(struct gpsd_ctx *ctx, json_t *msg)
|
||||
{
|
||||
json_t *devices = json_object_get(msg, "devices");
|
||||
size_t i;
|
||||
|
||||
if (!json_is_array(devices))
|
||||
return;
|
||||
|
||||
for (i = 0; i < json_array_size(devices); i++) {
|
||||
json_t *dev = json_array_get(devices, i);
|
||||
const char *path = json_string_value(json_object_get(dev, "path"));
|
||||
json_t *entry, *driver, *activated;
|
||||
|
||||
if (!path)
|
||||
continue;
|
||||
|
||||
entry = json_object_get(ctx->cache, path);
|
||||
if (!entry) {
|
||||
entry = json_object();
|
||||
json_object_set_new(ctx->cache, path, entry);
|
||||
}
|
||||
|
||||
driver = json_object_get(dev, "driver");
|
||||
if (json_is_string(driver))
|
||||
json_object_set(entry, "driver", driver);
|
||||
|
||||
/* activated is a timestamp string when active, absent when not */
|
||||
activated = json_object_get(dev, "activated");
|
||||
if (activated && json_is_string(activated) &&
|
||||
strlen(json_string_value(activated)) > 0)
|
||||
json_object_set_new(entry, "activated", json_true());
|
||||
else
|
||||
json_object_set_new(entry, "activated", json_false());
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_tpv(struct gpsd_ctx *ctx, json_t *msg)
|
||||
{
|
||||
const char *path = json_string_value(json_object_get(msg, "device"));
|
||||
json_t *entry, *val;
|
||||
|
||||
if (!path)
|
||||
return;
|
||||
|
||||
entry = json_object_get(ctx->cache, path);
|
||||
if (!entry) {
|
||||
entry = json_object();
|
||||
json_object_set_new(ctx->cache, path, entry);
|
||||
}
|
||||
|
||||
/* Fix mode: 0=unknown, 1=none, 2=2D, 3=3D */
|
||||
val = json_object_get(msg, "mode");
|
||||
if (json_is_integer(val))
|
||||
json_object_set(entry, "mode", val);
|
||||
|
||||
val = json_object_get(msg, "lat");
|
||||
if (json_is_number(val))
|
||||
json_object_set(entry, "lat", val);
|
||||
|
||||
val = json_object_get(msg, "lon");
|
||||
if (json_is_number(val))
|
||||
json_object_set(entry, "lon", val);
|
||||
|
||||
val = json_object_get(msg, "altHAE");
|
||||
if (json_is_number(val))
|
||||
json_object_set(entry, "altHAE", val);
|
||||
}
|
||||
|
||||
static void handle_sky(struct gpsd_ctx *ctx, json_t *msg)
|
||||
{
|
||||
const char *path = json_string_value(json_object_get(msg, "device"));
|
||||
json_t *entry, *sats;
|
||||
size_t i, visible, used;
|
||||
|
||||
if (!path)
|
||||
return;
|
||||
|
||||
entry = json_object_get(ctx->cache, path);
|
||||
if (!entry) {
|
||||
entry = json_object();
|
||||
json_object_set_new(ctx->cache, path, entry);
|
||||
}
|
||||
|
||||
sats = json_object_get(msg, "satellites");
|
||||
if (!json_is_array(sats))
|
||||
return;
|
||||
|
||||
visible = json_array_size(sats);
|
||||
used = 0;
|
||||
for (i = 0; i < visible; i++) {
|
||||
json_t *sat = json_array_get(sats, i);
|
||||
|
||||
if (json_is_true(json_object_get(sat, "used")))
|
||||
used++;
|
||||
}
|
||||
|
||||
json_object_set_new(entry, "satellites_visible", json_integer(visible));
|
||||
json_object_set_new(entry, "satellites_used", json_integer(used));
|
||||
}
|
||||
|
||||
static void process_line(struct gpsd_ctx *ctx, const char *line)
|
||||
{
|
||||
json_error_t err;
|
||||
json_t *msg;
|
||||
const char *cls;
|
||||
|
||||
msg = json_loads(line, 0, &err);
|
||||
if (!msg)
|
||||
return;
|
||||
|
||||
cls = json_string_value(json_object_get(msg, "class"));
|
||||
if (!cls)
|
||||
goto out;
|
||||
|
||||
if (strcmp(cls, "DEVICES") == 0)
|
||||
handle_devices(ctx, msg);
|
||||
else if (strcmp(cls, "TPV") == 0)
|
||||
handle_tpv(ctx, msg);
|
||||
else if (strcmp(cls, "SKY") == 0)
|
||||
handle_sky(ctx, msg);
|
||||
|
||||
cache_write(ctx);
|
||||
out:
|
||||
json_decref(msg);
|
||||
}
|
||||
|
||||
static void gpsd_disconnect(struct gpsd_ctx *ctx)
|
||||
{
|
||||
if (!ctx->connected)
|
||||
return;
|
||||
|
||||
ev_io_stop(ctx->loop, &ctx->sock_watcher);
|
||||
close(ctx->sock_fd);
|
||||
ctx->sock_fd = -1;
|
||||
ctx->connected = 0;
|
||||
ctx->buf_used = 0;
|
||||
DEBUG("gpsd: disconnected");
|
||||
}
|
||||
|
||||
static void sock_read_cb(struct ev_loop *, ev_io *w, int)
|
||||
{
|
||||
struct gpsd_ctx *ctx = (struct gpsd_ctx *)w->data;
|
||||
char *start, *nl;
|
||||
ssize_t n;
|
||||
|
||||
n = read(ctx->sock_fd, ctx->buf + ctx->buf_used,
|
||||
sizeof(ctx->buf) - ctx->buf_used - 1);
|
||||
if (n <= 0) {
|
||||
if (n < 0 && (errno == EAGAIN || errno == EINTR))
|
||||
return;
|
||||
DEBUG("gpsd: connection lost (%s)", n == 0 ? "EOF" : strerror(errno));
|
||||
gpsd_disconnect(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx->buf_used += n;
|
||||
ctx->buf[ctx->buf_used] = '\0';
|
||||
|
||||
/* Process complete lines (gpsd sends one JSON object per line) */
|
||||
start = ctx->buf;
|
||||
while ((nl = strchr(start, '\n')) != NULL) {
|
||||
*nl = '\0';
|
||||
if (nl > start)
|
||||
process_line(ctx, start);
|
||||
start = nl + 1;
|
||||
}
|
||||
|
||||
/* Shift remaining partial line to beginning of buffer */
|
||||
if (start != ctx->buf) {
|
||||
ctx->buf_used -= (start - ctx->buf);
|
||||
memmove(ctx->buf, start, ctx->buf_used);
|
||||
}
|
||||
|
||||
/* Buffer overflow protection */
|
||||
if (ctx->buf_used >= sizeof(ctx->buf) - 1) {
|
||||
ERROR("gpsd: read buffer overflow, resetting");
|
||||
ctx->buf_used = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int gpsd_connect(struct gpsd_ctx *ctx)
|
||||
{
|
||||
static const char watch_cmd[] = "?WATCH={\"enable\":true,\"json\":true};\n";
|
||||
struct sockaddr_in addr;
|
||||
int fd, flags;
|
||||
|
||||
fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
DEBUG("gpsd: socket(): %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(GPSD_PORT);
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
|
||||
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
DEBUG("gpsd: connect(): %s", strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Enable JSON watch mode (socket still blocking, localhost = instant) */
|
||||
if (write(fd, watch_cmd, strlen(watch_cmd)) < 0) {
|
||||
ERROR("gpsd: failed to send WATCH command: %s", strerror(errno));
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Switch to non-blocking for ev_io */
|
||||
flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags >= 0)
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
/* Clear stale cache data from previous connection */
|
||||
json_object_clear(ctx->cache);
|
||||
|
||||
ctx->sock_fd = fd;
|
||||
ctx->connected = 1;
|
||||
ctx->buf_used = 0;
|
||||
|
||||
ev_io_init(&ctx->sock_watcher, sock_read_cb, fd, EV_READ);
|
||||
ctx->sock_watcher.data = ctx;
|
||||
ev_io_start(ctx->loop, &ctx->sock_watcher);
|
||||
|
||||
INFO("gpsd: connected");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void check_timer_cb(struct ev_loop *, ev_timer *w, int)
|
||||
{
|
||||
struct gpsd_ctx *ctx = (struct gpsd_ctx *)w->data;
|
||||
|
||||
if (ctx->connected)
|
||||
return;
|
||||
|
||||
if (!gps_device_present()) {
|
||||
unlink(GPSD_CACHE_FILE);
|
||||
return;
|
||||
}
|
||||
|
||||
gpsd_connect(ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check sysrepo running config for hardware components with class
|
||||
* infix-hardware:gps. Returns 1 if at least one is found.
|
||||
*/
|
||||
static int has_gps_component(sr_conn_ctx_t *conn)
|
||||
{
|
||||
sr_session_ctx_t *ses;
|
||||
sr_val_t *vals = NULL;
|
||||
size_t cnt = 0;
|
||||
int found;
|
||||
|
||||
if (sr_session_start(conn, SR_DS_RUNNING, &ses))
|
||||
return 0;
|
||||
|
||||
sr_get_items(ses,
|
||||
"/ietf-hardware:hardware/component[class='infix-hardware:gps']/name",
|
||||
0, 0, &vals, &cnt);
|
||||
|
||||
found = cnt > 0;
|
||||
sr_free_values(vals, cnt);
|
||||
sr_session_stop(ses);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static void gpsd_activate(struct gpsd_ctx *ctx)
|
||||
{
|
||||
if (ctx->active)
|
||||
return;
|
||||
|
||||
ctx->active = 1;
|
||||
ev_timer_start(ctx->loop, &ctx->check_timer);
|
||||
|
||||
if (gps_device_present())
|
||||
gpsd_connect(ctx);
|
||||
|
||||
INFO("gpsd: GPS monitoring activated");
|
||||
}
|
||||
|
||||
static void gpsd_deactivate(struct gpsd_ctx *ctx)
|
||||
{
|
||||
if (!ctx->active)
|
||||
return;
|
||||
|
||||
ctx->active = 0;
|
||||
gpsd_disconnect(ctx);
|
||||
ev_timer_stop(ctx->loop, &ctx->check_timer);
|
||||
json_object_clear(ctx->cache);
|
||||
unlink(GPSD_CACHE_FILE);
|
||||
|
||||
INFO("gpsd: GPS monitoring deactivated");
|
||||
}
|
||||
|
||||
void gpsd_reload(struct gpsd_ctx *ctx)
|
||||
{
|
||||
if (has_gps_component(ctx->sr_conn))
|
||||
gpsd_activate(ctx);
|
||||
else
|
||||
gpsd_deactivate(ctx);
|
||||
}
|
||||
|
||||
int gpsd_init(struct gpsd_ctx *ctx, struct ev_loop *loop, sr_conn_ctx_t *conn)
|
||||
{
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
ctx->loop = loop;
|
||||
ctx->sock_fd = -1;
|
||||
ctx->sr_conn = conn;
|
||||
|
||||
ctx->cache = json_object();
|
||||
if (!ctx->cache) {
|
||||
ERROR("gpsd: failed to create cache object");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ev_timer_init(&ctx->check_timer, check_timer_cb,
|
||||
GPSD_CHECK_INTERVAL, GPSD_CHECK_INTERVAL);
|
||||
ctx->check_timer.data = ctx;
|
||||
/* Timer not started here -- gpsd_reload() activates when needed */
|
||||
|
||||
INFO("gpsd: GPS monitor initialized");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gpsd_exit(struct gpsd_ctx *ctx)
|
||||
{
|
||||
gpsd_deactivate(ctx);
|
||||
|
||||
if (ctx->cache) {
|
||||
json_decref(ctx->cache);
|
||||
ctx->cache = NULL;
|
||||
}
|
||||
|
||||
INFO("gpsd: GPS monitor stopped");
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
|
||||
#ifndef STATD_GPSD_H_
|
||||
#define STATD_GPSD_H_
|
||||
|
||||
#include <ev.h>
|
||||
#include <jansson.h>
|
||||
#include <sysrepo.h>
|
||||
|
||||
#define GPSD_CACHE_FILE "/run/gps-status.json"
|
||||
#define GPSD_PORT 2947
|
||||
#define GPSD_CHECK_INTERVAL 10.0 /* Seconds between device/connection checks */
|
||||
#define GPSD_READ_BUFSZ 4096
|
||||
|
||||
struct gpsd_ctx {
|
||||
struct ev_loop *loop;
|
||||
ev_timer check_timer; /* Periodic check for GPS devices / reconnect */
|
||||
ev_io sock_watcher; /* Read watcher on gpsd socket */
|
||||
int sock_fd; /* TCP socket to gpsd */
|
||||
char buf[GPSD_READ_BUFSZ]; /* Line accumulation buffer */
|
||||
size_t buf_used;
|
||||
json_t *cache; /* Accumulated GPS data, keyed by device path */
|
||||
int connected;
|
||||
sr_conn_ctx_t *sr_conn; /* Sysrepo connection for config queries */
|
||||
int active; /* GPS monitoring enabled (config has gps component) */
|
||||
};
|
||||
|
||||
int gpsd_init(struct gpsd_ctx *ctx, struct ev_loop *loop, sr_conn_ctx_t *conn);
|
||||
void gpsd_reload(struct gpsd_ctx *ctx);
|
||||
void gpsd_exit(struct gpsd_ctx *ctx);
|
||||
|
||||
#endif
|
||||
@@ -2132,6 +2132,7 @@ def show_hardware(json):
|
||||
usb_ports = [c for c in components if c.get("class") == "infix-hardware:usb"]
|
||||
sensors = [c for c in components if c.get("class") == "iana-hardware:sensor"]
|
||||
wifi_radios = [c for c in components if c.get("class") == "infix-hardware:wifi"]
|
||||
gps_receivers = [c for c in components if c.get("class") == "infix-hardware:gps"]
|
||||
|
||||
width = max(PadSensor.table_width(), 62)
|
||||
|
||||
@@ -2203,6 +2204,44 @@ def show_hardware(json):
|
||||
radios_table.row(phy, manufacturer, bands_str, standard_str, max_ap)
|
||||
radios_table.print()
|
||||
|
||||
if gps_receivers:
|
||||
Decore.title("GPS/GNSS Receivers", width)
|
||||
|
||||
for component in gps_receivers:
|
||||
gps = component.get("infix-hardware:gps-receiver", {})
|
||||
name = component.get("name", "unknown")
|
||||
device = gps.get("device", "N/A")
|
||||
driver = gps.get("driver", "Unknown")
|
||||
fix = gps.get("fix-mode", "none")
|
||||
activated = gps.get("activated", False)
|
||||
|
||||
print(f"{'Name':<20}: {name}")
|
||||
print(f"{'Device':<20}: {device}")
|
||||
print(f"{'Driver':<20}: {driver}")
|
||||
print(f"{'Status':<20}: {'Active' if activated else 'Inactive'}")
|
||||
print(f"{'Fix':<20}: {fix.upper()}")
|
||||
|
||||
sat_vis = gps.get("satellites-visible")
|
||||
sat_used = gps.get("satellites-used")
|
||||
if sat_vis is not None:
|
||||
print(f"{'Satellites':<20}: {sat_used}/{sat_vis} (used/visible)")
|
||||
|
||||
lat = gps.get("latitude")
|
||||
lon = gps.get("longitude")
|
||||
alt = gps.get("altitude")
|
||||
if lat is not None and lon is not None:
|
||||
lat_f = float(lat)
|
||||
lon_f = float(lon)
|
||||
lat_dir = "N" if lat_f >= 0 else "S"
|
||||
lon_dir = "E" if lon_f >= 0 else "W"
|
||||
pos = f"{abs(lat_f):.6f}{lat_dir} {abs(lon_f):.6f}{lon_dir}"
|
||||
if alt is not None:
|
||||
pos += f" {alt}m"
|
||||
print(f"{'Position':<20}: {pos}")
|
||||
|
||||
pps = gps.get("pps-available", False)
|
||||
print(f"{'PPS':<20}: {'Available' if pps else 'Not available'}")
|
||||
|
||||
if usb_ports:
|
||||
Decore.title("USB Ports", width)
|
||||
|
||||
@@ -2509,9 +2548,16 @@ def show_ntp(json, address=None):
|
||||
show_ntp_source_detail_single(matching[0], False)
|
||||
return
|
||||
|
||||
# Check for GPS/GNSS hardware reference clocks
|
||||
hw_components = json.get("ietf-hardware:hardware", {}).get("component", [])
|
||||
gps_sources = [c for c in hw_components if c.get("class") == "infix-hardware:gps"]
|
||||
|
||||
if is_server:
|
||||
if sources:
|
||||
print(f"{'Mode':<20}: Relay (no local reference clock)")
|
||||
elif gps_sources:
|
||||
gps_names = ", ".join(c.get("name", "?") for c in gps_sources)
|
||||
print(f"{'Mode':<20}: Server (GPS reference clock: {gps_names})")
|
||||
else:
|
||||
print(f"{'Mode':<20}: Server (local reference clock)")
|
||||
print(f"{'Port':<20}: {port}")
|
||||
@@ -2808,10 +2854,43 @@ def show_ntp_source(json, address=None):
|
||||
return
|
||||
|
||||
associations = ntp_data.get("associations", {}).get("association", [])
|
||||
if not associations:
|
||||
|
||||
# Check for GPS/GNSS reference clock sources from hardware data
|
||||
hw_components = json.get("ietf-hardware:hardware", {}).get("component", [])
|
||||
gps_sources = [c for c in hw_components if c.get("class") == "infix-hardware:gps"]
|
||||
|
||||
# Show GPS reference clocks
|
||||
if gps_sources:
|
||||
clock_state = ntp_data.get("clock-state", {}).get("system-status", {})
|
||||
clock_refid = clock_state.get("clock-refid", "").strip()
|
||||
|
||||
for gps in gps_sources:
|
||||
gps_data = gps.get("infix-hardware:gps-receiver", {})
|
||||
name = gps.get("name", "unknown")
|
||||
driver = gps_data.get("driver", "Unknown")
|
||||
fix = gps_data.get("fix-mode", "none")
|
||||
activated = gps_data.get("activated", False)
|
||||
sat_used = gps_data.get("satellites-used", 0)
|
||||
sat_vis = gps_data.get("satellites-visible", 0)
|
||||
|
||||
# Determine if this GPS is the current sync source
|
||||
is_synced = clock_refid in ("GPS", "PPS", "GLO", "GAL", "BDS", "GNSS")
|
||||
|
||||
state = "selected" if is_synced else ("active" if activated else "inactive")
|
||||
print(f"{'Reference Clock':<20}: {name} ({driver})")
|
||||
print(f"{'Status':<20}: {state}")
|
||||
print(f"{'Fix Mode':<20}: {fix.upper()}")
|
||||
if sat_vis:
|
||||
print(f"{'Satellites':<20}: {sat_used}/{sat_vis} (used/visible)")
|
||||
print()
|
||||
|
||||
if not associations and not gps_sources:
|
||||
print("No NTP associations found.")
|
||||
return
|
||||
|
||||
if not associations:
|
||||
return
|
||||
|
||||
# If address specified, show detailed view for that association
|
||||
if address:
|
||||
matching = [a for a in associations if a.get('address') == address]
|
||||
|
||||
@@ -667,6 +667,85 @@ def wifi_radio_components():
|
||||
return components
|
||||
|
||||
|
||||
def gps_receiver_components():
|
||||
"""Discover GPS/GNSS receivers and populate operational state.
|
||||
|
||||
GPS devices are discovered via /dev/gps* symlinks (created by udev rules).
|
||||
Status is read from /run/gps-status.json, a cache maintained by statd's
|
||||
background GPS monitor (gpsd.c) which streams data from gpsd without
|
||||
blocking the operational datastore.
|
||||
"""
|
||||
components = []
|
||||
|
||||
# Discover GPS devices via /dev/gps* symlinks (created by udev rules)
|
||||
gps_devices = {}
|
||||
for i in range(4):
|
||||
dev_path = f"/dev/gps{i}"
|
||||
if not HOST.exists(dev_path):
|
||||
continue
|
||||
# Resolve symlink to actual device (for matching gpsd cache keys)
|
||||
actual = HOST.run(("readlink", "-f", dev_path), default="").strip()
|
||||
gps_devices[actual] = {
|
||||
"name": f"gps{i}",
|
||||
"symlink": dev_path,
|
||||
}
|
||||
|
||||
if not gps_devices:
|
||||
return components
|
||||
|
||||
# Read cached GPS status from statd background monitor
|
||||
cache = HOST.read_json("/run/gps-status.json", {})
|
||||
|
||||
# Build hardware components for each discovered GPS device
|
||||
for actual_path, dev in gps_devices.items():
|
||||
name = dev["name"]
|
||||
component = {
|
||||
"name": name,
|
||||
"class": "infix-hardware:gps",
|
||||
"description": "GPS/GNSS Receiver"
|
||||
}
|
||||
|
||||
gps_data = {}
|
||||
gps_data["device"] = dev["symlink"]
|
||||
|
||||
# Look up cached status by actual device path
|
||||
info = cache.get(actual_path, {})
|
||||
|
||||
if info.get("driver"):
|
||||
gps_data["driver"] = info["driver"]
|
||||
gps_data["activated"] = bool(info.get("activated"))
|
||||
|
||||
mode = info.get("mode", 0)
|
||||
if mode == 2:
|
||||
gps_data["fix-mode"] = "2d"
|
||||
elif mode == 3:
|
||||
gps_data["fix-mode"] = "3d"
|
||||
else:
|
||||
gps_data["fix-mode"] = "none"
|
||||
|
||||
if "lat" in info:
|
||||
gps_data["latitude"] = f"{float(info['lat']):.6f}"
|
||||
if "lon" in info:
|
||||
gps_data["longitude"] = f"{float(info['lon']):.6f}"
|
||||
if "altHAE" in info:
|
||||
gps_data["altitude"] = f"{float(info['altHAE']):.1f}"
|
||||
|
||||
if "satellites_visible" in info:
|
||||
gps_data["satellites-visible"] = int(info["satellites_visible"])
|
||||
gps_data["satellites-used"] = int(info.get("satellites_used", 0))
|
||||
|
||||
# Check for PPS device availability
|
||||
pps_path = f"/dev/pps{name.replace('gps', '')}"
|
||||
gps_data["pps-available"] = HOST.exists(pps_path)
|
||||
|
||||
if gps_data:
|
||||
component["infix-hardware:gps-receiver"] = gps_data
|
||||
|
||||
components.append(component)
|
||||
|
||||
return components
|
||||
|
||||
|
||||
def operational():
|
||||
systemjson = HOST.read_json("/run/system.json", {})
|
||||
|
||||
@@ -679,6 +758,7 @@ def operational():
|
||||
hwmon_sensor_components() +
|
||||
thermal_sensor_components() +
|
||||
wifi_radio_components() +
|
||||
gps_receiver_components() +
|
||||
[],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -5,7 +5,39 @@ from .host import HOST
|
||||
|
||||
|
||||
def add_ntp_associations(out):
|
||||
"""Add NTP association information from chronyc sources and sourcestats"""
|
||||
"""Add NTP association information from chronyc sources and sourcestats.
|
||||
|
||||
The chronyc -c sources output is CSV with the following fields:
|
||||
[0] Mode indicator:
|
||||
^ server (we're a client to this source)
|
||||
= peer (symmetric mode)
|
||||
# local reference clock (GPS, PPS, etc.) - skipped, no IP address
|
||||
[1] State indicator:
|
||||
* selected (current sync source)
|
||||
+ candidate
|
||||
- outlier
|
||||
? unusable
|
||||
x falseticker
|
||||
~ unstable
|
||||
[2] Address (IP address or refclock name like "GPS")
|
||||
[3] Stratum
|
||||
[4] Poll interval (log2 seconds)
|
||||
[5] Reach (octal reachability register)
|
||||
[6] LastRx (seconds since last response)
|
||||
[7] Last offset (seconds)
|
||||
[8] Offset at last update (seconds)
|
||||
[9] Error estimate (seconds)
|
||||
|
||||
The chronyc -c sourcestats output is CSV with:
|
||||
[0] Address
|
||||
[1] NP (number of sample points)
|
||||
[2] NR (number of runs)
|
||||
[3] Span (seconds)
|
||||
[4] Frequency (ppm)
|
||||
[5] Freq Skew (ppm)
|
||||
[6] Offset (seconds)
|
||||
[7] Std Dev (seconds)
|
||||
"""
|
||||
try:
|
||||
# Get basic source information
|
||||
sources_data = HOST.run_multiline(["chronyc", "-c", "sources"], [])
|
||||
@@ -44,6 +76,10 @@ def add_ntp_associations(out):
|
||||
continue
|
||||
|
||||
mode_indicator = parts[0]
|
||||
# Skip reference clocks (mode "#") as they have names like "GPS" instead of IP addresses
|
||||
if mode_indicator == "#":
|
||||
continue
|
||||
|
||||
state_indicator = parts[1]
|
||||
address = parts[2]
|
||||
stratum = int(parts[3])
|
||||
@@ -156,7 +192,9 @@ def add_ntp_clock_state(out):
|
||||
refid_name = parts[1]
|
||||
|
||||
if refid_name:
|
||||
system_status["clock-refid"] = refid_name
|
||||
# NTP refids are always 4 bytes; chronyc strips trailing padding.
|
||||
# YANG typedef 'refid' requires exactly length 4 for strings.
|
||||
system_status["clock-refid"] = refid_name.ljust(4)[:4]
|
||||
elif refid_ip and len(refid_ip) == 8:
|
||||
try:
|
||||
a = int(refid_ip[0:2], 16)
|
||||
|
||||
@@ -36,6 +36,29 @@ def get_boot_order():
|
||||
return order
|
||||
|
||||
def add_ntp(out):
|
||||
"""Add NTP source information from chronyc sources.
|
||||
|
||||
The chronyc -c sources output is CSV with the following fields:
|
||||
[0] Mode indicator:
|
||||
^ server (we're a client to this source)
|
||||
= peer (symmetric mode)
|
||||
# local reference clock (GPS, PPS, etc.) - skipped, no IP address
|
||||
[1] State indicator:
|
||||
* selected (current sync source)
|
||||
+ candidate
|
||||
- outlier
|
||||
? unusable
|
||||
x falseticker
|
||||
~ unstable
|
||||
[2] Address (IP address or refclock name like "GPS")
|
||||
[3] Stratum
|
||||
[4] Poll interval (log2 seconds)
|
||||
[5] Reach (octal reachability register)
|
||||
[6] LastRx (seconds since last response)
|
||||
[7] Last offset (seconds)
|
||||
[8] Offset at last update (seconds)
|
||||
[9] Error estimate (seconds)
|
||||
"""
|
||||
data = HOST.run_multiline(["chronyc", "-c", "sources"], [])
|
||||
source = []
|
||||
state_mode_map = {
|
||||
@@ -54,6 +77,9 @@ def add_ntp(out):
|
||||
for line in data:
|
||||
src = {}
|
||||
line = line.split(',')
|
||||
# Skip reference clocks (mode "#") as they have names like "GPS" instead of IP addresses
|
||||
if line[0] == "#":
|
||||
continue
|
||||
src["address"] = line[2]
|
||||
src["mode"] = state_mode_map[line[0]]
|
||||
src["state"] = source_state_map[line[1]]
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
1692853200 00:1a:2b:3c:4d:5e 192.168.1.100 hostname01 01:00:1a:2b:3c:4d:5e
|
||||
1692853800 00:1a:2b:3c:4d:5f 192.168.1.101 hostname02 *
|
||||
1692854400 00:1a:2b:3c:4d:60 192.168.1.102 hostname03 01:00:1a:2b:3c:4d:60
|
||||
+20
-1
@@ -30,6 +30,7 @@
|
||||
|
||||
#include "shared.h"
|
||||
#include "journal.h"
|
||||
#include "gpsd.h"
|
||||
|
||||
/* New kernel feature, not in sys/mman.h yet */
|
||||
#ifndef MFD_NOEXEC_SEAL
|
||||
@@ -69,6 +70,7 @@ struct statd {
|
||||
sr_conn_ctx_t *sr_conn; /* Connection (owns YANG context) */
|
||||
struct ev_loop *ev_loop;
|
||||
struct journal_ctx journal; /* Journal thread context */
|
||||
struct gpsd_ctx gpsd; /* GPS monitor context */
|
||||
};
|
||||
|
||||
static int ly_add_yanger_data(const struct ly_ctx *ctx, struct lyd_node **parent,
|
||||
@@ -350,6 +352,14 @@ static void sigusr1_cb(struct ev_loop *, struct ev_signal *, int)
|
||||
debug ^= 1;
|
||||
}
|
||||
|
||||
static void sighup_cb(struct ev_loop *, struct ev_signal *w, int)
|
||||
{
|
||||
struct statd *statd = (struct statd *)w->data;
|
||||
|
||||
INFO("SIGHUP received, reloading GPS config");
|
||||
gpsd_reload(&statd->gpsd);
|
||||
}
|
||||
|
||||
static void sr_event_cb(struct ev_loop *, struct ev_io *w, int)
|
||||
{
|
||||
struct sub *sub = (struct sub *)w->data;
|
||||
@@ -452,7 +462,7 @@ static int subscribe_to_all(struct statd *statd)
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct ev_signal sigint_watcher, sigusr1_watcher;
|
||||
struct ev_signal sigint_watcher, sigusr1_watcher, sighup_watcher;
|
||||
int log_opts = LOG_PID | LOG_NDELAY;
|
||||
struct statd statd = {};
|
||||
const char *env;
|
||||
@@ -513,6 +523,10 @@ int main(int argc, char *argv[])
|
||||
sigusr1_watcher.data = &statd;
|
||||
ev_signal_start(statd.ev_loop, &sigusr1_watcher);
|
||||
|
||||
ev_signal_init(&sighup_watcher, sighup_cb, SIGHUP);
|
||||
sighup_watcher.data = &statd;
|
||||
ev_signal_start(statd.ev_loop, &sighup_watcher);
|
||||
|
||||
err = journal_start(&statd.journal, statd.sr_query_ses);
|
||||
if (err) {
|
||||
sr_session_stop(statd.sr_query_ses);
|
||||
@@ -521,6 +535,10 @@ int main(int argc, char *argv[])
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (gpsd_init(&statd.gpsd, statd.ev_loop, statd.sr_conn))
|
||||
INFO("GPS monitoring not available");
|
||||
gpsd_reload(&statd.gpsd);
|
||||
|
||||
/* Signal readiness to Finit */
|
||||
pidfile(NULL);
|
||||
|
||||
@@ -530,6 +548,7 @@ int main(int argc, char *argv[])
|
||||
/* We should never get here during normal operation */
|
||||
INFO("Status daemon shutting down");
|
||||
|
||||
gpsd_exit(&statd.gpsd);
|
||||
journal_stop(&statd.journal);
|
||||
|
||||
unsub_to_all(&statd);
|
||||
|
||||
Reference in New Issue
Block a user