///|
/// Errors raised by J1939 identifier and transport helpers.
pub suberror J1939Error {
InvalidPriority
InvalidPgn
InvalidAddress
InvalidPayloadLength
InvalidSequence
TransferTooLarge
} derive(Debug)
///|
/// Validate a J1939 identifier's fields.
pub fn validate_j1939(value : J1939Id) -> Unit raise J1939Error {
if value.priority > 7 {
raise InvalidPriority
}
if value.pdu_format == 0 {
raise InvalidPgn
}
}
///|
pub fn J1939Id::priority(self : J1939Id) -> Byte {
self.priority
}
///|
pub fn J1939Id::data_page(self : J1939Id) -> Bool {
self.data_page
}
///|
pub fn J1939Id::pdu_format(self : J1939Id) -> Byte {
self.pdu_format
}
///|
pub fn J1939Id::pdu_specific(self : J1939Id) -> Byte {
self.pdu_specific
}
///|
pub fn J1939Id::destination(self : J1939Id) -> Byte? {
if self.pdu_format < 240 {
Some(self.pdu_specific)
} else {
None
}
}
///|
/// A J1939 application payload with its decoded identifier.
pub struct J1939Message {
identifier : J1939Id
payload : Array[Byte]
}
///|
pub fn j1939_message(
identifier : J1939Id,
payload : Array[Byte],
) -> J1939Message raise J1939Error {
validate_j1939(identifier)
if payload.length() > 8 {
raise InvalidPayloadLength
}
{ identifier, payload: payload.copy() }
}
///|
pub fn J1939Message::identifier(self : J1939Message) -> J1939Id {
self.identifier
}
///|
pub fn J1939Message::payload(self : J1939Message) -> Array[Byte] {
self.payload.copy()
}
///|
/// Create a data frame from a J1939 message.
pub fn J1939Message::to_frame(self : J1939Message) -> Frame raise FrameError {
data_frame(encode_j1939(self.identifier), self.payload, extended=true)
}
///|
/// Decode an extended CAN data frame as a J1939 message.
pub fn decode_j1939_frame(frame : Frame) -> J1939Message? raise J1939Error {
if !frame.is_extended() || !frame.is_data() {
return None
}
let identifier = decode_j1939(frame.id())
Some(j1939_message(identifier, frame.data()))
}
///|
/// Return the numeric destination address, or broadcast for PDU2 messages.
pub fn j1939_destination(identifier : J1939Id) -> Byte {
match identifier.destination() {
Some(value) => value
None => 0xFF
}
}
///|
/// Test whether a J1939 identifier belongs to a PGN.
pub fn j1939_matches_pgn(identifier : J1939Id, pgn : UInt) -> Bool {
identifier.pgn() == pgn
}
///|
/// Return a priority-adjusted arbitration identifier.
pub fn j1939_with_priority(
identifier : J1939Id,
priority : Byte,
) -> J1939Id raise J1939Error {
if priority > 7 {
raise InvalidPriority
}
{
priority,
data_page: identifier.data_page,
pdu_format: identifier.pdu_format,
pdu_specific: identifier.pdu_specific,
source_address: identifier.source_address,
}
}
///|
/// Return a source-address-adjusted identifier.
pub fn j1939_with_source(
identifier : J1939Id,
source : Byte,
) -> J1939Id raise J1939Error {
if source > 0xFF {
raise InvalidAddress
}
{
priority: identifier.priority,
data_page: identifier.data_page,
pdu_format: identifier.pdu_format,
pdu_specific: identifier.pdu_specific,
source_address: source,
}
}
///|
/// Build a J1939 BAM transport-protocol announcement.
pub fn j1939_bam(
pgn : UInt,
payload : Array[Byte],
) -> Array[Array[Byte]] raise J1939Error {
if pgn > 0x3FFFF {
raise InvalidPgn
}
if payload.is_empty() || payload.length() > 1785 {
raise TransferTooLarge
}
let packet_count = (payload.length() + 6) / 7
let announcement : Array[Byte] = [
0x20,
payload.length().to_byte(),
(payload.length() >> 8).to_byte(),
packet_count.to_byte(),
0xFF,
pgn.to_byte(),
(pgn >> 8).to_byte(),
(pgn >> 16).to_byte(),
]
let result : Array[Array[Byte]] = [announcement]
let mut offset = 0
for sequence in 1..<=packet_count {
let count = if payload.length() - offset > 7 {
7
} else {
payload.length() - offset
}
let packet = [sequence.to_byte()] +
payload[offset:offset + count].to_owned()
result.push(packet + Array::make(8 - packet.length(), 0xFF))
offset += count
}
result
}
///|
/// Parse a BAM announcement as `(length, packet_count, pgn)`.
pub fn j1939_parse_bam(announcement : Array[Byte]) -> (Int, Int, UInt)? {
if announcement.length() < 8 || announcement[0] != 0x20 {
return None
}
let length = announcement[1].to_int() | (announcement[2].to_int() << 8)
let count = announcement[3].to_int()
let pgn = announcement[5].to_uint() |
(announcement[6].to_uint() << 8) |
(announcement[7].to_uint() << 16)
Some((length, count, pgn))
}
///|
/// Reassemble BAM data packets after validating their sequence numbers.
pub fn j1939_reassemble_bam(
packets : Array[Array[Byte]],
total_length : Int,
) -> Array[Byte] raise J1939Error {
if total_length <= 0 || total_length > 1785 {
raise TransferTooLarge
}
let result : Array[Byte] = []
let mut expected = 1
for packet in packets {
if packet.is_empty() || packet[0].to_int() != expected {
raise InvalidSequence
}
for byte in packet[1:] {
if result.length() < total_length {
result.push(byte)
}
}
expected += 1
}
if result.length() != total_length {
raise InvalidPayloadLength
}
result
}
///|
/// Return the standard J1939 priority ordering for messages.
pub fn sort_j1939(messages : Array[J1939Message]) -> Array[J1939Message] {
let result = messages.copy()
result.sort_by((left, right) => {
let left_id = encode_j1939(left.identifier)
let right_id = encode_j1939(right.identifier)
if left_id < right_id {
-1
} else if left_id > right_id {
1
} else {
0
}
})
result
}