Skip to content

Commit 3f45ed6

Browse files
Group data-downloader sensors by DBC CAN messages (#72)
* Group data-downloader sensors by DBC CAN message with subsystem colour coding - GET /api/sensors/grouped: left-joins DB sensor names against the DBC message/signal tree; transmitter node (MOBO, INV, etc.) used as subsystem; graceful fallback when no DBC is configured. - POST /api/dbc/refresh: bust in-memory DBC cache on demand. - dbc_utils.py: auto-discovers alphabetically newest .dbc in GitHub repo (no GITHUB_DBC_PATH required); caches to DATA_DIR/dbc_cache.dbc; GITHUB_DBC_PATH accepted as optional pin; falls back to DBC_FILE_PATH. - config.py: GITHUB_DBC_TOKEN/REPO/BRANCH/PATH and DBC_FILE_PATH settings. - requirements.txt: cantools + requests. - docker-compose.yml: DBC env vars wired into data-downloader-api. - SensorGroupedGrid.tsx: collapsible groups, 8-hue subsystem palette (djb2-stable), subsystem + hex CAN ID badges, dark/light mode. - App.tsx: parallel grouped fetch; grouped grid when DBC available, flat fallback otherwise; DBC source tag. - data-download.tsx: sensor select uses optgroup per message. - styles.css: message group layout classes. * Auto-select most recently committed DBC from GitHub repo instead of alphabetically last * Move DBC env var docs to top-level installer .env.example * Consolidate into single .env.example under server/installer, remove data-downloader copy * Keep grouped sensor rows expanded and show decimal CAN IDs * Provide CI slicks stub for compose image builds * Stop code-generator image from requiring local anomaly scan script * Make code-generator FastEmbed prefetch best-effort
1 parent 57b0645 commit 3f45ed6

15 files changed

Lines changed: 743 additions & 34 deletions

File tree

.github/workflows/docker-build.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,20 @@ jobs:
9393
run: |
9494
cd server/installer
9595
cp .env.example .env
96+
mkdir -p .ci-slicks/src/slicks
97+
cat > .ci-slicks/pyproject.toml <<'EOF'
98+
[project]
99+
name = "slicks"
100+
version = "0.0.0"
101+
[build-system]
102+
requires = ["setuptools>=68"]
103+
build-backend = "setuptools.build_meta"
104+
[tool.setuptools.packages.find]
105+
where = ["src"]
106+
EOF
107+
touch .ci-slicks/README.md
108+
echo '__version__ = "0.0.0"' > .ci-slicks/src/slicks/__init__.py
109+
echo "SLICKS_HOST_PATH=${PWD}/.ci-slicks" >> .env
96110
97111
- name: Pull pre-built images
98112
run: |

server/installer/.env.example

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
DBC_FILE_PATH=example.dbc
55

66
# ------------------------------------------------------------
7-
# File uploader — team DBCs from GitHub (optional)
7+
# Team DBC from GitHub (used by file-uploader and data-downloader)
88
# ------------------------------------------------------------
99
# Fine-grained PAT or classic PAT with contents:read on Western-Formula-Racing/DBC
1010
GITHUB_DBC_TOKEN=
1111
# GITHUB_DBC_REPO=Western-Formula-Racing/DBC
1212
# GITHUB_DBC_BRANCH=main
13+
# Data-downloader auto-selects the most recently committed .dbc in the repo.
14+
# Set GITHUB_DBC_PATH to pin a specific file instead (e.g. WFR26.dbc).
15+
# GITHUB_DBC_PATH=
1316

1417
# Optional limits for .zip uploads (file-uploader); defaults are generous for team use
1518
# UPLOAD_ZIP_MAX_ARCHIVE_BYTES=2147483648
@@ -86,10 +89,19 @@ SENSOR_LOOKBACK_DAYS=365
8689
SCAN_INTERVAL_SECONDS=3600
8790

8891
VITE_API_BASE_URL=http://localhost:8000
89-
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
92+
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,https://daq.westernformularacing.org
9093

9194
# End Data Downloader configuration
9295

96+
# ------------------------------------------------------------
97+
# Health monitor
98+
# ------------------------------------------------------------
99+
# All defaults match the standard docker-compose stack; only override if needed.
100+
# HEALTH_MONITOR_INTERVAL_SECONDS=60
101+
# HEALTH_MONITOR_TIMESCALEDB_CONTAINER=timescaledb
102+
# HEALTH_MONITOR_SCANNER_CONTAINER=data-downloader-scanner
103+
# HEALTH_MONITOR_SCANNER_API_URL=http://data-downloader-api:8000
104+
93105
# ------------------------------------------------------------
94106
# AI Code Generation — MiniMax (Anthropic-compatible SDK)
95107
# ------------------------------------------------------------

server/installer/data-downloader/.env.example

Lines changed: 0 additions & 20 deletions
This file was deleted.

server/installer/data-downloader/backend/app.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from pydantic import BaseModel
1616

1717
from backend.config import get_settings
18+
from backend.dbc_utils import group_sensors_by_message, load_dbc_db, refresh_dbc
1819
from backend.services import DataDownloaderService
1920

2021

@@ -124,6 +125,36 @@ def list_sensors(season: str | None = None) -> dict:
124125
return service.get_sensors(season=season)
125126

126127

128+
@app.get("/api/sensors/grouped")
129+
def list_sensors_grouped(season: str | None = None) -> dict:
130+
"""
131+
Return sensors grouped by their DBC CAN message and transmitter node.
132+
133+
Each entry in ``messages`` contains only signals that actually have data in
134+
TimescaleDB (left-join: DB is truth, DBC is the categorisation guide).
135+
Sensors with no matching DBC entry appear in ``ungrouped``.
136+
137+
Falls back gracefully to ``{"messages": [], "ungrouped": all_sensors}``
138+
when no DBC is configured.
139+
"""
140+
sensor_payload = service.get_sensors(season=season)
141+
sensor_names: list[str] = sensor_payload.get("sensors", [])
142+
db, source = load_dbc_db(settings)
143+
grouped = group_sensors_by_message(sensor_names, db)
144+
return {
145+
"updated_at": sensor_payload.get("updated_at"),
146+
"dbc_source": source,
147+
**grouped,
148+
}
149+
150+
151+
@app.post("/api/dbc/refresh")
152+
def dbc_refresh() -> dict:
153+
"""Force-reload the DBC from GitHub (or local file). Returns the new source."""
154+
source = refresh_dbc(settings)
155+
return {"status": "ok", "dbc_source": source}
156+
157+
127158
@app.get("/api/scanner-status")
128159
def scanner_status() -> dict:
129160
return service.get_scanner_status()

server/installer/data-downloader/backend/config.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,25 @@ class Settings(BaseModel):
110110
default_factory=lambda: _parse_origins(os.getenv("ALLOWED_ORIGINS", "*"))
111111
)
112112

113+
# DBC source — GitHub takes priority over local file
114+
github_dbc_token: str = Field(
115+
default_factory=lambda: os.getenv("GITHUB_DBC_TOKEN", "")
116+
)
117+
github_dbc_repo: str = Field(
118+
default_factory=lambda: os.getenv("GITHUB_DBC_REPO", "Western-Formula-Racing/DBC")
119+
)
120+
github_dbc_branch: str = Field(
121+
default_factory=lambda: os.getenv("GITHUB_DBC_BRANCH", "main")
122+
)
123+
# Specific file path within the repo, e.g. "WFR26.dbc"
124+
github_dbc_path: str = Field(
125+
default_factory=lambda: os.getenv("GITHUB_DBC_PATH", "")
126+
)
127+
# Fallback: local DBC file path
128+
dbc_file_path: str | None = Field(
129+
default_factory=lambda: os.getenv("DBC_FILE_PATH") or None
130+
)
131+
113132

114133
@lru_cache(maxsize=1)
115134
def get_settings() -> Settings:

0 commit comments

Comments
 (0)