///|
/// Local CANopen CAN frame and medium skeleton for W1B.
///|
pub(all) enum CanopenFrameKind {
NmtFrame
HeartbeatFrame
SdoDownloadFrame
SdoUploadFrame
Rpdo1Frame
Tpdo1Frame
ErrorStateFrame
} derive(Eq, Debug)
///|
pub fn CanopenFrameKind::label(self : CanopenFrameKind) -> String {
match self {
NmtFrame => "nmt"
HeartbeatFrame => "heartbeat"
SdoDownloadFrame => "sdo-download"
SdoUploadFrame => "sdo-upload"
Rpdo1Frame => "rpdo1"
Tpdo1Frame => "tpdo1"
ErrorStateFrame => "error-state"
}
}
///|
pub(all) struct CanopenFrameEnvelope {
cob_id : Int
node_id : Int
dlc : Int
rtr : Bool
extended : Bool
kind : CanopenFrameKind
payload_digest : Int
} derive(Eq, Debug)
///|
pub fn CanopenFrameEnvelope::make(
cob_id~ : Int,
node_id~ : Int,
dlc~ : Int,
kind~ : CanopenFrameKind,
payload_digest~ : Int,
) -> CanopenFrameEnvelope {
{ cob_id, node_id, dlc, rtr: false, extended: false, kind, payload_digest }
}
///|
pub fn CanopenFrameEnvelope::valid_standard_data_frame(
self : CanopenFrameEnvelope,
) -> Bool {
self.cob_id >= 0 &&
self.cob_id <= 0x7ff &&
self.dlc >= 0 &&
self.dlc <= 8 &&
!self.rtr &&
!self.extended
}
///|
pub(all) struct CanopenFrame {
envelope : CanopenFrameEnvelope
object_index : UInt?
subindex : Byte?
label : String
} derive(Eq, Debug)
///|
pub fn CanopenFrame::valid(self : CanopenFrame) -> Bool {
self.envelope.valid_standard_data_frame() && self.label != ""
}
///|
pub(all) struct CanopenSdoExpeditedFrame {
frame : CanopenFrame
command_specifier : Int
expedited : Bool
size_indicated : Bool
byte_count : Int
} derive(Eq, Debug)
///|
pub fn CanopenSdoExpeditedFrame::valid(self : CanopenSdoExpeditedFrame) -> Bool {
self.frame.valid() &&
self.expedited &&
self.size_indicated &&
self.byte_count > 0 &&
self.byte_count <= 4
}
///|
pub(all) struct CanopenPdoFrame {
frame : CanopenFrame
channel : String
mapped_objects : Array[String]
direction : @trace.TraceDirection
} derive(Eq, Debug)
///|
pub fn CanopenPdoFrame::valid(self : CanopenPdoFrame) -> Bool {
self.frame.valid() &&
self.mapped_objects.length() > 0 &&
(self.direction == @trace.Tx || self.direction == @trace.Rx)
}
///|
pub(all) struct CanopenArbitrationSample {
left : CanopenFrame
right : CanopenFrame
winner : CanopenFrame
loser : CanopenFrame
deterministic : Bool
} derive(Eq, Debug)
///|
pub fn CanopenArbitrationSample::from_pair(
left~ : CanopenFrame,
right~ : CanopenFrame,
) -> CanopenArbitrationSample {
if left.envelope.cob_id <= right.envelope.cob_id {
{ left, right, winner: left, loser: right, deterministic: true }
} else {
{ left, right, winner: right, loser: left, deterministic: true }
}
}
///|
pub fn CanopenArbitrationSample::passes(
self : CanopenArbitrationSample,
) -> Bool {
self.deterministic &&
self.winner.envelope.cob_id <= self.loser.envelope.cob_id
}
///|
pub(all) enum CanopenErrorState {
ErrorActive
ErrorPassive
BusOff
} derive(Eq, Debug)
///|
pub fn CanopenErrorState::label(self : CanopenErrorState) -> String {
match self {
ErrorActive => "error_active"
ErrorPassive => "error_passive"
BusOff => "bus_off"
}
}
///|
pub(all) struct CanopenErrorCounters {
tx_error_count : Int
rx_error_count : Int
} derive(Eq, Debug)
///|
pub fn CanopenErrorCounters::state(
self : CanopenErrorCounters,
) -> CanopenErrorState {
if self.tx_error_count >= 256 {
BusOff
} else if self.tx_error_count >= 128 || self.rx_error_count >= 128 {
ErrorPassive
} else {
ErrorActive
}
}
///|
pub(all) struct CanopenErrorStateSample {
counters : CanopenErrorCounters
state : CanopenErrorState
trace_direction : @trace.TraceDirection
live_bus_evidence : Bool
} derive(Eq, Debug)
///|
pub fn CanopenErrorStateSample::passes(self : CanopenErrorStateSample) -> Bool {
self.state == self.counters.state() &&
self.trace_direction == @trace.Probe &&
!self.live_bus_evidence
}
///|
pub(all) struct CanopenFrameMediumReport {
sdo_expedited_valid : Bool
pdo_frames_valid : Bool
arbitration_valid : Bool
error_state_valid : Bool
trace_event_count : Int
replay_count : Int
trace_digest : @core.SimDigest
observer_passes : Bool
observer_checked_events : Int
protocol_matched_steps : Int
live_can_evidence : Bool
} derive(Eq, Debug)
///|
pub fn CanopenFrameMediumReport::passes(
self : CanopenFrameMediumReport,
) -> Bool {
self.sdo_expedited_valid &&
self.pdo_frames_valid &&
self.arbitration_valid &&
self.error_state_valid &&
self.replay_count == self.trace_event_count &&
self.observer_passes &&
self.observer_checked_events == 8 &&
self.protocol_matched_steps == 8 &&
!self.live_can_evidence
}
///|
pub fn canopen_sdo_expedited_download_fixture(
node_id? : Int = 1,
index? : UInt = @profile_cia402.cia402_mode_of_operation_index,
subindex? : Byte = b'\x00',
value_digest? : Int = 0x08,
) -> CanopenSdoExpeditedFrame {
let frame = CanopenFrame::make(
cob_id=canopen_sdo_download_cob_id(node_id~),
node_id~,
dlc=8,
kind=SdoDownloadFrame,
payload_digest=sdo_payload_digest(
command_specifier=0x23,
index~,
subindex~,
value_digest~,
),
label="canopen.frame.sdo.download.expedited",
object_index=Some(index),
subindex=Some(subindex),
)
{
frame,
command_specifier: 0x23,
expedited: true,
size_indicated: true,
byte_count: 4,
}
}
///|
pub fn canopen_sdo_expedited_upload_fixture(
node_id? : Int = 1,
index? : UInt = @profile_cia402.cia402_fault_index,
subindex? : Byte = b'\x00',
value_digest? : Int = 0x0000,
) -> CanopenSdoExpeditedFrame {
let frame = CanopenFrame::make(
cob_id=canopen_sdo_upload_cob_id(node_id~),
node_id~,
dlc=8,
kind=SdoUploadFrame,
payload_digest=sdo_payload_digest(
command_specifier=0x43,
index~,
subindex~,
value_digest~,
),
label="canopen.frame.sdo.upload.expedited",
object_index=Some(index),
subindex=Some(subindex),
)
{
frame,
command_specifier: 0x43,
expedited: true,
size_indicated: true,
byte_count: 4,
}
}
///|
pub fn canopen_rpdo1_frame_fixture(node_id? : Int = 1) -> CanopenPdoFrame {
{
frame: CanopenFrame::make(
cob_id=canopen_rpdo1_cob_id(node_id~),
node_id~,
dlc=8,
kind=Rpdo1Frame,
payload_digest=0x6040 * 131 + 0x607a,
label="canopen.frame.rpdo1.tx",
object_index=None,
subindex=None,
),
channel: "rpdo1",
mapped_objects: ["axis.controlword", "axis.target_position"],
direction: @trace.Tx,
}
}
///|
pub fn canopen_tpdo1_frame_fixture(node_id? : Int = 1) -> CanopenPdoFrame {
{
frame: CanopenFrame::make(
cob_id=canopen_tpdo1_cob_id(node_id~),
node_id~,
dlc=8,
kind=Tpdo1Frame,
payload_digest=0x6041 * 131 + 0x6064,
label="canopen.frame.tpdo1.rx",
object_index=None,
subindex=None,
),
channel: "tpdo1",
mapped_objects: ["axis.statusword", "axis.actual_position"],
direction: @trace.Rx,
}
}
///|
pub fn canopen_arbitration_sample_fixture(
node_id? : Int = 1,
) -> CanopenArbitrationSample {
CanopenArbitrationSample::from_pair(
left=canopen_rpdo1_frame_fixture(node_id~).frame,
right=canopen_tpdo1_frame_fixture(node_id~).frame,
)
}
///|
pub fn canopen_error_state_sample_fixture() -> CanopenErrorStateSample {
let counters = { tx_error_count: 130, rx_error_count: 0 }
{
counters,
state: counters.state(),
trace_direction: @trace.Probe,
live_bus_evidence: false,
}
}
///|
pub fn canopen_frame_medium_trace_fixture(
node_id? : Int = 1,
seed? : Int = 401,
) -> @trace.TraceLog {
let log = canopen_cia402_trace_fixture(node_id~)
let sdo_download = canopen_sdo_expedited_download_fixture(node_id~)
let sdo_upload = canopen_sdo_expedited_upload_fixture(node_id~)
let rpdo1 = canopen_rpdo1_frame_fixture(node_id~)
let tpdo1 = canopen_tpdo1_frame_fixture(node_id~)
let arbitration = canopen_arbitration_sample_fixture(node_id~)
let error_state = canopen_error_state_sample_fixture()
append_can_frame_event(
log,
11,
sdo_download.frame,
@trace.Tx,
100L,
seed~,
node_text="controller",
)
append_can_frame_event(
log,
12,
sdo_upload.frame,
@trace.Rx,
110L,
seed~,
node_text="drive",
)
append_can_frame_event(
log,
13,
rpdo1.frame,
rpdo1.direction,
120L,
seed~,
node_text="controller",
)
append_can_frame_event(
log,
14,
tpdo1.frame,
tpdo1.direction,
130L,
seed~,
node_text="drive",
)
append_can_probe_event(
log,
15,
label="canopen.arbitration.winner." +
arbitration.winner.envelope.kind.label(),
payload_digest=arbitration.winner.envelope.cob_id,
time_ns=140L,
seed~,
)
append_can_probe_event(
log,
16,
label="canopen.error_state." + error_state.state.label(),
payload_digest=error_state.counters.tx_error_count,
time_ns=150L,
seed~,
)
log
}
///|
pub fn canopen_frame_medium_report(
node_id? : Int = 1,
seed? : Int = 401,
) -> CanopenFrameMediumReport {
let trace = canopen_frame_medium_trace_fixture(node_id~, seed~)
let observer = canopen_cia402_observer_backend(node_id~).check_trace(trace)
let sdo_download = canopen_sdo_expedited_download_fixture(node_id~)
let sdo_upload = canopen_sdo_expedited_upload_fixture(node_id~)
let rpdo1 = canopen_rpdo1_frame_fixture(node_id~)
let tpdo1 = canopen_tpdo1_frame_fixture(node_id~)
{
sdo_expedited_valid: sdo_download.valid() && sdo_upload.valid(),
pdo_frames_valid: rpdo1.valid() && tpdo1.valid(),
arbitration_valid: canopen_arbitration_sample_fixture(node_id~).passes(),
error_state_valid: canopen_error_state_sample_fixture().passes(),
trace_event_count: trace.len(),
replay_count: replay_count(trace),
trace_digest: trace.portable_digest(seed~),
observer_passes: observer.passes(),
observer_checked_events: observer.checked_events,
protocol_matched_steps: observer.matched_steps,
live_can_evidence: false,
}
}
///|
fn CanopenFrame::make(
cob_id~ : Int,
node_id~ : Int,
dlc~ : Int,
kind~ : CanopenFrameKind,
payload_digest~ : Int,
label~ : String,
object_index~ : UInt?,
subindex~ : Byte?,
) -> CanopenFrame {
{
envelope: CanopenFrameEnvelope::make(
cob_id~,
node_id~,
dlc~,
kind~,
payload_digest~,
),
object_index,
subindex,
label,
}
}
///|
fn append_can_frame_event(
log : @trace.TraceLog,
event_id : Int,
frame : CanopenFrame,
direction : @trace.TraceDirection,
time_ns : Int64,
seed~ : Int,
node_text~ : String,
) -> Unit {
log.append(
@trace.TraceEvent::make(
event_id~,
parent_id=None,
vtime=@core.VTime::from_ns(time_ns),
clock_domain="sim",
raw_ns=time_ns,
node_id=node_text,
medium_id=canopen_medium_id(),
channel_id=Some(@core.ChannelId(frame.envelope.cob_id)),
direction~,
payload_digest=Some(frame.envelope.payload_digest),
rng_step=event_id,
seed~,
backend=@core.SimNative,
label=frame.label,
),
)
}
///|
fn append_can_probe_event(
log : @trace.TraceLog,
event_id : Int,
label~ : String,
payload_digest~ : Int,
time_ns~ : Int64,
seed~ : Int,
) -> Unit {
log.append(
@trace.TraceEvent::make(
event_id~,
parent_id=None,
vtime=@core.VTime::from_ns(time_ns),
clock_domain="sim",
raw_ns=time_ns,
node_id="can-bus",
medium_id=canopen_medium_id(),
channel_id=None,
direction=@trace.Probe,
payload_digest=Some(payload_digest),
rng_step=event_id,
seed~,
backend=@core.SimNative,
label~,
),
)
}
///|
fn sdo_payload_digest(
command_specifier~ : Int,
index~ : UInt,
subindex~ : Byte,
value_digest~ : Int,
) -> Int {
(
(command_specifier * 131 + index.reinterpret_as_int()) * 131 +
subindex.to_int()
) *
131 +
value_digest
}
///|
fn replay_count(log : @trace.TraceLog) -> Int {
let replay = @trace.Replay::from_log(log)
let mut count = 0
while !replay.is_exhausted() {
ignore(replay.next_event())
count += 1
}
count
}