@yinyinfurong_zmr/dbc-can
Version:
A general purpose CAN (Controller Area Network) toolbox with support for .dbc file parsing, CAN message decoding, and more
433 lines • 16.6 kB
JavaScript
import Can from '../can/Can';
class Writer {
constructor() {
this.dbcString = '';
}
/**
* Main constructor class that will organize and write all
* attributes of DbcData structure
* @param data dbc data loaded or created using main DBC class
*/
constructFile(data) {
this.dbcString = '';
// Main file attributes
this.writeVersion(data.version ? data.version : '');
this.writeNamespace(data.newSymbols);
this.writeBusSpeed(data.busSpeed);
this.writeNodes(Array.from(data.nodes.keys()));
if (data.valueTables) {
this.writeValTable(data.valueTables);
}
// Both messages and signals
this.writeMessagesAndSignals(data.messages);
this.writeMessageTransmitters(data.networkBridges);
// Write all comments
if (data.description) {
this.writeBaseComment(data.description);
}
this.writeMessageAndSignalComments(data.messages);
this.writeAttributeDefinitions(data);
this.writeAttributeDefaults(data);
this.writeAttributeValues(data);
this.writeSignalGroups(data.messages);
this.writeSignalTables(data.messages);
this.writeEnvVarTables(data.environmentVariables);
this.writeSignalDataType(data.messages);
return this.dbcString;
}
/**
*
* @param version Version of the dbc file
*/
writeVersion(version) {
const lineContent = `VERSION "${version}"`;
this.writeLine(lineContent);
this.writeLine('');
}
writeMessageTransmitters(transmitters) {
transmitters.forEach((transmitter, canId) => {
const lineContent = `BO_TX_BU_ ${canId} : ${transmitter.join(',')};`;
this.writeLine(lineContent);
this.writeLine('');
});
this.writeLine('');
}
writeNamespace(ns = null) {
const lineContent = `NS_:`;
this.writeLine(lineContent);
if (ns) {
ns.forEach((newSymbol) => {
this.writeLine(` ${newSymbol}`);
});
}
this.writeLine('');
}
/**
* Speed of the CAN bus, typically expressed as 250, 500, etc
* @param busConfiguration Speed of the CAN bus
*/
writeBusSpeed(busConfiguration) {
let lineContent = '';
if (busConfiguration === null) {
lineContent = `BS_:`;
}
else {
lineContent = `BS_: ${busConfiguration}`;
}
this.writeLine(lineContent);
this.writeLine('');
}
/**
* Generic list of nodes that exist for the CAN architecture
* @param nodes List of nodes that are attached to messages and signals
*/
writeNodes(nodes) {
let lineContent = '';
if (nodes === null || nodes.length === 0) {
lineContent = `BU_:`;
}
else {
// TODO: Actually enumerate list out
lineContent = `BU_: ${nodes.join(' ')}`;
}
this.writeLine(lineContent);
this.writeLine('');
}
/**
* Generic the signal groups as provided from the model
* @param groups Map of available signal groups to write
*/
writeSignalGroups(messages) {
messages.forEach((message) => {
message.signalGroups.forEach((group) => {
const signalList = group.signals.join(' ');
const lineContent = `SIG_GROUP_ ${group.id} ${group.name} ${group.groupId} : ${signalList};`;
this.writeLine(lineContent);
});
});
this.writeLine('');
}
/**
*
* @param message Individual message to be written to the file
*/
writeMessagesAndSignals(messages) {
for (const [name, message] of messages) {
let node;
if (message.sendingNode) {
node = message.sendingNode;
}
else {
// Default that is typically generated from CANDB++ (from Vector)
node = 'Vector___XXX';
}
// Need to encode extended ID flag if message is extended
const can = new Can();
const canId = message.extended ? can.setExtendedFlag(message.id) : message.id;
const lineContent = `BO_ ${canId.toString()} ${message.name}: ${message.dlc.toString()} ${node}`;
this.writeLine(lineContent);
// Extract signals and exand in file
for (const [signalName, signal] of message.signals) {
this.writeSignal(signal);
}
this.writeLine('');
}
}
/**
*
* @param signal Signal to be writen to dbc file
*/
writeSignal(signal) {
const endian = signal.endian === 'Motorola' ? '0' : '1';
const sign = signal.signed ? '-' : '+';
const nodes = signal.receivingNodes.length === 0 ? 'Vector___XXXX' : signal.receivingNodes.join(',');
const name = signal.multiplex ? signal.name + ' ' + signal.multiplex : signal.name;
// Format: SG_ Signal0 : 0|32@1- (1,0) [0|0] "" Node1
const lineContent = ` SG_ ` +
`${name} : ${signal.startBit.toString()}|${signal.length.toString()}@${endian}${sign}` +
` (${signal.factor.toString()},${signal.offset.toString()}) [${signal.min.toString()}|${signal.max.toString()}] ` +
`"${signal.unit}" ${nodes}`;
this.writeLine(lineContent);
}
writeBaseComment(comment) {
this.writeLine(`CM_ "${comment}" ;`);
}
writeMessageAndSignalComments(messages) {
for (const [name, msg] of messages) {
if (msg.description) {
this.writeLine(`CM_ BO_ ${msg.id.toString()} "${msg.description}" ;`);
}
for (const [signalName, signal] of msg.signals) {
if (signal.description) {
this.writeLine(`CM_ SG_ ${msg.id.toString()} ${signalName} "${signal.description}" ;`);
}
}
}
}
generateEnumTable(tableMembers) {
let members = '';
for (const [enumVal, enumName] of tableMembers) {
members = members + enumVal.toString() + ' ' + `"${enumName}"` + ' ';
}
return `${members}`;
}
writeValTable(valueTable) {
for (const [name, tableMembers] of valueTable) {
const members = this.generateEnumTable(tableMembers);
const lineContent = `VAL_TABLE_ ${name} ${members};`;
this.writeLine(lineContent);
}
this.writeLine('');
}
writeSignalTables(messages) {
for (const [name, msg] of messages) {
for (const [signalName, signal] of msg.signals) {
if (signal.valueTable) {
const members = this.generateEnumTable(signal.valueTable);
const lineContent = `VAL_ ${msg.id.toString()} ${signal.name} ${members};`;
this.writeLine(lineContent);
}
}
}
this.writeLine('');
}
writeEnvVarTables(environmentVariables) {
environmentVariables.forEach((ev) => {
if (ev.valueTable) {
const members = this.generateEnumTable(ev.valueTable);
const lineContent = `VAL_ ${ev.name} ${members};`;
this.writeLine(lineContent);
}
});
}
enumListToString(enumList) {
return enumList.reduce((accumulator, currentValue, idx) => {
let str = '';
if (idx === 0) {
str = `"${currentValue.trim()}"`;
}
else {
str = `, "${currentValue.trim()}"`;
}
return accumulator + str;
}, '');
}
createAttributeDefinitionStr(value, type) {
let lineContent;
if (type) {
lineContent = `BA_DEF_ ${type} "${value.name}" ${value.dataType}`;
}
else {
lineContent = `BA_DEF_ "${value.name}" ${value.dataType}`;
}
switch (value.dataType) {
case 'INT':
lineContent = lineContent + ` ${value.min} ${value.max};`;
break;
case 'FLOAT':
lineContent = lineContent + ` ${value.min} ${value.max};`;
break;
case 'ENUM':
if (value.options) {
lineContent = lineContent + ' ' + this.enumListToString(value.options) + ';';
}
break;
case 'STRING':
lineContent = lineContent + ';';
break;
case 'HEX':
lineContent = lineContent + ` ${value.min} ${value.max};`;
break;
}
return lineContent;
}
writeAttributeDefinitions(data) {
// Write global attributes
data.attributes.forEach((value, key) => {
this.writeLine(this.createAttributeDefinitionStr(value, null));
});
// Write node attributes
data.nodes.forEach((node) => {
node.attributes.forEach((value, key) => {
this.writeLine(this.createAttributeDefinitionStr(value, 'BU_'));
});
});
// Write message attributes
const messages = data.messages;
messages.forEach((message) => {
message.attributes.forEach((value, key) => {
this.writeLine(this.createAttributeDefinitionStr(value, 'BO_'));
});
});
// Write signal attributes
messages.forEach((message) => {
const signals = message.signals;
if (signals) {
signals.forEach((signal, key) => {
signal.attributes.forEach((value, name) => {
this.writeLine(this.createAttributeDefinitionStr(value, 'SG_'));
});
});
}
});
this.writeLine('');
}
createAttributeDefaultString(value) {
let lineContent = `BA_DEF_DEF_ "${value.name}"`;
switch (value.dataType) {
case 'STRING':
lineContent = lineContent + ` "${value.defaultValue}";`;
break;
case 'ENUM':
lineContent = lineContent + ` "${value.defaultValue}";`;
break;
default:
lineContent = lineContent + ` ${value.defaultValue};`;
break;
}
return lineContent;
}
writeAttributeDefaults(data) {
// Write global attributes
data.attributes.forEach((value, key) => {
this.writeLine(this.createAttributeDefaultString(value));
});
// Write node attributes
data.nodes.forEach((node) => {
node.attributes.forEach((value, key) => {
this.writeLine(this.createAttributeDefaultString(value));
});
});
// Write message attributes
const messages = data.messages;
messages.forEach((message) => {
message.attributes.forEach((value, key) => {
this.writeLine(this.createAttributeDefaultString(value));
});
});
// Write signal attributes
messages.forEach((message) => {
const signals = message.signals;
if (signals) {
signals.forEach((signal, key) => {
signal.attributes.forEach((value, name) => {
this.writeLine(this.createAttributeDefaultString(value));
});
});
}
});
this.writeLine('');
}
createAttributeValueString(value, type, id, node, signal) {
let lineContent = `BA_ "${value.name}"`;
if (!type) {
if (value.dataType === 'STRING') {
lineContent = lineContent + ` "${value.value}";`;
}
else {
lineContent = lineContent + ` ${value.value};`;
}
return lineContent;
}
// BA_ "BUIntAttribute" BU_ Node0 100;
// BA_ "BOStringAttribute" BO_ 1234 "MessageAttribute";
// BA_ "SGEnumAttribute" SG_ 1234 Signal0 2;
switch (type) {
case 'BU_':
if (value.dataType === 'STRING') {
lineContent = lineContent + ` ${type} ${node} "${value.value}";`;
}
else {
lineContent = lineContent + ` ${type} ${node} ${value.value};`;
}
break;
case 'BO_':
if (value.dataType === 'STRING') {
lineContent = lineContent + ` ${type} ${id} "${value.value}";`;
}
else {
lineContent = lineContent + ` ${type} ${id} ${value.value};`;
}
break;
case 'SG_':
if (value.dataType === 'STRING') {
lineContent = lineContent + ` ${type} ${id} ${signal} "${value.value}";`;
}
else {
lineContent = lineContent + ` ${type} ${id} ${signal} ${value.value};`;
}
break;
}
return lineContent;
}
writeAttributeValues(data) {
// Write global attributes
data.attributes.forEach((value, key) => {
// skip if default = value so its no double defined in the dbc file output
if (value.defaultValue !== value.value) {
this.writeLine(this.createAttributeValueString(value, null, null, null, null));
}
});
// Write node attributes
data.nodes.forEach((node) => {
node.attributes.forEach((value, key) => {
if (value.defaultValue !== value.value) {
this.writeLine(this.createAttributeValueString(value, 'BU_', null, node.name, null));
}
});
});
// Write message attributes
const messages = data.messages;
messages.forEach((message) => {
message.attributes.forEach((value, key) => {
if (value.defaultValue !== value.value) {
this.writeLine(this.createAttributeValueString(value, 'BO_', message.id, null, null));
}
});
});
// Write signal attributes
messages.forEach((message) => {
const signals = message.signals;
if (signals) {
signals.forEach((signal, key) => {
signal.attributes.forEach((value, name) => {
if (value.defaultValue !== value.value) {
this.writeLine(this.createAttributeValueString(value, 'SG_', message.id, null, signal.name));
}
});
});
}
});
this.writeLine('');
}
writeSignalDataType(messages) {
for (const [name, msg] of messages) {
for (const [signalName, signal] of msg.signals) {
if (signal.dataType && (signal.dataType === 'float' || signal.dataType === 'double')) {
let type;
if (signal.dataType === 'double') {
type = '2';
}
else {
type = '1';
}
const lineContent = `SIG_VALTYPE_ ${msg.id.toString()} ${signal.name} : ${type};`;
this.writeLine(lineContent);
}
}
}
this.writeLine('');
}
/**
* Main writer function for class. New line character will be added automatically
* to each line, so subsquent calls to this function
* will automatically start on the next line.
*
* @param line Line content to write to file
*/
writeLine(line) {
this.dbcString += `${line}\n`;
}
}
export default Writer;
//# sourceMappingURL=Writer.js.map