From 57961ac0a5f02a6fa771b59ce2e7009ae63c3c3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= Date: Wed, 11 Feb 2026 13:59:27 +0100 Subject: [PATCH] GPS: Refactor, do not use hotplug depend and move to finit Also remove caching of GPS data in operational, gpsd now depend on its GPS device instead of hotplug magic. --- package/feature-gps/25-gpsd.rules | 42 +- package/feature-gps/Config.in | 3 - package/feature-gps/feature-gps.mk | 7 + patches/gpsd/0001-Always-log-to-syslog.patch | 41 ++ src/confd/src/hardware.c | 75 +++- src/statd/Makefile.am | 2 +- src/statd/gpsd.c | 415 ------------------- src/statd/gpsd.h | 32 -- src/statd/python/cli_pretty/cli_pretty.py | 32 +- src/statd/python/yanger/ietf_hardware.py | 128 ++++-- src/statd/statd.c | 20 +- 11 files changed, 266 insertions(+), 531 deletions(-) create mode 100644 patches/gpsd/0001-Always-log-to-syslog.patch delete mode 100644 src/statd/gpsd.c delete mode 100644 src/statd/gpsd.h diff --git a/package/feature-gps/25-gpsd.rules b/package/feature-gps/25-gpsd.rules index 906936b4..5e8fd9a6 100644 --- a/package/feature-gps/25-gpsd.rules +++ b/package/feature-gps/25-gpsd.rules @@ -7,59 +7,61 @@ 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" +# ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="gps%n" # 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" +ATTRS{idVendor}=="0557", ATTRS{idProduct}=="2008", SYMLINK+="gps%n" # 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" +ATTRS{idVendor}=="067b", ATTRS{idProduct}=="aaa0", SYMLINK+="gps%n" # 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" +# ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="gps%n" # 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" +ATTRS{idVendor}=="1163", ATTRS{idProduct}=="0100", SYMLINK+="gps%n" # Cypress M8/CY7C64013 (DeLorme LT-40) -ATTRS{idVendor}=="1163", ATTRS{idProduct}=="0200", SYMLINK+="gps%n", RUN+="/usr/lib/udev/gpsd.hotplug" +ATTRS{idVendor}=="1163", ATTRS{idProduct}=="0200", SYMLINK+="gps%n" # 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" +ATTRS{idVendor}=="091e", ATTRS{idProduct}=="0003", SYMLINK+="gps%n" # 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" +#ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="gps%n" # 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" +#ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea71", SYMLINK+="gps%n" # 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" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a5", SYMLINK+="gps%n" # 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" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a6", SYMLINK+="gps%n" # 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" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a7", SYMLINK+="gps%n" # 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" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a8", SYMLINK+="gps%n" # 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" +ATTRS{idVendor}=="1546", ATTRS{idProduct}=="01a9", SYMLINK+="gps%n" # 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" +ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="3329", SYMLINK+="gps%n" # 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" +ATTRS{interface}=="Telit Wireless Module Port", ATTRS{bInterfaceNumber}=="06", SYMLINK+="gps%n" -# u-blox AG, u-blox 8 (tested with u-blox8 GNSS Mouse Receiver / GR-801) [linux module: cdc_acm] +# 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" +#ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", SYMLINK+="gps%n" LABEL="gpsd_rules_end" + +# Virtio serial GPS ports (QEMU emulated GPS for testing) +SUBSYSTEM=="virtio-ports", ATTR{name}=="gps0", SYMLINK+="gps0" +SUBSYSTEM=="virtio-ports", ATTR{name}=="gps1", SYMLINK+="gps1" diff --git a/package/feature-gps/Config.in b/package/feature-gps/Config.in index a222e521..c16ba922 100644 --- a/package/feature-gps/Config.in +++ b/package/feature-gps/Config.in @@ -1,9 +1,6 @@ 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 diff --git a/package/feature-gps/feature-gps.mk b/package/feature-gps/feature-gps.mk index 6da640c9..77083148 100644 --- a/package/feature-gps/feature-gps.mk +++ b/package/feature-gps/feature-gps.mk @@ -6,13 +6,20 @@ FEATURE_GPS_PACKAGE_VERSION = 1.0 FEATURE_GPS_PACKAGE_LICENSE = MIT +FEATURE_GPS_DEPENDENCIES += gpsd define FEATURE_GPS_INSTALL_TARGET_CMDS $(INSTALL) -D -m 0644 $(FEATURE_GPS_PKGDIR)/gpsd.default \ $(TARGET_DIR)/etc/default/gpsd +endef + +# Install custom udev rules as a gpsd post-install hook to ensure +# they are not overwritten by gpsd during parallel or incremental builds. +define FEATURE_GPS_INSTALL_UDEV_RULES $(INSTALL) -D -m 0644 $(FEATURE_GPS_PKGDIR)/25-gpsd.rules \ $(TARGET_DIR)/usr/lib/udev/rules.d/25-gpsd.rules endef +GPSD_POST_INSTALL_TARGET_HOOKS += FEATURE_GPS_INSTALL_UDEV_RULES define FEATURE_GPS_LINUX_CONFIG_FIXUPS $(call KCONFIG_SET_OPT,CONFIG_USB_ACM,m) diff --git a/patches/gpsd/0001-Always-log-to-syslog.patch b/patches/gpsd/0001-Always-log-to-syslog.patch new file mode 100644 index 00000000..09940ff1 --- /dev/null +++ b/patches/gpsd/0001-Always-log-to-syslog.patch @@ -0,0 +1,41 @@ +From 9ba3bc688c45a0c9e761c6c8bd0be83d6570c9dc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= +Date: Fri, 27 Feb 2026 16:58:12 +0100 +Subject: [PATCH] Always log to syslog +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit +Organization: Wires + +Signed-off-by: Mattias Walström +--- + gpsd/libgpsd_core.c | 13 ++----------- + 1 file changed, 2 insertions(+), 11 deletions(-) + +diff --git a/gpsd/libgpsd_core.c b/gpsd/libgpsd_core.c +index 982f822b8..c53c64838 100644 +--- a/gpsd/libgpsd_core.c ++++ b/gpsd/libgpsd_core.c +@@ -180,17 +180,8 @@ static void gpsd_vlog(const int errlevel, + // this was crazy expensive, just fix the bad log calls + // gps_visibilize(outbuf, outlen, buf, strlen(buf)); + +- if (getpid() == getsid(getpid())) { +- // I think this calls syslog() only when daemonized +- syslog(level, "%s", outbuf); +- } else if (NULL != errout->report) { +- // we are a thread, use report()? +- // FIXME: is POSIX syslog() thread safe? +- errout->report(outbuf); +- } else { +- // foreground, use stderr? +- (void)fputs(outbuf, stderr); +- } ++ // always log to syslog, even with -N ++ syslog(level, "%s", outbuf); + gpsd_release_reporting_lock(); + #endif // !SQUELCH_ENABLE + } +-- +2.43.0 + diff --git a/src/confd/src/hardware.c b/src/confd/src/hardware.c index 73c11c8c..65114493 100644 --- a/src/confd/src/hardware.c +++ b/src/confd/src/hardware.c @@ -17,6 +17,9 @@ #define XPATH_BASE_ "/ietf-hardware:hardware" #define HOSTAPD_CONF "/etc/hostapd-%s.conf" #define HOSTAPD_CONF_NEXT HOSTAPD_CONF"+" +#define GPSD_CONF "/etc/finit.d/available/gpsd.conf" +#define GPSD_CONF_NEXT GPSD_CONF"+" +#define GPSD_MAX_DEVICES 4 static int dir_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { @@ -593,6 +596,7 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l { struct lyd_node *difs = NULL, *dif = NULL; int rc = SR_ERR_OK; + int gps_changed = 0; if (!lydx_find_xpathf(diff, XPATH_BASE_)) return SR_ERR_OK; @@ -707,13 +711,76 @@ int hardware_change(sr_session_ctx_t *session, struct lyd_node *config, struct l wifi_iface_count = 0; } } else if (!strcmp(class, "infix-hardware:gps")) { - if (event != SR_EV_DONE) - continue; - - systemf("initctl -nbq touch statd"); + gps_changed = 1; } } + if (gps_changed) { + const char *gps_names[GPSD_MAX_DEVICES] = { NULL }; + struct lyd_node *cifs, *comp; + int num_gps = 0; + + switch (event) { + case SR_EV_UPDATE: + case SR_EV_ENABLED: + case SR_EV_RPC: + break; + + case SR_EV_ABORT: + unlink(GPSD_CONF_NEXT); + break; + + case SR_EV_CHANGE: + cifs = lydx_get_descendant(config, "hardware", "component", NULL); + LYX_LIST_FOR_EACH(cifs, comp, "component") { + const char *cls = lydx_get_cattr(comp, "class"); + + if (strcmp(cls, "infix-hardware:gps")) + continue; + if (num_gps < GPSD_MAX_DEVICES) + gps_names[num_gps] = lydx_get_cattr(comp, "name"); + else + return SR_ERR_INTERNAL; + num_gps++; + } + if (num_gps > 0) { + FILE *fp; + + fp = fopen(GPSD_CONF_NEXT, "w"); + if (!fp) { + ERROR("Could not open " GPSD_CONF_NEXT); + return SR_ERR_INTERNAL; + } + int i; + + fprintf(fp, "# Generated by confd, do not edit.\n"); + fprintf(fp, "service"); + fprintf(fp, " "); + fprintf(fp, " pid:!/run/gpsd.pid"); + fprintf(fp, " env:-/etc/default/gpsd \\\n"); + fprintf(fp, "\t[2345] gpsd -n"); + for (i = 0; i < num_gps && i < GPSD_MAX_DEVICES; i++) + fprintf(fp, " /dev/%s", gps_names[i]); + fprintf(fp, " -P /run/gpsd.pid -- GPS Daemon\n"); + fclose(fp); + } + break; + + case SR_EV_DONE: + if (fexist(GPSD_CONF_NEXT)) { + unlink(GPSD_CONF); + rename(GPSD_CONF_NEXT, GPSD_CONF); + systemf("initctl -nbq enable gpsd"); + } else { + unlink(GPSD_CONF); + systemf("initctl -nbq disable gpsd"); + } + break; + } + } err: return rc; diff --git a/src/statd/Makefile.am b/src/statd/Makefile.am index 160495b2..9dd4fa4d 100644 --- a/src/statd/Makefile.am +++ b/src/statd/Makefile.am @@ -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 gpsd.c gpsd.h +statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h statd_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE statd_CFLAGS = -W -Wall -Wextra statd_CFLAGS += $(jansson_CFLAGS) $(libyang_CFLAGS) $(sysrepo_CFLAGS) diff --git a/src/statd/gpsd.c b/src/statd/gpsd.c deleted file mode 100644 index 770b4dbe..00000000 --- a/src/statd/gpsd.c +++ /dev/null @@ -1,415 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#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"); -} diff --git a/src/statd/gpsd.h b/src/statd/gpsd.h deleted file mode 100644 index 94fdc71b..00000000 --- a/src/statd/gpsd.h +++ /dev/null @@ -1,32 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause */ - -#ifndef STATD_GPSD_H_ -#define STATD_GPSD_H_ - -#include -#include -#include - -#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 diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index 54bc5e05..e4a19c9d 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -2207,24 +2207,27 @@ def show_hardware(json): if gps_receivers: Decore.title("GPS/GNSS Receivers", width) + gps_table = SimpleTable([ + Column('NAME'), + Column('DEVICE'), + Column('STATUS'), + Column('FIX'), + Column('SATELLITES'), + Column('POSITION', flexible=True), + Column('PPS') + ], min_width=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()}") + status = "Active" if activated else "Inactive" + fix = gps.get("fix-mode", "none").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)") + satellites = f"{sat_used}/{sat_vis}" if sat_vis is not None else "N/A" lat = gps.get("latitude") lon = gps.get("longitude") @@ -2237,11 +2240,14 @@ def show_hardware(json): 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}") + else: + pos = "N/A" - pps = gps.get("pps-available", False) - print(f"{'PPS':<20}: {'Available' if pps else 'Not available'}") + pps = "Yes" if gps.get("pps-available", False) else "No" + gps_table.row(name, device, status, fix, satellites, pos, pps) + + gps_table.print() if usb_ports: Decore.title("USB Ports", width) diff --git a/src/statd/python/yanger/ietf_hardware.py b/src/statd/python/yanger/ietf_hardware.py index b68493c4..f1fda828 100644 --- a/src/statd/python/yanger/ietf_hardware.py +++ b/src/statd/python/yanger/ietf_hardware.py @@ -667,13 +667,53 @@ def wifi_radio_components(): return components +def _gpsd_poll(): + """Query gpsd for current state via ?POLL command. + + Returns a dict keyed by device path with tpv/sky data merged, + or empty dict on failure. + """ + import json + import socket + + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(0.5) + sock.connect(("127.0.0.1", 2947)) + + # Read VERSION banner + sock.recv(4096) + + # Send POLL request + sock.sendall(b'?WATCH={"enable":true,"json":true};\n?POLL;\n') + + # Read until we get the POLL response + buf = b"" + for _ in range(5): + chunk = sock.recv(4096) + if not chunk: + break + buf += chunk + for line in buf.decode(errors="replace").splitlines(): + try: + msg = json.loads(line) + except (json.JSONDecodeError, ValueError): + continue + if msg.get("class") == "POLL": + sock.close() + return msg + sock.close() + except (OSError, socket.error): + pass + + return {} + + 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. + GPS devices are discovered via /dev/gps* symlinks (created by udev + rules). Status is queried live from gpsd via the ?POLL command. """ components = [] @@ -683,7 +723,6 @@ def gps_receiver_components(): 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}", @@ -693,12 +732,24 @@ def gps_receiver_components(): if not gps_devices: return components - # Read cached GPS status from statd background monitor - cache = HOST.read_json("/run/gps-status.json", {}) + # Query gpsd directly for current state + poll = _gpsd_poll() + active = poll.get("active", 0) + + # Index TPV and SKY responses by device path + tpv_by_dev = {} + for tpv in poll.get("tpv", []): + dev = tpv.get("device", "") + tpv_by_dev[dev] = tpv + + sky_by_dev = {} + for sky in poll.get("sky", []): + dev = sky.get("device", "") + sky_by_dev[dev] = sky - # Build hardware components for each discovered GPS device for actual_path, dev in gps_devices.items(): name = dev["name"] + symlink = dev["symlink"] component = { "name": name, "class": "infix-hardware:gps", @@ -706,16 +757,22 @@ def gps_receiver_components(): } gps_data = {} - gps_data["device"] = dev["symlink"] + gps_data["device"] = symlink - # Look up cached status by actual device path - info = cache.get(actual_path, {}) + # Match by resolved path or symlink (fallback to single report) + tpv = tpv_by_dev.get(actual_path, tpv_by_dev.get(symlink, {})) + sky = sky_by_dev.get(actual_path, sky_by_dev.get(symlink, {})) + if not tpv and len(tpv_by_dev) == 1: + tpv = next(iter(tpv_by_dev.values())) + if not sky and len(sky_by_dev) == 1: + sky = next(iter(sky_by_dev.values())) - if info.get("driver"): - gps_data["driver"] = info["driver"] - gps_data["activated"] = bool(info.get("activated")) + gps_data["activated"] = active > 0 and bool(tpv) - mode = info.get("mode", 0) + if tpv.get("driver"): + gps_data["driver"] = tpv["driver"] + + mode = tpv.get("mode", 0) if mode == 2: gps_data["fix-mode"] = "2d" elif mode == 3: @@ -723,16 +780,39 @@ def gps_receiver_components(): 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 "lat" in tpv: + gps_data["latitude"] = f"{float(tpv['lat']):.6f}" + if "lon" in tpv: + gps_data["longitude"] = f"{float(tpv['lon']):.6f}" + if "altHAE" in tpv: + gps_data["altitude"] = f"{float(tpv['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)) + sat_vis = 0 + sat_used = 0 + + sats = sky.get("satellites", []) + if isinstance(sats, list): + sat_vis = len(sats) + sat_used = sum(1 for s in sats if s.get("used")) + + # Fallback for gpsd variants that report aggregated counters only + # (common in POLL output for some setups). + if not sat_vis: + sat_vis = int(sky.get("nSat", sky.get("satellites_visible", 0)) or 0) + if not sat_used: + sat_used = int(sky.get("uSat", sky.get("satellites_used", 0)) or 0) + + # Last fallback to TPV keys when SKY is absent. + if not sat_vis: + sat_vis = int(tpv.get("nSat", tpv.get("satellites_visible", 0)) or 0) + if not sat_used: + sat_used = int(tpv.get("uSat", tpv.get("satellites_used", 0)) or 0) + + if sat_used > sat_vis: + sat_vis = sat_used + + gps_data["satellites-visible"] = sat_vis + gps_data["satellites-used"] = sat_used # Check for PPS device availability pps_path = f"/dev/pps{name.replace('gps', '')}" diff --git a/src/statd/statd.c b/src/statd/statd.c index 139c0943..13214165 100644 --- a/src/statd/statd.c +++ b/src/statd/statd.c @@ -30,7 +30,6 @@ #include "shared.h" #include "journal.h" -#include "gpsd.h" /* New kernel feature, not in sys/mman.h yet */ #ifndef MFD_NOEXEC_SEAL @@ -70,7 +69,6 @@ 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, @@ -352,13 +350,6 @@ 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) { @@ -462,7 +453,7 @@ static int subscribe_to_all(struct statd *statd) int main(int argc, char *argv[]) { - struct ev_signal sigint_watcher, sigusr1_watcher, sighup_watcher; + struct ev_signal sigint_watcher, sigusr1_watcher; int log_opts = LOG_PID | LOG_NDELAY; struct statd statd = {}; const char *env; @@ -523,10 +514,6 @@ 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); @@ -535,10 +522,6 @@ 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); @@ -548,7 +531,6 @@ 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);