Skip to content

Commit dbbc693

Browse files
committed
Add dbc fetch service, timestamp
1 parent 8716856 commit dbbc693

7 files changed

Lines changed: 313 additions & 54 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ celerybeat.pid
137137

138138
# Environments
139139
.env
140+
.env.local
141+
.env.*.local
140142
.envrc
141143
.venv
142144
env/

flight-recorder/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Fine-grained PAT with Contents:read on Western-Formula-Racing/DBC
2+
# Set this in your deployment environment (Cloudflare Pages, etc.)
3+
# DO NOT commit a real token — this file is just documentation.
4+
VITE_GITHUB_DBC_READONLY_TOKEN=github_pat_...

flight-recorder/src/App.tsx

Lines changed: 172 additions & 50 deletions
Large diffs are not rendered by default.

flight-recorder/src/services/LoggingHandler.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ class LoggingHandler {
1010
webSocketService.on('decoded', (decoded: any) => {
1111
const messages = Array.isArray(decoded) ? decoded : [decoded];
1212

13+
// Stamp with wall-clock time at FDR receipt.
14+
// The car's msg.time is relative (ms since ECU boot) and has no
15+
// meaning as a Unix timestamp — InfluxDB needs absolute epoch ms.
16+
const receivedAt = Date.now();
1317
messages.forEach(msg => {
1418
if (msg?.signals) {
1519
loggingService.logFrame(
16-
msg.time || Date.now(),
17-
msg.canId,
18-
msg.data // Using raw buffer data from processor
20+
receivedAt,
21+
msg.canId,
22+
msg.rawBytes ?? []
1923
);
2024
}
2125
});

flight-recorder/src/services/SyncService.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class SyncService {
6060

6161
for (const [sigName, sigData] of Object.entries(decoded.signals)) {
6262
const value = (sigData as any).sensorReading;
63-
if (typeof value !== 'number') continue;
63+
if (typeof value !== 'number' || !isFinite(value)) continue;
6464

6565
const tags = `signalName=${this.escapeTag(sigName)},messageName=${this.escapeTag(decoded.messageName)},canId=${frame.canId}`;
6666
const fields = `sensorReading=${value}`;
@@ -164,6 +164,11 @@ export class SyncService {
164164
throw new Error(`InfluxDB write failed: ${response.status} ${response.statusText} - ${errorText}`);
165165
}
166166
}
167+
168+
/** Drop the cached processor so the next sync creates a fresh one from the current dbcFile. */
169+
public invalidateProcessor(): void {
170+
this.processor = null;
171+
}
167172
}
168173

169174
export const syncService = new SyncService();

flight-recorder/src/services/WebSocketService.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ export class WebSocketService {
170170
public getMessageCount(): number {
171171
return this.messageCount;
172172
}
173+
174+
/**
175+
* Discard the cached CAN processor so the next message creates a fresh one
176+
* from the current dbcFile. Call this after fetching a new DBC.
177+
*/
178+
public async resetProcessor(): Promise<void> {
179+
this.processor = null;
180+
this.processor = await createCanProcessor();
181+
console.log('[WebSocket] CAN processor reloaded with new DBC');
182+
}
173183
}
174184

175185
export const webSocketService = new WebSocketService();

flight-recorder/src/utils/canProcessor.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ interface DecodedMessage {
7777
};
7878
};
7979
rawData: string;
80+
rawBytes: number[]; // Original byte array — preserved for IndexedDB storage
8081
}
8182

8283
// Type for batch processing results
@@ -398,6 +399,7 @@ export function decodeCanMessage(
398399
time: time,
399400
signals: {},
400401
rawData: rawDataStr,
402+
rawBytes: messageData,
401403
};
402404
}
403405

@@ -420,6 +422,7 @@ export function decodeCanMessage(
420422
time: time,
421423
signals,
422424
rawData: rawDataStr,
425+
rawBytes: messageData,
423426
};
424427
} catch (error) {
425428
console.error(`Error decoding message ${canId}:`, error);
@@ -626,6 +629,115 @@ export async function createCanProcessor(): Promise<any> {
626629
};
627630
}
628631

632+
const GITHUB_REPO = 'Western-Formula-Racing/DBC';
633+
// Fine-grained PAT injected at build time via VITE_GITHUB_TOKEN env var.
634+
// Needs Contents:read on Western-Formula-Racing/DBC.
635+
const GITHUB_TOKEN = (import.meta as any).env?.VITE_GITHUB_DBC_READONLY_TOKEN ?? '';
636+
637+
function githubHeaders(raw = false): HeadersInit {
638+
const h: HeadersInit = {
639+
Accept: raw ? 'application/vnd.github.v3.raw' : 'application/vnd.github.v3+json',
640+
'X-GitHub-Api-Version': '2022-11-28',
641+
};
642+
if (GITHUB_TOKEN) (h as Record<string, string>)['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
643+
return h;
644+
}
645+
646+
export interface DBCFileInfo {
647+
name: string;
648+
path: string;
649+
sha: string;
650+
size: number;
651+
}
652+
653+
export interface DBCApplyResult {
654+
ok: boolean;
655+
message: string;
656+
commitSha?: string;
657+
commitMessage?: string;
658+
}
659+
660+
/** List all .dbc files in the Western-Formula-Racing/DBC repo root. */
661+
export async function listDBCFiles(): Promise<{ ok: boolean; files?: DBCFileInfo[]; message?: string }> {
662+
try {
663+
const res = await fetch(`https://api.github.com/repos/${GITHUB_REPO}/contents/`, {
664+
headers: githubHeaders(),
665+
});
666+
if (!res.ok) {
667+
return { ok: false, message: `GitHub ${res.status}: ${res.statusText}` };
668+
}
669+
const items: any[] = await res.json();
670+
const files: DBCFileInfo[] = items
671+
.filter(f => f.type === 'file' && f.name.toLowerCase().endsWith('.dbc'))
672+
.map(f => ({ name: f.name, path: f.path, sha: f.sha, size: f.size }));
673+
return { ok: true, files };
674+
} catch (err) {
675+
return { ok: false, message: err instanceof Error ? err.message : String(err) };
676+
}
677+
}
678+
679+
/**
680+
* Fetch a specific DBC file from the repo, cache it locally, and update the
681+
* in-memory dbcFile so the next createCanProcessor() call uses it.
682+
* Also fetches the latest commit that touched the file for display.
683+
*/
684+
export async function fetchAndApplyDBC(filename: string): Promise<DBCApplyResult> {
685+
// Fetch raw file content
686+
let fileRes: Response;
687+
try {
688+
fileRes = await fetch(
689+
`https://api.github.com/repos/${GITHUB_REPO}/contents/${encodeURIComponent(filename)}`,
690+
{ headers: githubHeaders(true) }
691+
);
692+
} catch (err) {
693+
return { ok: false, message: `Network error: ${err instanceof Error ? err.message : String(err)}` };
694+
}
695+
696+
if (!fileRes.ok) {
697+
return { ok: false, message: `GitHub ${fileRes.status}: ${fileRes.statusText}` };
698+
}
699+
700+
const dbcText = await fileRes.text();
701+
if (!dbcText.trim()) {
702+
return { ok: false, message: 'Fetched DBC is empty' };
703+
}
704+
705+
// Fetch the most recent commit that touched this file
706+
let commitSha = '';
707+
let commitMessage = '';
708+
try {
709+
const commitRes = await fetch(
710+
`https://api.github.com/repos/${GITHUB_REPO}/commits?path=${encodeURIComponent(filename)}&per_page=1`,
711+
{ headers: githubHeaders() }
712+
);
713+
if (commitRes.ok) {
714+
const commits: any[] = await commitRes.json();
715+
if (commits.length > 0) {
716+
commitSha = commits[0].sha.slice(0, 7);
717+
commitMessage = commits[0].commit.message.split('\n')[0];
718+
}
719+
}
720+
} catch { /* non-fatal */ }
721+
722+
// Update in-memory state immediately
723+
dbcFile = dbcText;
724+
usingCache = true;
725+
localStorage.setItem('dbc-selected-file', filename);
726+
727+
// Persist to Cache API, fall back to localStorage
728+
try {
729+
const cache = await caches.open('dbc-files');
730+
await cache.put('cache.dbc', new Response(dbcText, {
731+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
732+
}));
733+
} catch {
734+
try { localStorage.setItem('dbc-file-content', dbcText); } catch { /* ignore */ }
735+
}
736+
737+
const sizeKb = (dbcText.length / 1024).toFixed(1);
738+
return { ok: true, message: `${filename}${sizeKb} KB`, commitSha, commitMessage };
739+
}
740+
629741
/**
630742
* Example: Setup WebSocket listener with CAN processor
631743
* Usage in your browser app:

0 commit comments

Comments
 (0)