///|
/// A decoded physical signal value.
pub struct DecodedSignal {
name : String
raw : UInt
physical : Double
unit : String
}
///|
/// A decoded DBC message with the source identifier.
pub struct DecodedMessage {
id : UInt
name : String
signals : Array[DecodedSignal]
}
///|
/// Errors raised by checked DBC runtime operations.
pub suberror DbcRuntimeError {
InvalidSignalLayout
PayloadTooShort
PayloadTooLong
DuplicateSignal
MissingSignalValue
ValueOutOfRange
} derive(Debug)
///|
/// A lookup table for message definitions.
pub struct DbcDatabase {
messages : Array[Message]
}
///|
/// Create an empty database.
pub fn new_dbc_database() -> DbcDatabase {
{ messages: [] }
}
///|
/// Add a message definition, rejecting duplicate numeric identifiers.
pub fn DbcDatabase::add(
self : DbcDatabase,
item : Message,
) -> Unit raise DbcRuntimeError {
if self.by_id(item.id()) is Some(_) {
raise DuplicateSignal
}
self.messages.push(item)
}
///|
/// Build a database using the existing DBC subset parser.
pub fn parse_dbc_database(input : String) -> DbcDatabase raise DbcError {
let database = new_dbc_database()
for item in parse_dbc(input) {
database.add(item) catch {
_ => ()
}
}
database
}
///|
/// Return the number of definitions.
pub fn DbcDatabase::length(self : DbcDatabase) -> Int {
self.messages.length()
}
///|
/// Return all message definitions in source order.
pub fn DbcDatabase::messages(self : DbcDatabase) -> Array[Message] {
self.messages.copy()
}
///|
/// Find a message by CAN identifier.
pub fn DbcDatabase::by_id(self : DbcDatabase, id : UInt) -> Message? {
for item in self.messages {
if item.id() == id {
return Some(item)
}
}
None
}
///|
/// Find a message by its symbolic name.
pub fn DbcDatabase::by_name(self : DbcDatabase, name : String) -> Message? {
for item in self.messages {
if item.name() == name {
return Some(item)
}
}
None
}
///|
/// Return all numeric message identifiers.
pub fn DbcDatabase::ids(self : DbcDatabase) -> Array[UInt] {
self.messages.map(item => item.id())
}
///|
/// Return signal definitions in source order.
pub fn Message::signals(self : Message) -> Array[Signal] {
self.signals.copy()
}
///|
/// Return the number of defined signals.
pub fn Message::signal_count(self : Message) -> Int {
self.signals.length()
}
///|
/// Validate message size and all signal bit ranges.
pub fn Message::validate(self : Message) -> Bool {
if self.dlc < 0 || self.dlc > 64 {
return false
}
for item in self.signals {
if !item.is_valid(self.dlc) {
return false
}
}
true
}
///|
/// Decode all signals in a payload.
pub fn Message::decode(
self : Message,
data : Array[Byte],
) -> DecodedMessage raise DbcRuntimeError {
if data.length() < self.dlc {
raise PayloadTooShort
}
if !self.validate() {
raise InvalidSignalLayout
}
let decoded : Array[DecodedSignal] = []
for item in self.signals {
decoded.push({
name: item.name(),
raw: item.decode_raw(data),
physical: item.decode(data),
unit: item.unit(),
})
}
{ id: self.id, name: self.name, signals: decoded }
}
///|
/// Encode physical signal values into a message payload.
pub fn Message::encode_values(
self : Message,
values : Map[String, Double],
initial? : Array[Byte] = [],
) -> Array[Byte] raise DbcRuntimeError {
if !self.validate() {
raise InvalidSignalLayout
}
if initial.length() > self.dlc {
raise PayloadTooLong
}
let result = initial.copy()
while result.length() < self.dlc {
result.push(0)
}
for item in self.signals {
if !values.contains(item.name()) {
raise MissingSignalValue
}
let physical = values[item.name()]
if !item.accepts(physical) {
raise ValueOutOfRange
}
let encoded = item.encode(result, physical)
for index in 0.. Frame raise DbcRuntimeError {
let data = self.encode_values(values)
if self.dlc > 8 {
fd_frame(self.id, data, extended~) catch {
_ => raise PayloadTooLong
}
} else {
data_frame(self.id, data, extended~) catch {
_ => raise PayloadTooLong
}
}
}
///|
/// Decode a frame using the message selected by its identifier.
pub fn DbcDatabase::decode_frame(
self : DbcDatabase,
frame : Frame,
) -> DecodedMessage? raise DbcRuntimeError {
match self.by_id(frame.id()) {
Some(item) => Some(item.decode(frame.data()))
None => None
}
}
///|
/// Return the message identifier from a decoded message.
pub fn DecodedMessage::id(self : DecodedMessage) -> UInt {
self.id
}
///|
/// Return the message name from a decoded message.
pub fn DecodedMessage::name(self : DecodedMessage) -> String {
self.name
}
///|
/// Return decoded signals.
pub fn DecodedMessage::signals(self : DecodedMessage) -> Array[DecodedSignal] {
self.signals.copy()
}
///|
/// Find a decoded value by signal name.
pub fn DecodedMessage::find(
self : DecodedMessage,
name : String,
) -> DecodedSignal? {
for item in self.signals {
if item.name == name {
return Some(item)
}
}
None
}
///|
pub fn DecodedSignal::name(self : DecodedSignal) -> String {
self.name
}
///|
pub fn DecodedSignal::raw(self : DecodedSignal) -> UInt {
self.raw
}
///|
pub fn DecodedSignal::physical(self : DecodedSignal) -> Double {
self.physical
}
///|
pub fn DecodedSignal::unit(self : DecodedSignal) -> String {
self.unit
}
///|
/// Return the configured signal bit size.
pub fn Signal::size(self : Signal) -> Int {
self.size
}
///|
/// Return whether this signal uses signed two's-complement values.
pub fn Signal::is_signed(self : Signal) -> Bool {
self.signed
}
///|
pub fn Signal::factor(self : Signal) -> Double {
self.factor
}
///|
pub fn Signal::offset(self : Signal) -> Double {
self.offset
}
///|
pub fn Signal::minimum(self : Signal) -> Double {
self.minimum
}
///|
pub fn Signal::maximum(self : Signal) -> Double {
self.maximum
}
///|
pub fn Signal::unit(self : Signal) -> String {
self.unit
}
///|
/// Check whether a signal fits inside a payload of `dlc` bytes.
pub fn Signal::is_valid(self : Signal, dlc : Int) -> Bool {
self.start_bit >= 0 &&
self.size > 0 &&
self.size <= 32 &&
self.start_bit + self.size <= dlc * 8
}
///|
/// Return the largest raw unsigned value representable by this signal.
pub fn Signal::raw_max(self : Signal) -> UInt {
if self.size >= 32 {
0xFFFFFFFF
} else {
(1 << self.size) - 1
}
}
///|
/// Decode a signed raw value using the signal width.
pub fn Signal::decode_signed(self : Signal, data : Array[Byte]) -> Int {
let raw = self.decode_raw(data)
if !self.signed || self.size == 0 {
raw.reinterpret_as_int()
} else if ((raw >> (self.size - 1)) & 1) == 0 {
raw.reinterpret_as_int()
} else {
let mask : UInt = if self.size >= 32 {
0xFFFFFFFF
} else {
(1 << self.size) - 1
}
(raw | (0xFFFFFFFF ^ mask)).reinterpret_as_int()
}
}
///|
/// Decode a physical value while honoring the DBC signedness flag.
pub fn Signal::decode_physical(self : Signal, data : Array[Byte]) -> Double {
self.decode_signed(data).to_double() * self.factor + self.offset
}
///|
/// Return the signal's inclusive physical range when configured.
pub fn Signal::has_range(self : Signal) -> Bool {
self.maximum > self.minimum
}
///|
/// Check a physical value against the configured range.
pub fn Signal::accepts(self : Signal, value : Double) -> Bool {
!self.has_range() || (value >= self.minimum && value <= self.maximum)
}
///|
/// Render a decoded message as a stable diagnostic line.
pub fn DecodedMessage::to_text(self : DecodedMessage) -> String {
let builder = StringBuilder()
builder.write_string("0x")
builder.write_string(self.id.to_string())
builder.write_string(" ")
builder.write_string(self.name)
for item in self.signals {
builder.write_string(" ")
builder.write_string(item.name)
builder.write_string("=")
builder.write_string(item.physical.to_string())
}
builder.to_string()
}