Skip to content

Commit 71eb4c8

Browse files
sunpicursoragent
andcommitted
Restore GNSS ingestion for lat, lon, and elev telemetry.
Re-add the ModemManager sidecar and gnss_reader integration so GPS data from the EG25 modem is merged into CSV, InfluxDB, and radio outputs. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 4aa95d3 commit 71eb4c8

14 files changed

Lines changed: 895 additions & 477 deletions

Makefile

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
1-
CC ?= cc
2-
CFLAGS ?= -std=c11 -O2 -Wall -Wextra -Wpedantic
3-
CFLAGS += -Isrc -Ithird_party
4-
5-
SRC = \
6-
src/main.c \
7-
src/config.c \
8-
src/influx.c \
9-
src/format_loader.c \
10-
src/can_reader.c \
11-
src/decoder.c \
12-
src/encoder.c \
13-
src/db_watcher.c \
14-
src/writer.c \
15-
src/serial_radio.c \
16-
third_party/cJSON.c
17-
18-
TARGET = can_telem
19-
LDFLAGS ?= -lcurl -lsqlite3 -lpthread -lm
20-
21-
$(TARGET): $(SRC)
22-
$(CC) $(CFLAGS) $(SRC) -o $@ $(LDFLAGS)
23-
24-
clean:
25-
rm -f $(TARGET)
26-
27-
.PHONY: clean
1+
CC ?= cc
2+
CFLAGS ?= -std=c11 -O2 -Wall -Wextra -Wpedantic
3+
CFLAGS += -Isrc -Ithird_party
4+
5+
SRC = \
6+
src/main.c \
7+
src/config.c \
8+
src/influx.c \
9+
src/format_loader.c \
10+
src/can_reader.c \
11+
src/decoder.c \
12+
src/encoder.c \
13+
src/db_watcher.c \
14+
src/writer.c \
15+
src/serial_radio.c \
16+
src/gnss_reader.c \
17+
third_party/cJSON.c
18+
19+
TARGET = can_telem
20+
LDFLAGS ?= -lcurl -lsqlite3 -lpthread -lm
21+
22+
$(TARGET): $(SRC)
23+
$(CC) $(CFLAGS) $(SRC) -o $@ $(LDFLAGS)
24+
25+
clean:
26+
rm -f $(TARGET)
27+
28+
.PHONY: clean

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ can-telem-cloud/
300300
│ ├── writer.[ch] — CSV append sink
301301
│ ├── influx.[ch] — InfluxDB Cloud batch upload sink
302302
│ ├── serial_radio.[ch] — UART radio sink (RFD900A / CP2102)
303+
│ ├── gnss_reader.[ch] — GNSS cache reader (lat/lon/elev injection)
303304
│ ├── encoder.[ch] — CAN signal encoder (for TX path)
304305
│ └── db_watcher.[ch] — SQLite DB watcher for TX signals
305306
├── third_party/

can_telem.conf.example

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ can_interface = can0
1818
# influx_token = paste-token-here
1919
# Or omit influx_token and run: export INFLUX_TOKEN=...
2020
# influx_upload_interval_ms = 1000
21-
# Production mobile app contract:
22-
# telemetry_snapshot,signal=<name> value=<number>
23-
# Keep this unset to use the default telemetry_snapshot measurement, or set it
24-
# explicitly for clarity.
25-
# influx_measurement = telemetry_snapshot
21+
# Production mobile app contract:
22+
# telemetry_snapshot,signal=<name> value=<number>
23+
# Keep this unset to use the default telemetry_snapshot measurement, or set it
24+
# explicitly for clarity.
25+
# influx_measurement = telemetry_snapshot
2626

2727
# --- Optional: DB-driven CAN transmit (SQLite) ---
2828
# Enable this to poll values from a SQLite DB and transmit updates on CAN for
@@ -46,3 +46,17 @@ can_interface = can0
4646
# radio_device = /dev/ttyUSB0
4747
# radio_baud = 9600
4848
# radio_flush_interval_ms = 1000
49+
50+
# --- Optional: signal role filters (without editing sc-data-format) ---
51+
# Comma-separated signal names. Leave empty/missing to allow all.
52+
# rx_signals = pack_voltage,pack_current,soc,lat,lon,elev
53+
# tx_signals = torque_cmd,drive_mode
54+
55+
# --- Optional: GNSS merge ---
56+
# Pull lat/lon/elev from /run/can_telem/gnss.json (written by gnss_service.py)
57+
# gnss_enabled = true
58+
# gnss_poll_interval_ms = 1000
59+
# gnss_cache_path = /run/can_telem/gnss.json
60+
# gnss_lat_signal = lat
61+
# gnss_lon_signal = lon
62+
# gnss_elev_signal = elev

deploy/can-telem-gnss.service

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[Unit]
2+
Description=can-telem GNSS cache service
3+
After=network-online.target ModemManager.service
4+
Wants=network-online.target
5+
6+
[Service]
7+
Type=simple
8+
ExecStart=/usr/bin/python3 /home/sunpi/can-telem-cloud/tools/gnss_service.py
9+
Restart=always
10+
RestartSec=2
11+
User=root
12+
Group=root
13+
14+
[Install]
15+
WantedBy=multi-user.target

src/can_reader.c

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,57 @@
1515

1616
#include "decoder.h"
1717

18-
#define PLACEHOLDER_CAN_ID 0xFFFu
18+
static int rx_allowed(const config_file_t *cf, const char *sig_name) {
19+
if (!cf || !cf->has_rx_signals || cf->rx_signals[0] == '\0') return 1;
20+
return config_list_contains(cf->rx_signals, sig_name);
21+
}
22+
23+
static const signal_def_t *find_signal_by_name(const signal_table_t *table, const char *name) {
24+
if (!table || !name || !name[0]) return NULL;
25+
for (size_t b = 0; b < SIG_TABLE_BUCKETS; ++b) {
26+
for (const sig_node_t *n = table->buckets[b]; n; n = n->next) {
27+
if (strcmp(n->sig.name, name) == 0) return &n->sig;
28+
}
29+
}
30+
return NULL;
31+
}
32+
33+
static void inject_one_signal(writer_t *w,
34+
influx_ctx_t *influx,
35+
serial_radio_ctx_t *radio,
36+
const config_file_t *cf,
37+
const signal_def_t *sig,
38+
double value) {
39+
if (!sig || !rx_allowed(cf, sig->name)) return;
40+
decoded_value_t dv;
41+
memset(&dv, 0, sizeof dv);
42+
dv.value = value;
43+
strcpy(dv.raw_hex, "gnss");
44+
writer_append(w, sig, &dv);
45+
if (influx) influx_accumulate(influx, sig, &dv);
46+
serial_radio_accumulate(radio, sig, &dv);
47+
}
48+
49+
static void maybe_inject_gnss(const signal_table_t *table,
50+
writer_t *w,
51+
influx_ctx_t *influx,
52+
serial_radio_ctx_t *radio,
53+
gnss_reader_t *gnss,
54+
const config_file_t *cf,
55+
const signal_def_t *lat_sig,
56+
const signal_def_t *lon_sig,
57+
const signal_def_t *elev_sig) {
58+
(void)table;
59+
if (!gnss || !gnss->enabled) return;
60+
(void)gnss_reader_tick(gnss);
61+
62+
double lat = 0.0, lon = 0.0, elev = 0.0;
63+
if (!gnss_reader_get_fix(gnss, &lat, &lon, &elev)) return;
64+
65+
inject_one_signal(w, influx, radio, cf, lat_sig, lat);
66+
inject_one_signal(w, influx, radio, cf, lon_sig, lon);
67+
inject_one_signal(w, influx, radio, cf, elev_sig, elev);
68+
}
1969

2070
int can_reader_open(const char *ifname) {
2171
if (!ifname) return -1;
@@ -45,7 +95,6 @@ int can_reader_open(const char *ifname) {
4595
close(s);
4696
return -1;
4797
}
48-
4998
return s;
5099
}
51100

@@ -54,9 +103,21 @@ int can_reader_loop(int fd,
54103
writer_t *w,
55104
influx_ctx_t *influx,
56105
serial_radio_ctx_t *radio,
106+
gnss_reader_t *gnss,
107+
const config_file_t *cf,
57108
volatile sig_atomic_t *running) {
58109
if (fd < 0 || !table || !w || !running) return -1;
59110

111+
const char *lat_name = (cf && cf->has_gnss_lat_signal && cf->gnss_lat_signal[0])
112+
? cf->gnss_lat_signal : "lat";
113+
const char *lon_name = (cf && cf->has_gnss_lon_signal && cf->gnss_lon_signal[0])
114+
? cf->gnss_lon_signal : "lon";
115+
const char *elev_name = (cf && cf->has_gnss_elev_signal && cf->gnss_elev_signal[0])
116+
? cf->gnss_elev_signal : "elev";
117+
const signal_def_t *lat_sig = find_signal_by_name(table, lat_name);
118+
const signal_def_t *lon_sig = find_signal_by_name(table, lon_name);
119+
const signal_def_t *elev_sig = find_signal_by_name(table, elev_name);
120+
60121
struct can_frame frame;
61122
while (*running) {
62123
int poll_ms = 200;
@@ -68,6 +129,7 @@ int can_reader_loop(int fd,
68129
return -1;
69130
}
70131
if (pr == 0) {
132+
maybe_inject_gnss(table, w, influx, radio, gnss, cf, lat_sig, lon_sig, elev_sig);
71133
writer_tick(w);
72134
if (influx) influx_tick(influx);
73135
serial_radio_tick(radio);
@@ -96,6 +158,7 @@ int can_reader_loop(int fd,
96158
for (; node; node = node->next) {
97159
if (node->sig.can_id != id) continue;
98160
if (node->sig.placeholder) continue;
161+
if (!rx_allowed(cf, node->sig.name)) continue;
99162

100163
decoded_value_t dv;
101164
if (decoder_extract(&node->sig, frame.data,
@@ -106,10 +169,11 @@ int can_reader_loop(int fd,
106169
if (influx) influx_accumulate(influx, &node->sig, &dv);
107170
serial_radio_accumulate(radio, &node->sig, &dv);
108171
}
172+
173+
maybe_inject_gnss(table, w, influx, radio, gnss, cf, lat_sig, lon_sig, elev_sig);
109174
writer_tick(w);
110175
if (influx) influx_tick(influx);
111176
serial_radio_tick(radio);
112177
}
113-
114178
return 0;
115179
}

src/can_reader.h

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,26 @@
1-
#ifndef CAN_TELEM_CAN_READER_H
2-
#define CAN_TELEM_CAN_READER_H
3-
4-
#include <signal.h>
5-
#include <stdbool.h>
6-
#include <stdint.h>
7-
8-
#include "format_loader.h"
9-
#include "influx.h"
10-
#include "serial_radio.h"
11-
#include "writer.h"
12-
13-
/*
14-
* Open a raw SocketCAN socket bound to `ifname`.
15-
* Returns a file descriptor >= 0 on success, or -1 on error.
16-
*/
17-
int can_reader_open(const char *ifname);
18-
19-
/*
20-
* Blocking receive loop: reads frames from `fd`, matches each ID against
21-
* `table`, decodes every matching signal (skipping placeholders with
22-
* can_id==0xFFF) and appends the value via the writer.
23-
* If `influx` is non-NULL and enabled, updates Influx aggregators and may
24-
* flush to the cloud on a timer.
25-
* If `radio` is non-NULL and enabled, accumulates the latest value per
26-
* signal and flushes them to the serial radio on a separate timer.
27-
* Runs until `*running` becomes 0.
28-
*/
29-
int can_reader_loop(int fd,
30-
const signal_table_t *table,
31-
writer_t *w,
32-
influx_ctx_t *influx,
33-
serial_radio_ctx_t *radio,
34-
volatile sig_atomic_t *running);
35-
36-
#endif
1+
#ifndef CAN_TELEM_CAN_READER_H
2+
#define CAN_TELEM_CAN_READER_H
3+
4+
#include <signal.h>
5+
#include <stdbool.h>
6+
#include <stdint.h>
7+
8+
#include "config.h"
9+
#include "format_loader.h"
10+
#include "gnss_reader.h"
11+
#include "influx.h"
12+
#include "serial_radio.h"
13+
#include "writer.h"
14+
15+
int can_reader_open(const char *ifname);
16+
17+
int can_reader_loop(int fd,
18+
const signal_table_t *table,
19+
writer_t *w,
20+
influx_ctx_t *influx,
21+
serial_radio_ctx_t *radio,
22+
gnss_reader_t *gnss,
23+
const config_file_t *cf,
24+
volatile sig_atomic_t *running);
25+
26+
#endif

0 commit comments

Comments
 (0)