///|
/// Explicit physical/raw/label assignment; never infer meaning from a number.
pub(all) enum SignalValue {
Physical(Double)
Raw(RawValue)
Label(String)
} derive(Eq, @debug.Debug)
///|
/// One named input assignment.
pub(all) struct SignalAssignment {
name : String
value : SignalValue
} derive(Eq, @debug.Debug)
///|
/// Decoded data preserves exact raw values and reports physical range violations.
pub(all) struct DecodedSignal {
name : String
raw : RawValue
physical : Double
unit : String
label : String?
out_of_range : Bool
} derive(Eq, @debug.Debug)
///|
/// A selected message and only its currently active signals.
pub(all) struct DecodedMessage {
name : String
frame_id : FrameId
signals : Array[DecodedSignal]
diagnostics : Array[Diagnostic]
} derive(Eq, @debug.Debug)
///|
/// Encoded frame and warnings (range policy and unknown mux).
pub(all) struct EncodedMessage {
frame : CanFrame
diagnostics : Array[Diagnostic]
} derive(Eq, @debug.Debug)
///|
/// Validated snapshot with cached bit coordinates for repeated frame processing.
pub struct CompiledMessage {
message : Message
positions : Array[Array[Int]]
selector : Int?
}
///|
/// Validate codec semantics once and snapshot mutable arrays before reuse.
pub fn compile_message(message : Message) -> CompiledMessage raise DbcError {
let findings = []
validate_message(message, Database::new(), findings, check_nodes=false)
for d in findings {
if d.severity == Error {
raise Failure(d)
}
}
ignore(FrameId::new(message.frame_id.id, extended=message.frame_id.extended))
if message.raw_dbc_id != message.frame_id.to_dbc() {
fail("message.raw_id", "inconsistent frame identity")
}
let signals = message.signals.map(fn(s) {
Signal::{ ..s, values: s.values.copy(), receivers: s.receivers.copy() }
})
let snapshot = Message::{ ..message, signals, }
let positions = []
let mut selector = None
for i, s in signals {
positions.push(signal_bits(s, message.length))
if s.multiplex == Multiplexer {
selector = Some(i)
}
}
{ message: snapshot, positions, selector }
}
///|
fn raw_from_positions(
s : Signal,
payload : Array[Byte],
positions : Array[Int],
) -> RawValue {
let bits = match s.byte_order {
Intel => extract_intel(payload, positions)
Motorola => extract_motorola(payload, positions)
}
if !s.signed {
return Unsigned(bits)
}
if s.bit_length == 64 {
return Signed(bits.reinterpret_as_int64())
}
let extended = if (bits & (1UL << (s.bit_length - 1))) != 0UL {
bits | (0xFFFFFFFFFFFFFFFFUL << s.bit_length)
} else {
bits
}
Signed(extended.reinterpret_as_int64())
}
///|
fn branch_active(mux : MultiplexInfo, selector : UInt64?) -> Bool {
match mux {
Always | Multiplexer => true
Branch(n) =>
match selector {
Some(v) => n >= 0L && n.reinterpret_as_uint64() == v
None => false
}
}
}
///|
fn unknown_mux(message : Message, selector : UInt64?) -> Bool {
let mut branches = false
let mut known = false
for s in message.signals {
if s.multiplex is Branch(_) {
branches = true
if branch_active(s.multiplex, selector) {
known = true
}
}
}
branches && !known
}
///|
fn enum_label(signal : Signal, raw : RawValue) -> String? {
for v in signal.values {
let matches = match raw {
Signed(n) => n == v.raw
Unsigned(n) => v.raw >= 0L && n == v.raw.reinterpret_as_uint64()
}
if matches {
return Some(v.label)
}
}
None
}
///|
/// Decode using cached layout. Payload length and frame kind must match exactly.
pub fn CompiledMessage::decode(
self : CompiledMessage,
frame : CanFrame,
) -> DecodedMessage raise DbcError {
let m = self.message
if frame.frame_id != m.frame_id {
fail("codec.frame_id", "frame identity does not match message")
}
if frame.payload.length() != m.length {
fail(
"codec.payload_length", "payload length must exactly match database message",
)
}
let selector = match self.selector {
Some(i) =>
match raw_from_positions(m.signals[i], frame.payload, self.positions[i]) {
Unsigned(v) => Some(v)
Signed(_) => None
}
None => None
}
let signals = []
let diagnostics = []
if unknown_mux(m, selector) {
diagnostics.push(
diagnostic(
"mux.unknown",
"selector has no defined branch",
message_name=Some(m.name),
severity=Warning,
),
)
}
for i, s in m.signals {
if !branch_active(s.multiplex, selector) {
continue
}
let raw = raw_from_positions(s, frame.payload, self.positions[i])
let physical = raw_to_physical(s, raw)
let out_of_range = physical < s.minimum || physical > s.maximum
if out_of_range {
diagnostics.push(
diagnostic(
"codec.decoded_range",
"decoded physical value exceeds declared range",
message_name=Some(m.name),
signal_name=Some(s.name),
severity=Warning,
),
)
}
signals.push({
name: s.name,
raw,
physical,
unit: s.unit,
label: enum_label(s, raw),
out_of_range,
})
}
{ name: m.name, frame_id: m.frame_id, signals, diagnostics }
}
///|
fn assignment_raw(
s : Signal,
value : SignalValue,
policy : RangePolicy,
diagnostics : Array[Diagnostic],
) -> RawValue raise DbcError {
match value {
Raw(raw) => {
ignore(checked_raw_bits(s, raw))
raw
}
Physical(v) => {
let raw = physical_to_raw(s, v, range_policy=policy)
if policy == Warn &&
(
v < s.minimum ||
v > s.maximum ||
raw_to_physical(s, raw) < s.minimum ||
raw_to_physical(s, raw) > s.maximum
) {
diagnostics.push(
diagnostic(
"codec.encoded_range",
"physical assignment exceeds declared range",
signal_name=Some(s.name),
severity=Warning,
),
)
}
raw
}
Label(label) => {
for v in s.values {
if v.label == label {
let raw = if s.signed {
Signed(v.raw)
} else {
Unsigned(v.raw.reinterpret_as_uint64())
}
ignore(checked_raw_bits(s, raw))
return raw
}
}
fail("codec.label", "unknown enumeration label \{label}")
Unsigned(0UL)
}
}
}
///|
/// Encode all active signals; inactive, duplicate, missing and unknown inputs fail.
/// Unoccupied payload bits are zero. Raw inputs bypass physical range policy.
pub fn CompiledMessage::encode(
self : CompiledMessage,
assignments : Array[SignalAssignment],
range_policy? : RangePolicy = Reject,
) -> EncodedMessage raise DbcError {
let m = self.message
let inputs : Map[String, SignalValue] = Map([])
let diagnostics = []
for a in assignments {
if m.signal_by_name(a.name) is None {
fail("codec.unknown_signal", "unknown signal \{a.name}")
}
if inputs.contains(a.name) {
fail("codec.duplicate_input", "duplicate signal \{a.name}")
}
inputs[a.name] = a.value
}
let selector = match self.selector {
Some(i) => {
let s = m.signals[i]
let value = match inputs.get(s.name) {
Some(v) => v
None => {
fail("codec.missing_input", "missing selector \{s.name}")
Raw(Unsigned(0UL))
}
}
match assignment_raw(s, value, range_policy, diagnostics) {
Unsigned(n) => Some(n)
Signed(_) => None
}
}
None => None
}
if unknown_mux(m, selector) {
diagnostics.push(
diagnostic(
"mux.unknown",
"selector has no defined branch",
message_name=Some(m.name),
severity=Warning,
),
)
}
let payload : Array[Byte] = Array::make(m.length, 0)
for i, s in m.signals {
if !branch_active(s.multiplex, selector) {
if inputs.contains(s.name) {
fail(
"codec.inactive_input",
"assignment belongs to inactive branch: \{s.name}",
)
}
continue
}
let value = match inputs.get(s.name) {
Some(v) => v
None => {
fail("codec.missing_input", "missing active signal \{s.name}")
Raw(Unsigned(0UL))
}
}
let raw = if s.multiplex == Multiplexer {
Unsigned(selector.unwrap())
} else {
assignment_raw(s, value, range_policy, diagnostics)
}
write_bits(s, payload, self.positions[i], checked_raw_bits(s, raw))
}
{ frame: { frame_id: m.frame_id, payload }, diagnostics }
}
///|
/// Look up the frame identity in a database and decode its active signals.
pub fn decode_frame(
db : Database,
frame : CanFrame,
) -> DecodedMessage raise DbcError {
let message = match db.message_by_id(frame.frame_id) {
Some(m) => m
None => {
fail("codec.unknown_id", "unknown frame identity")
return {
name: "",
frame_id: frame.frame_id,
signals: [],
diagnostics: [],
}
}
}
compile_message(message).decode(frame)
}
///|
/// Encode a semantic message with explicit signal values and range policy.
pub fn encode_message(
message : Message,
assignments : Array[SignalAssignment],
range_policy? : RangePolicy = Reject,
) -> EncodedMessage raise DbcError {
compile_message(message).encode(assignments, range_policy~)
}