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 <dev/gps0> instead of hotplug magic.
This commit is contained in:
Mattias Walström
2026-03-05 08:21:34 +01:00
parent 565a07a0b2
commit 57961ac0a5
11 changed files with 266 additions and 531 deletions
+22 -20
View File
@@ -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"
-3
View File
@@ -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
+7
View File
@@ -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)
@@ -0,0 +1,41 @@
From 9ba3bc688c45a0c9e761c6c8bd0be83d6570c9dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
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 <lazzer@gmail.com>
---
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
+71 -4
View File
@@ -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, " <!");
for (i = 0; i < num_gps && i < GPSD_MAX_DEVICES; i++)
fprintf(fp, "%sdev/%s", i ? "," : "", gps_names[i]);
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;
+1 -1
View File
@@ -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)
-415
View File
@@ -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 <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");
}
-32
View File
@@ -1,32 +0,0 @@
/* 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
+19 -13
View File
@@ -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)
+104 -24
View File
@@ -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', '')}"
+1 -19
View File
@@ -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);