Skip to content

Commit a36d94a

Browse files
Merge pull request #18 from Western-Formula-Racing/dev-public-dbc
Changed to public version of DBC and testing data; centralized category tags
2 parents fbb7900 + 90ca99c commit a36d94a

7 files changed

Lines changed: 316 additions & 59 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Category Configuration
2+
3+
This project uses a simple text-based configuration file to define CAN message categories.
4+
5+
## Configuration File
6+
7+
Location: `src/assets/categories.txt`
8+
9+
## Format
10+
11+
```
12+
CategoryName,TailwindColorClass,MessageIDs
13+
```
14+
15+
### Components
16+
17+
- **CategoryName**: Display name for the category (e.g., "VCU", "BMS", "TEST MSG")
18+
- **TailwindColorClass**: Tailwind CSS background color class (e.g., "bg-purple-500", "bg-sky-400")
19+
- **MessageIDs**: Comma-separated list of CAN message IDs or ranges
20+
21+
### Message ID Formats
22+
23+
- **Individual IDs**: Single numbers (e.g., `256`, `512`)
24+
- **Ranges**: Hyphen-separated (e.g., `100-110` includes 100, 101, 102, ..., 110)
25+
- **Mixed**: Combine both (e.g., `1,2,100-110,256`)
26+
27+
### Comments
28+
29+
Lines starting with `#` are treated as comments and ignored.
30+
31+
## Example Configuration
32+
33+
```txt
34+
# CAN Message Category Configuration
35+
36+
# Test messages
37+
TEST MSG,bg-purple-500,256,512
38+
39+
# Vehicle Control Unit messages
40+
VCU,bg-sky-400,170,171,172,173
41+
42+
# Battery Management System messages
43+
BMS,bg-orange-400,168,176,177,192
44+
45+
# Inverter messages with range
46+
INV,bg-green-400,163,180-190
47+
```
48+
49+
## Tailwind Color Classes
50+
51+
Common Tailwind background color classes you can use:
52+
53+
- `bg-purple-500` - Purple
54+
- `bg-sky-400` - Light Blue
55+
- `bg-orange-400` - Orange
56+
- `bg-green-400` - Green
57+
- `bg-red-500` - Red
58+
- `bg-blue-500` - Blue
59+
- `bg-yellow-400` - Yellow
60+
- `bg-pink-500` - Pink
61+
- `bg-indigo-500` - Indigo
62+
63+
## Default Category
64+
65+
Messages that don't match any configured category will be assigned to "NO CAT" with a `bg-blue-500` color.
66+
67+
## Usage in Code
68+
69+
Categories are automatically loaded and parsed at build time. No code changes are needed to add or modify categories - just edit the `categories.txt` file.
70+
71+
The category system is used by:
72+
- `src/components/DataCard.tsx` - Card view
73+
- `src/components/DataRow.tsx` - List/table view
74+
- `src/config/categories.ts` - Parser and utility functions
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# CAN Message Category Configuration
2+
# Format: CategoryName,TailwindColorClass,MessageIDs
3+
# Message IDs can be individual numbers or ranges (e.g., 100-110)
4+
# Lines starting with # are comments
5+
6+
TEST MSG,bg-purple-500,256,512
7+
VCU,bg-sky-400,170,171,172,173
8+
BMS,bg-orange-400,168,176,177,192
9+
INV,bg-green-400,163,180-190
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
VERSION ""
2+
3+
NS_ :
4+
NS_DESC_
5+
CM_
6+
BA_DEF_
7+
BA_
8+
VAL_
9+
CAT_DEF_
10+
CAT_
11+
FILTER
12+
BA_DEF_DEF_
13+
EV_DATA_
14+
ENVVAR_DATA_
15+
SGTYPE_
16+
SGTYPE_VAL_
17+
BA_DEF_SGTYPE_
18+
BA_SGTYPE_
19+
SIG_TYPE_REF_
20+
VAL_TABLE_
21+
SIG_GROUP_
22+
SIGTYPE_VALTYPE_
23+
SIGTYPE_VALTYPE_DATA_
24+
BO_TX_BU_
25+
BA_DEF_REL_
26+
BA_REL_
27+
BA_DEF_DEF_REL_
28+
BU_SG_REL_
29+
BU_EV_REL_
30+
BU_BO_REL_
31+
SG_MUL_VAL_
32+
33+
BS_:
34+
35+
BU_: Vector__XXX
36+
37+
BO_ 256 ExampleDrivetrain: 8 Vector__XXX
38+
SG_ EngineSpeed : 0|16@1+ (0.125,0) [0|8000] "rpm" Vector__XXX
39+
SG_ ThrottlePosition : 16|8@1+ (0.5,0) [0|100] "%" Vector__XXX
40+
SG_ OilPressure : 24|8@1+ (1,0) [0|255] "kPa" Vector__XXX
41+
SG_ SteeringAngle : 32|16@1- (0.1,0) [-180|180] "deg" Vector__XXX
42+
43+
BO_ 512 ExampleBattery: 8 Vector__XXX
44+
SG_ PackVoltage : 0|16@1+ (0.1,0) [0|600] "V" Vector__XXX
45+
SG_ PackCurrent : 16|16@1- (0.1,0) [-300|300] "A" Vector__XXX
46+
SG_ StateOfCharge : 32|8@1+ (0.5,0) [0|100] "%" Vector__XXX
47+
SG_ CellTemperature : 40|8@1+ (1,-40) [-40|215] "C" Vector__XXX
48+
49+
CM_ BO_ 256 "Sample drivetrain message for development and testing";
50+
CM_ BO_ 512 "Sample battery telemetry message for development and testing";
51+
CM_ SG_ 256 EngineSpeed "Engine crankshaft speed";
52+
CM_ SG_ 256 ThrottlePosition "Driver throttle pedal position";
53+
CM_ SG_ 256 OilPressure "Lubrication system pressure";
54+
CM_ SG_ 256 SteeringAngle "Front wheel steering angle";
55+
CM_ SG_ 512 PackVoltage "High voltage pack voltage";
56+
CM_ SG_ 512 PackCurrent "High voltage pack current";
57+
CM_ SG_ 512 StateOfCharge "Estimated battery state of charge";
58+
CM_ SG_ 512 CellTemperature "Representative cell temperature";

pecan/Frontend/pecan-live-dashboard/src/components/DataCard.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Dropdown from "./Dropdown";
22
import React, { useState, useMemo, useEffect } from "react";
3+
import { determineCategory, getCategoryColor } from "../config/categories";
34

45
interface InputProps {
56
msgID: string;
@@ -43,17 +44,12 @@ function DataCard({ msgID, name, category, data, lastUpdated, rawData }: Readonl
4344
const timeDiff = lastUpdated ? currentTime - lastUpdated : 0;
4445

4546
const computedCategory = useMemo(() => {
46-
if (category) return category;
47-
if (!data || data.length === 0) return "NO CAT";
48-
const signalNames = data.flatMap(obj => Object.keys(obj));
49-
const hasINV = signalNames.some(name => name.includes("INV"));
50-
const hasBMS = signalNames.some(name => name.includes("BMS") || name.includes("TORCH")) || name.includes("TORCH");
51-
const hasVCU = signalNames.some(name => name.includes("VCU"));
52-
if (hasVCU) return "VCU";
53-
else if (hasBMS) return "BMS/TORCH";
54-
else if (hasINV) return "INV";
55-
else return "NO CAT";
56-
}, [category, data]);
47+
return determineCategory(msgID, category);
48+
}, [category, msgID]);
49+
50+
const categoryColor = useMemo(() => {
51+
return getCategoryColor(computedCategory);
52+
}, [computedCategory]);
5753

5854
const [collapsed, setCollapsed] = useState(false);
5955
const menuItems = collapsed ? ["Add to Favourites", "br", "Expand"] : ["Add to Favourites", "br", "Collapse"];
@@ -155,13 +151,7 @@ function DataCard({ msgID, name, category, data, lastUpdated, rawData }: Readonl
155151
{/* Category Name */}
156152
{/* div background colour will change based on which category is assigned to it */}
157153
<div
158-
className={`${collapsed ? "rounded-r-lg" : "rounded-t-md"} col-span-2 h-[40px] mx-[0px] box-border flex justify-center items-center
159-
${computedCategory === "VCU" ? "bg-sky-400" :
160-
computedCategory === "INV" ? "bg-green-400" :
161-
computedCategory === "CAT2" ? "bg-sky-500" :
162-
computedCategory === "BMS/TORCH" ? "bg-orange-400" :
163-
computedCategory === "CAT4" ? "bg-red-500" :
164-
"bg-blue-500"}`} // Default
154+
className={`${collapsed ? "rounded-r-lg" : "rounded-t-md"} col-span-2 h-[40px] mx-[0px] box-border flex justify-center items-center ${categoryColor}`}
165155
// TODO: Assign data categories to colours
166156
>
167157
<p className="text-white text-xs font-semibold">{computedCategory}</p>

pecan/Frontend/pecan-live-dashboard/src/components/DataRow.tsx

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useMemo, useEffect } from "react";
2+
import { determineCategory, getCategoryColor } from "../config/categories";
23

34
interface DataRowProps {
45
msgID: string;
@@ -22,26 +23,12 @@ export default function DataRow({ msgID, name, category, data, rawData, lastUpda
2223
const timeDiff = lastUpdated ? currentTime - lastUpdated : 0;
2324

2425
const computedCategory = useMemo(() => {
25-
if (category) return category;
26-
if (!data || data.length === 0) return "NO CAT";
27-
const signalNames = data.flatMap(obj => Object.keys(obj));
28-
const hasINV = signalNames.some(name => name.includes("INV"));
29-
const hasBMS = signalNames.some(name => name.includes("BMS") || name.includes("TORCH")) || name.includes("TORCH");
30-
const hasVCU = signalNames.some(name => name.includes("VCU"));
31-
if (hasVCU) return "VCU";
32-
else if (hasBMS) return "BMS/TORCH";
33-
else if (hasINV) return "INV";
34-
else return "NO CAT";
35-
}, [category, data]);
36-
37-
// Category colour logic (same as DataCard)
38-
const categoryColor =
39-
computedCategory === "VCU" ? "bg-sky-400" :
40-
computedCategory === "INV" ? "bg-green-400" :
41-
computedCategory === "CAT2" ? "bg-sky-500" :
42-
computedCategory === "BMS/TORCH" ? "bg-orange-400" :
43-
computedCategory === "CAT4" ? "bg-red-500" :
44-
"bg-blue-500"; // default
26+
return determineCategory(msgID, category);
27+
}, [category, msgID]);
28+
29+
const categoryColor = useMemo(() => {
30+
return getCategoryColor(computedCategory);
31+
}, [computedCategory]);
4532

4633
// Alternating row background
4734
const rowBg = index % 2 === 0 ? "bg-sidebar" : "bg-data-module-bg";
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* Category Configuration
3+
*
4+
* Centralized configuration for CAN message categories.
5+
* Parses a simple text file format for easy configuration.
6+
*
7+
* Format:
8+
* CategoryName,Color,MessageIDs
9+
*
10+
* Examples:
11+
* BMS,bg-orange-400,1,2,100-110
12+
* VCU,bg-sky-400,256,512
13+
* TEST MSG,bg-purple-500,256,512
14+
*/
15+
16+
import categoryConfig from '../assets/categories.txt?raw';
17+
18+
export interface Category {
19+
name: string;
20+
color: string;
21+
messageIds: Set<number>;
22+
}
23+
24+
/**
25+
* Default category for uncategorized messages
26+
*/
27+
export const DEFAULT_CATEGORY = {
28+
name: "NO CAT",
29+
color: "bg-blue-500"
30+
};
31+
32+
/**
33+
* Parse a range string like "100-110" into an array of numbers
34+
*/
35+
function parseRange(range: string): number[] {
36+
const parts = range.split('-');
37+
if (parts.length === 2) {
38+
const start = parseInt(parts[0]);
39+
const end = parseInt(parts[1]);
40+
if (!isNaN(start) && !isNaN(end)) {
41+
const result: number[] = [];
42+
for (let i = start; i <= end; i++) {
43+
result.push(i);
44+
}
45+
return result;
46+
}
47+
}
48+
return [];
49+
}
50+
51+
/**
52+
* Parse the category configuration file
53+
*/
54+
function parseCategories(configText: string): Category[] {
55+
const categories: Category[] = [];
56+
const lines = configText.split('\n');
57+
58+
for (const line of lines) {
59+
// Skip empty lines and comments
60+
const trimmed = line.trim();
61+
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('//')) {
62+
continue;
63+
}
64+
65+
const parts = trimmed.split(',').map(p => p.trim());
66+
if (parts.length < 3) {
67+
console.warn(`Invalid category line: ${line}`);
68+
continue;
69+
}
70+
71+
const name = parts[0];
72+
const color = parts[1];
73+
const messageIds = new Set<number>();
74+
75+
// Parse message IDs (parts[2] onwards)
76+
for (let i = 2; i < parts.length; i++) {
77+
const part = parts[i];
78+
79+
// Check if it's a range (e.g., "100-110")
80+
if (part.includes('-')) {
81+
const rangeIds = parseRange(part);
82+
rangeIds.forEach(id => messageIds.add(id));
83+
} else {
84+
// Single ID
85+
const id = parseInt(part);
86+
if (!isNaN(id)) {
87+
messageIds.add(id);
88+
}
89+
}
90+
}
91+
92+
categories.push({ name, color, messageIds });
93+
}
94+
95+
return categories;
96+
}
97+
98+
/**
99+
* Loaded categories from configuration file
100+
*/
101+
export const CATEGORIES: Category[] = parseCategories(categoryConfig);
102+
103+
/**
104+
* Determine the category for a CAN message
105+
* @param msgID - CAN message ID (as string)
106+
* @param explicitCategory - Optional explicit category override
107+
* @returns Category name
108+
*/
109+
export function determineCategory(
110+
msgID: string,
111+
explicitCategory?: string
112+
): string {
113+
// If explicit category is provided, use it
114+
if (explicitCategory) return explicitCategory;
115+
116+
const numericId = parseInt(msgID);
117+
if (isNaN(numericId)) return DEFAULT_CATEGORY.name;
118+
119+
// Find first matching category
120+
for (const category of CATEGORIES) {
121+
if (category.messageIds.has(numericId)) {
122+
return category.name;
123+
}
124+
}
125+
126+
// No match found, return default
127+
return DEFAULT_CATEGORY.name;
128+
}
129+
130+
/**
131+
* Get the color class for a category
132+
* @param categoryName - Category name
133+
* @returns Tailwind CSS color class
134+
*/
135+
export function getCategoryColor(categoryName: string): string {
136+
const category = CATEGORIES.find(cat => cat.name === categoryName);
137+
return category?.color ?? DEFAULT_CATEGORY.color;
138+
}
139+
140+
/**
141+
* Get all category names
142+
* @returns Array of category names
143+
*/
144+
export function getAllCategoryNames(): string[] {
145+
return CATEGORIES.map(cat => cat.name);
146+
}

0 commit comments

Comments
 (0)