///|
/// Lifecycle state of a portable CAN adapter.
pub enum CanAdapterLifecycle {
CanAdapterCreated
CanAdapterConfigured
CanAdapterOpen
CanAdapterBusOff
CanAdapterClosed
}
///|
pub fn can_adapter_lifecycle_variants() -> Array[CanAdapterLifecycle] {
[
CanAdapterCreated,
CanAdapterConfigured,
CanAdapterOpen,
CanAdapterBusOff,
CanAdapterClosed,
]
}
///|
/// Capabilities exposed by a hardware or virtual CAN backend.
pub struct CanAdapterCapabilities {
classic : Bool
can_fd : Bool
bitrate_switch : Bool
listen_only : Bool
loopback : Bool
timestamping : Bool
max_rx_queue : Int
max_tx_queue : Int
}
///|
pub fn can_adapter_capabilities(
classic? : Bool = true,
can_fd? : Bool = true,
bitrate_switch? : Bool = true,
listen_only? : Bool = true,
loopback? : Bool = true,
timestamping? : Bool = true,
max_rx_queue? : Int = 1024,
max_tx_queue? : Int = 1024,
) -> CanAdapterCapabilities {
{
classic,
can_fd,
bitrate_switch,
listen_only,
loopback,
timestamping,
max_rx_queue,
max_tx_queue,
}
}
///|
pub fn CanAdapterCapabilities::classic(self : CanAdapterCapabilities) -> Bool {
self.classic
}
///|
pub fn CanAdapterCapabilities::can_fd(self : CanAdapterCapabilities) -> Bool {
self.can_fd
}
///|
pub fn CanAdapterCapabilities::bitrate_switch(
self : CanAdapterCapabilities,
) -> Bool {
self.bitrate_switch
}
///|
pub fn CanAdapterCapabilities::listen_only(
self : CanAdapterCapabilities,
) -> Bool {
self.listen_only
}
///|
pub fn CanAdapterCapabilities::loopback(self : CanAdapterCapabilities) -> Bool {
self.loopback
}
///|
pub fn CanAdapterCapabilities::timestamping(
self : CanAdapterCapabilities,
) -> Bool {
self.timestamping
}
///|
pub fn CanAdapterCapabilities::max_rx_queue(
self : CanAdapterCapabilities,
) -> Int {
self.max_rx_queue
}
///|
pub fn CanAdapterCapabilities::max_tx_queue(
self : CanAdapterCapabilities,
) -> Int {
self.max_tx_queue
}
///|
/// Configuration requested from an adapter before opening it.
pub struct CanAdapterConfig {
nominal_bitrate_kbps : UInt
data_bitrate_kbps : UInt
listen_only : Bool
loopback : Bool
receive_own : Bool
filter : Filter?
}
///|
pub fn can_adapter_config(
nominal_bitrate_kbps? : UInt = 500,
data_bitrate_kbps? : UInt = 2_000,
listen_only? : Bool = false,
loopback? : Bool = false,
receive_own? : Bool = false,
filter? : Filter? = None,
) -> CanAdapterConfig {
{
nominal_bitrate_kbps,
data_bitrate_kbps,
listen_only,
loopback,
receive_own,
filter,
}
}
///|
pub fn CanAdapterConfig::nominal_bitrate_kbps(self : CanAdapterConfig) -> UInt {
self.nominal_bitrate_kbps
}
///|
pub fn CanAdapterConfig::data_bitrate_kbps(self : CanAdapterConfig) -> UInt {
self.data_bitrate_kbps
}
///|
pub fn CanAdapterConfig::listen_only(self : CanAdapterConfig) -> Bool {
self.listen_only
}
///|
pub fn CanAdapterConfig::loopback(self : CanAdapterConfig) -> Bool {
self.loopback
}
///|
pub fn CanAdapterConfig::receive_own(self : CanAdapterConfig) -> Bool {
self.receive_own
}
///|
pub fn CanAdapterConfig::filter(self : CanAdapterConfig) -> Filter? {
self.filter
}
///|
/// Errors returned by the backend-neutral adapter façade.
pub suberror CanAdapterError {
CanAdapterNotOpen
CanAdapterAlreadyOpen
CanAdapterBusOffError
CanAdapterQueueFull
CanAdapterUnsupported
CanAdapterInvalidConfiguration
CanAdapterRejectedFrame
}
///|
/// A timestamped frame delivered by an adapter.
pub struct CanAdapterMessage {
timestamp_us : UInt64
frame : Frame
direction_tx : Bool
sequence : UInt
}
///|
pub fn can_adapter_message(
timestamp_us : UInt64,
frame : Frame,
direction_tx : Bool,
sequence : UInt,
) -> CanAdapterMessage {
{ timestamp_us, frame, direction_tx, sequence }
}
///|
pub fn CanAdapterMessage::timestamp_us(self : CanAdapterMessage) -> UInt64 {
self.timestamp_us
}
///|
pub fn CanAdapterMessage::frame(self : CanAdapterMessage) -> Frame {
self.frame
}
///|
pub fn CanAdapterMessage::direction_tx(self : CanAdapterMessage) -> Bool {
self.direction_tx
}
///|
pub fn CanAdapterMessage::sequence(self : CanAdapterMessage) -> UInt {
self.sequence
}
///|
pub fn CanAdapterMessage::direction_text(self : CanAdapterMessage) -> String {
if self.direction_tx {
"tx"
} else {
"rx"
}
}
///|
/// Counters maintained by a CAN adapter.
pub struct CanAdapterStats {
mut transmitted : Int
mut received : Int
mut dropped_rx : Int
mut rejected_tx : Int
mut bus_off : Int
mut error_frames : Int
mut bytes_tx : Int
mut bytes_rx : Int
}
///|
pub fn new_can_adapter_stats() -> CanAdapterStats {
{
transmitted: 0,
received: 0,
dropped_rx: 0,
rejected_tx: 0,
bus_off: 0,
error_frames: 0,
bytes_tx: 0,
bytes_rx: 0,
}
}
///|
pub fn CanAdapterStats::transmitted(self : CanAdapterStats) -> Int {
self.transmitted
}
///|
pub fn CanAdapterStats::received(self : CanAdapterStats) -> Int {
self.received
}
///|
pub fn CanAdapterStats::dropped_rx(self : CanAdapterStats) -> Int {
self.dropped_rx
}
///|
pub fn CanAdapterStats::rejected_tx(self : CanAdapterStats) -> Int {
self.rejected_tx
}
///|
pub fn CanAdapterStats::bus_off(self : CanAdapterStats) -> Int {
self.bus_off
}
///|
pub fn CanAdapterStats::error_frames(self : CanAdapterStats) -> Int {
self.error_frames
}
///|
pub fn CanAdapterStats::bytes_tx(self : CanAdapterStats) -> Int {
self.bytes_tx
}
///|
pub fn CanAdapterStats::bytes_rx(self : CanAdapterStats) -> Int {
self.bytes_rx
}
///|
pub fn CanAdapterStats::total(self : CanAdapterStats) -> Int {
self.transmitted + self.received
}
///|
pub fn CanAdapterStats::to_text(self : CanAdapterStats) -> String {
"tx=" +
self.transmitted.to_string() +
" rx=" +
self.received.to_string() +
" drop_rx=" +
self.dropped_rx.to_string() +
" reject_tx=" +
self.rejected_tx.to_string()
}
///|
/// A deterministic in-memory adapter implementing the hardware contract.
pub struct CanPort {
name : String
capabilities : CanAdapterCapabilities
mut config : CanAdapterConfig
mut lifecycle : CanAdapterLifecycle
rx_queue : Array[CanAdapterMessage]
tx_queue : Array[CanAdapterMessage]
mut sequence : UInt
stats : CanAdapterStats
}
///|
pub fn new_can_port(
name : String,
capabilities? : CanAdapterCapabilities = can_adapter_capabilities(),
) -> CanPort {
{
name,
capabilities,
config: can_adapter_config(),
lifecycle: CanAdapterCreated,
rx_queue: [],
tx_queue: [],
sequence: 0,
stats: new_can_adapter_stats(),
}
}
///|
pub fn CanPort::name(self : CanPort) -> String {
self.name
}
///|
pub fn CanPort::capabilities(self : CanPort) -> CanAdapterCapabilities {
self.capabilities
}
///|
pub fn CanPort::config(self : CanPort) -> CanAdapterConfig {
self.config
}
///|
pub fn CanPort::lifecycle(self : CanPort) -> CanAdapterLifecycle {
self.lifecycle
}
///|
pub fn CanPort::stats(self : CanPort) -> CanAdapterStats {
self.stats
}
///|
pub fn CanPort::configure(
self : CanPort,
config : CanAdapterConfig,
) -> Unit raise CanAdapterError {
if self.lifecycle is CanAdapterOpen {
raise CanAdapterAlreadyOpen
}
if config.nominal_bitrate_kbps() == 0 ||
(config.data_bitrate_kbps() == 0 && self.capabilities.can_fd()) {
raise CanAdapterInvalidConfiguration
}
if config.listen_only() && !self.capabilities.listen_only() {
raise CanAdapterUnsupported
}
if config.loopback() && !self.capabilities.loopback() {
raise CanAdapterUnsupported
}
self.config = config
self.lifecycle = CanAdapterConfigured
}
///|
pub fn CanPort::open(self : CanPort) -> Unit raise CanAdapterError {
match self.lifecycle {
CanAdapterCreated => raise CanAdapterInvalidConfiguration
CanAdapterConfigured | CanAdapterClosed => self.lifecycle = CanAdapterOpen
CanAdapterOpen => raise CanAdapterAlreadyOpen
CanAdapterBusOff => raise CanAdapterBusOffError
}
}
///|
pub fn CanPort::close(self : CanPort) -> Unit {
self.lifecycle = CanAdapterClosed
self.rx_queue.clear()
self.tx_queue.clear()
}
///|
pub fn CanPort::set_bus_off(self : CanPort) -> Unit {
self.lifecycle = CanAdapterBusOff
self.stats.bus_off += 1
}
///|
pub fn CanPort::recover(self : CanPort) -> Unit {
self.lifecycle = CanAdapterConfigured
}
///|
pub fn CanPort::transmit(
self : CanPort,
timestamp_us : UInt64,
frame : Frame,
) -> Unit raise CanAdapterError {
if !(self.lifecycle is CanAdapterOpen) {
raise CanAdapterNotOpen
}
if self.lifecycle is CanAdapterBusOff {
raise CanAdapterBusOffError
}
if frame.is_error() {
self.stats.rejected_tx += 1
self.stats.error_frames += 1
raise CanAdapterRejectedFrame
}
if self.config.filter is Some(filter) && !filter.matches(frame) {
self.stats.rejected_tx += 1
raise CanAdapterRejectedFrame
}
if self.tx_queue.length() >= self.capabilities.max_tx_queue() {
self.stats.rejected_tx += 1
raise CanAdapterQueueFull
}
self.sequence += 1
self.tx_queue.push(
can_adapter_message(timestamp_us, frame, true, self.sequence),
)
self.stats.transmitted += 1
self.stats.bytes_tx += frame.data().length()
if self.config.loopback() && self.config.receive_own() {
ignore(self.inject(timestamp_us, frame))
}
}
///|
pub fn CanPort::inject(
self : CanPort,
timestamp_us : UInt64,
frame : Frame,
) -> Bool {
if self.rx_queue.length() >= self.capabilities.max_rx_queue() {
self.stats.dropped_rx += 1
false
} else if self.config.filter is Some(filter) && !filter.matches(frame) {
false
} else {
self.sequence += 1
self.rx_queue.push(
can_adapter_message(timestamp_us, frame, false, self.sequence),
)
self.stats.received += 1
self.stats.bytes_rx += frame.data().length()
if frame.is_error() {
self.stats.error_frames += 1
}
true
}
}
///|
pub fn CanPort::receive(self : CanPort) -> CanAdapterMessage? {
if self.rx_queue.is_empty() {
None
} else {
Some(self.rx_queue.remove(0))
}
}
///|
pub fn CanPort::drain_rx(
self : CanPort,
limit? : Int = 0,
) -> Array[CanAdapterMessage] {
let result : Array[CanAdapterMessage] = []
let maximum = if limit <= 0 || limit > self.rx_queue.length() {
self.rx_queue.length()
} else {
limit
}
for _ in 0.. Array[CanAdapterMessage] {
let result : Array[CanAdapterMessage] = []
let maximum = if limit <= 0 || limit > self.tx_queue.length() {
self.tx_queue.length()
} else {
limit
}
for _ in 0.. Int {
self.rx_queue.length()
}
///|
pub fn CanPort::tx_pending(self : CanPort) -> Int {
self.tx_queue.length()
}
///|
pub fn CanPort::is_ready(self : CanPort) -> Bool {
self.lifecycle is CanAdapterOpen
}
///|
/// Estimate the wire completion time for a queued frame.
pub fn CanPort::completion_time_us(
self : CanPort,
message : CanAdapterMessage,
) -> UInt64 {
let arbitration = can_frame_duration_us(
message.frame(),
self.config.nominal_bitrate_kbps(),
self.config.data_bitrate_kbps(),
)
message.timestamp_us() + arbitration
}
///|
/// Return a queue snapshot ordered by timestamp then sequence.
pub fn CanPort::pending_tx(self : CanPort) -> Array[CanAdapterMessage] {
let result = self.tx_queue.copy()
result.sort_by((left, right) => {
if left.timestamp_us() < right.timestamp_us() {
-1
} else if left.timestamp_us() > right.timestamp_us() {
1
} else {
left.sequence().reinterpret_as_int() -
right.sequence().reinterpret_as_int()
}
})
result
}
///|
/// A collection of adapter ports used by a test harness.
pub struct CanAdapterHub {
ports : Array[CanPort]
mut routed : Int
mut missed : Int
}
///|
pub fn new_can_adapter_hub() -> CanAdapterHub {
{ ports: [], routed: 0, missed: 0 }
}
///|
pub fn CanAdapterHub::add(self : CanAdapterHub, port : CanPort) -> Bool {
if self.find(port.name()) is Some(_) {
false
} else {
self.ports.push(port)
true
}
}
///|
pub fn CanAdapterHub::find(self : CanAdapterHub, name : String) -> CanPort? {
for port in self.ports {
if port.name() == name {
return Some(port)
}
}
None
}
///|
pub fn CanAdapterHub::broadcast(
self : CanAdapterHub,
source : String,
timestamp_us : UInt64,
frame : Frame,
) -> Int {
let mut delivered = 0
for port in self.ports {
if port.name() != source && port.inject(timestamp_us, frame) {
delivered += 1
}
}
if delivered == 0 {
self.missed += 1
} else {
self.routed += delivered
}
delivered
}
///|
pub fn CanAdapterHub::ports(self : CanAdapterHub) -> Array[CanPort] {
self.ports.copy()
}
///|
pub fn CanAdapterHub::routed(self : CanAdapterHub) -> Int {
self.routed
}
///|
pub fn CanAdapterHub::missed(self : CanAdapterHub) -> Int {
self.missed
}
///|
pub fn CanAdapterHub::to_text(self : CanAdapterHub) -> String {
let lines : Array[String] = []
for port in self.ports {
lines.push(port.name() + " " + port.stats().to_text())
}
lines.join("\n")
}
///|
/// Estimate a frame duration using the nominal and data bitrates.
pub fn can_frame_duration_us(
frame : Frame,
nominal_bitrate_kbps : UInt,
data_bitrate_kbps : UInt,
) -> UInt64 {
estimate_transmission_us(frame, nominal_bitrate_kbps, data_bitrate_kbps)
}