Skip to content

Commit 536327c

Browse files
committed
Add WebSocket v2 protocol support
Add fdr branch + main Add Cloudflare deploy job for FDR
1 parent b490210 commit 536327c

8 files changed

Lines changed: 161 additions & 0 deletions

File tree

.github/workflows/telemetry-ci.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ on:
66
paths:
77
- 'universal-telemetry-software/**'
88
- 'pecan/**'
9+
- 'flight-recorder/**'
910
- '.github/workflows/telemetry-ci.yml'
1011
pull_request:
1112
paths:
1213
- 'universal-telemetry-software/**'
1314
- 'pecan/**'
15+
- 'flight-recorder/**'
1416
- '.github/workflows/telemetry-ci.yml'
1517

1618
jobs:
@@ -19,6 +21,7 @@ jobs:
1921
outputs:
2022
telemetry: ${{ steps.filter.outputs.telemetry }}
2123
pecan: ${{ steps.filter.outputs.pecan }}
24+
fdr: ${{ steps.filter.outputs.fdr }}
2225
steps:
2326
- uses: actions/checkout@v4
2427
- uses: dorny/paths-filter@v3
@@ -29,6 +32,8 @@ jobs:
2932
- 'universal-telemetry-software/**'
3033
pecan:
3134
- 'pecan/**'
35+
fdr:
36+
- 'flight-recorder/**'
3237
3338
# ── Build images once, share via artifact ───────────────────────────────
3439
build:
@@ -123,6 +128,12 @@ jobs:
123128
npm ci
124129
npx tsc --noEmit
125130
131+
- name: Build and typecheck FDR
132+
working-directory: ./flight-recorder
133+
run: |
134+
npm ci
135+
npx tsc --noEmit
136+
126137
- name: Set up Python
127138
uses: actions/setup-python@v5
128139
with:
@@ -569,3 +580,40 @@ jobs:
569580
directory: './pecan/dist'
570581
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
571582
branch: dev
583+
584+
deploy-fdr-cloudflare:
585+
needs: [unit-tests, integration, vcan]
586+
runs-on: ubuntu-latest
587+
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/fdr'
588+
permissions:
589+
contents: read
590+
deployments: write
591+
592+
steps:
593+
- name: Checkout code
594+
uses: actions/checkout@v4
595+
596+
- name: Setup Node.js
597+
uses: actions/setup-node@v4
598+
with:
599+
node-version: '20'
600+
cache: 'npm'
601+
cache-dependency-path: flight-recorder/package-lock.json
602+
603+
- name: Install dependencies
604+
working-directory: ./flight-recorder
605+
run: npm ci
606+
607+
- name: Build
608+
working-directory: ./flight-recorder
609+
run: npm run build
610+
611+
- name: Deploy to Cloudflare Pages
612+
uses: cloudflare/pages-action@v1
613+
with:
614+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
615+
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
616+
projectName: 'wfr-fdr'
617+
directory: './flight-recorder/dist'
618+
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
619+
branch: ${{ github.ref_name }}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { createCanProcessor } from './canProcessor';
3+
4+
describe('FDR Protocol V2 Compatibility', () => {
5+
it('should process Protocol V2 enveloped messages from Dashboard', async () => {
6+
const processor = await createCanProcessor();
7+
const v2Message = {
8+
type: 'can_data',
9+
messages: [
10+
{ canId: 192, data: [0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], time: 1000 }
11+
]
12+
};
13+
const result = processor.processWebSocketMessage(v2Message);
14+
15+
expect(Array.isArray(result)).toBe(true);
16+
expect(result?.length).toBe(1);
17+
expect(result?.[0]?.messageName).toBe('M192_Command_Message');
18+
expect(result?.[0]?.canId).toBe(192);
19+
expect(result?.[0]?.time).toBe(1000);
20+
});
21+
});

flight-recorder/src/utils/canProcessor.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ interface WebSocketMessage {
8888
canId?: number;
8989
id?: number;
9090
data?: number[];
91+
type?: string;
92+
messages?: WebSocketMessage[];
9193
}
9294

9395
type WebSocketInput = string | WebSocketMessage | WebSocketMessage[];
@@ -581,6 +583,11 @@ export async function createCanProcessor(): Promise<any> {
581583

582584
// If it's an object with time, canId/id and data properties
583585
if (typeof wsMessage === "object") {
586+
// Handle Protocol V2 envelope: {"type": "can_data", "messages": [...]}
587+
if ((wsMessage as any).type === "can_data" && Array.isArray((wsMessage as any).messages)) {
588+
return this.processWebSocketMessage((wsMessage as any).messages);
589+
}
590+
584591
const time = wsMessage.time || wsMessage.timestamp || Date.now();
585592
const canId = wsMessage.canId || wsMessage.id;
586593
const data = wsMessage.data;

flight-recorder/tsconfig.app.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"compilerOptions": {
3+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4+
"target": "ES2022",
5+
"useDefineForClassFields": true,
6+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
7+
"module": "ESNext",
8+
"types": ["vite/client"],
9+
"skipLibCheck": true,
10+
11+
/* Bundler mode */
12+
"moduleResolution": "bundler",
13+
"allowImportingTsExtensions": true,
14+
"verbatimModuleSyntax": true,
15+
"moduleDetection": "force",
16+
"noEmit": true,
17+
"jsx": "react-jsx",
18+
19+
/* Linting */
20+
"strict": true,
21+
"noUnusedLocals": true,
22+
"noUnusedParameters": true,
23+
"erasableSyntaxOnly": true,
24+
"noFallthroughCasesInSwitch": true,
25+
"noUncheckedSideEffectImports": true
26+
},
27+
"include": ["src"]
28+
}

flight-recorder/tsconfig.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"files": [],
3+
"references": [
4+
{ "path": "./tsconfig.app.json" },
5+
{ "path": "./tsconfig.node.json" }
6+
]
7+
}

flight-recorder/tsconfig.node.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"compilerOptions": {
3+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4+
"target": "ES2023",
5+
"lib": ["ES2023"],
6+
"module": "ESNext",
7+
"types": [],
8+
"skipLibCheck": true,
9+
10+
/* Bundler mode */
11+
"moduleResolution": "bundler",
12+
"allowImportingTsExtensions": true,
13+
"verbatimModuleSyntax": true,
14+
"moduleDetection": "force",
15+
"noEmit": true,
16+
17+
/* Linting */
18+
"strict": true,
19+
"noUnusedLocals": true,
20+
"noUnusedParameters": true,
21+
"erasableSyntaxOnly": true,
22+
"noFallthroughCasesInSwitch": true,
23+
"noUncheckedSideEffectImports": true
24+
},
25+
"include": ["vite.config.ts"]
26+
}

pecan/src/utils/canProcessor.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,23 @@ describe('CAN Processor Unit Tests', () => {
330330
expect(result.length).toBe(3); // 2 valid CAN messages + 1 unknown CAN message
331331
expect(result[1].messageName).toBe('Unknown_CAN_0x0000270F');
332332
});
333+
334+
it('should process Protocol V2 enveloped messages from Dashboard', async () => {
335+
const processor = await createCanProcessor();
336+
const v2Message = {
337+
type: 'can_data',
338+
messages: [
339+
{ canId: 192, data: [0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], time: 1000 }
340+
]
341+
};
342+
const result = processor.processWebSocketMessage(v2Message);
343+
344+
expect(Array.isArray(result)).toBe(true);
345+
expect(result?.length).toBe(1);
346+
expect(result?.[0]?.messageName).toBe('M192_Command_Message');
347+
expect(result?.[0]?.canId).toBe(192);
348+
expect(result?.[0]?.time).toBe(1000);
349+
});
333350
});
334351

335352
describe('DBC File Loading', () => {

pecan/src/utils/canProcessor.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ interface WebSocketMessage {
132132
canId?: number;
133133
id?: number;
134134
data?: number[];
135+
type?: string;
136+
messages?: WebSocketMessage[];
135137
}
136138

137139
type WebSocketInput = string | WebSocketMessage | WebSocketMessage[];
@@ -625,6 +627,11 @@ export async function createCanProcessor(): Promise<any> {
625627

626628
// If it's an object with time, canId/id and data properties
627629
if (typeof wsMessage === "object") {
630+
// Handle Protocol V2 envelope: {"type": "can_data", "messages": [...]}
631+
if ((wsMessage as any).type === "can_data" && Array.isArray((wsMessage as any).messages)) {
632+
return this.processWebSocketMessage((wsMessage as any).messages);
633+
}
634+
628635
const time = wsMessage.time || wsMessage.timestamp || Date.now();
629636
const canId = wsMessage.canId || wsMessage.id;
630637
const data = wsMessage.data;

0 commit comments

Comments
 (0)