///|
/// Local CANopen CiA 402 binding skeleton for W1A.
///|
pub fn package_id() -> String {
"fieldbus_core/canopen"
}
///|
pub fn canopen_medium_id() -> String {
"canopen"
}
///|
pub let canopen_nmt_cob_id : Int = 0x000
///|
pub let canopen_tpdo1_base_cob_id : Int = 0x180
///|
pub let canopen_rpdo1_base_cob_id : Int = 0x200
///|
pub let canopen_sdo_upload_base_cob_id : Int = 0x580
///|
pub let canopen_sdo_download_base_cob_id : Int = 0x600
///|
pub let canopen_heartbeat_base_cob_id : Int = 0x700
///|
pub fn canopen_tpdo1_cob_id(node_id~ : Int) -> Int {
canopen_tpdo1_base_cob_id + node_id
}
///|
pub fn canopen_rpdo1_cob_id(node_id~ : Int) -> Int {
canopen_rpdo1_base_cob_id + node_id
}
///|
pub fn canopen_sdo_upload_cob_id(node_id~ : Int) -> Int {
canopen_sdo_upload_base_cob_id + node_id
}
///|
pub fn canopen_sdo_download_cob_id(node_id~ : Int) -> Int {
canopen_sdo_download_base_cob_id + node_id
}
///|
pub fn canopen_heartbeat_cob_id(node_id~ : Int) -> Int {
canopen_heartbeat_base_cob_id + node_id
}
///|
pub(all) enum CanopenNmtCommand {
EnterOperational
EnterStopped
EnterPreOperational
ResetNode
ResetCommunication
} derive(Eq, Debug)
///|
pub fn CanopenNmtCommand::code(self : CanopenNmtCommand) -> Int {
match self {
EnterOperational => 1
EnterStopped => 2
EnterPreOperational => 128
ResetNode => 129
ResetCommunication => 130
}
}
///|
pub fn CanopenNmtCommand::label(self : CanopenNmtCommand) -> String {
match self {
EnterOperational => "enter_operational"
EnterStopped => "enter_stopped"
EnterPreOperational => "enter_pre_operational"
ResetNode => "reset_node"
ResetCommunication => "reset_communication"
}
}
///|
pub(all) enum CanopenNmtState {
Bootup
PreOperational
Operational
Stopped
} derive(Eq, Debug)
///|
pub fn CanopenNmtState::code(self : CanopenNmtState) -> Int {
match self {
Bootup => 0
PreOperational => 127
Operational => 5
Stopped => 4
}
}
///|
pub fn CanopenNmtState::label(self : CanopenNmtState) -> String {
match self {
Bootup => "bootup"
PreOperational => "pre_operational"
Operational => "operational"
Stopped => "stopped"
}
}
///|
pub(all) struct CanopenCia402Asset {
object_id : String
label : String
index : UInt
subindex : Byte
channel : String
cob_id : Int
direction : @trace.TraceDirection
behavior : String
} derive(Eq, Debug)
///|
pub fn canopen_cia402_assets(node_id? : Int = 1) -> Array[CanopenCia402Asset] {
[
{
object_id: "axis.controlword",
label: "cia402.controlword.write",
index: @profile_cia402.cia402_controlword_index,
subindex: b'\x00',
channel: "rpdo1",
cob_id: canopen_rpdo1_cob_id(node_id~),
direction: @trace.Tx,
behavior: "controller command path uses RPDO1 for 0x6040:0 controlword",
},
{
object_id: "axis.statusword",
label: "cia402.statusword.read",
index: @profile_cia402.cia402_statusword_index,
subindex: b'\x00',
channel: "tpdo1",
cob_id: canopen_tpdo1_cob_id(node_id~),
direction: @trace.Rx,
behavior: "drive feedback path uses TPDO1 for 0x6041:0 statusword",
},
{
object_id: "axis.mode",
label: "cia402.mode.write",
index: @profile_cia402.cia402_mode_of_operation_index,
subindex: b'\x00',
channel: "sdo-download",
cob_id: canopen_sdo_download_cob_id(node_id~),
direction: @trace.Tx,
behavior: "controller writes 0x6060:0 mode of operation through SDO download",
},
{
object_id: "axis.target_position",
label: "cia402.target_position.write",
index: @profile_cia402.cia402_target_position_index,
subindex: b'\x00',
channel: "rpdo1",
cob_id: canopen_rpdo1_cob_id(node_id~),
direction: @trace.Tx,
behavior: "controller cyclic setpoint path uses RPDO1 for 0x607A:0 target position",
},
{
object_id: "axis.actual_position",
label: "cia402.actual_position.read",
index: @profile_cia402.cia402_actual_position_index,
subindex: b'\x00',
channel: "tpdo1",
cob_id: canopen_tpdo1_cob_id(node_id~),
direction: @trace.Rx,
behavior: "drive cyclic feedback path uses TPDO1 for 0x6064:0 actual position",
},
{
object_id: "axis.fault",
label: "cia402.fault.detected",
index: @profile_cia402.cia402_fault_index,
subindex: b'\x00',
channel: "sdo-upload",
cob_id: canopen_sdo_upload_cob_id(node_id~),
direction: @trace.Rx,
behavior: "controller reads 0x603F:0 error code through SDO upload as a profile fault event",
},
]
}
///|
pub fn canopen_cia402_bindings(
node_id? : Int = 1,
) -> Array[@isocontract.ObjectBinding] {
[
canopen_binding(
object_id="axis.controlword",
binding_id="canopen.node" + node_id.to_string() + ".rpdo1.6040.write",
label="cia402.controlword.write",
direction=@trace.Tx,
kind=@isocontract.CanopenPdo,
index=@profile_cia402.cia402_controlword_index,
channel="rpdo1",
),
canopen_binding(
object_id="axis.statusword",
binding_id="canopen.node" + node_id.to_string() + ".tpdo1.6041.read",
label="cia402.statusword.read",
direction=@trace.Rx,
kind=@isocontract.CanopenPdo,
index=@profile_cia402.cia402_statusword_index,
channel="tpdo1",
),
canopen_binding(
object_id="axis.mode",
binding_id="canopen.node" + node_id.to_string() + ".sdo.6060.write",
label="cia402.mode.write",
direction=@trace.Tx,
kind=@isocontract.CanopenSdo,
index=@profile_cia402.cia402_mode_of_operation_index,
channel="sdo-download",
),
canopen_binding(
object_id="axis.target_position",
binding_id="canopen.node" + node_id.to_string() + ".rpdo1.607a.write",
label="cia402.target_position.write",
direction=@trace.Tx,
kind=@isocontract.CanopenPdo,
index=@profile_cia402.cia402_target_position_index,
channel="rpdo1",
),
canopen_binding(
object_id="axis.actual_position",
binding_id="canopen.node" + node_id.to_string() + ".tpdo1.6064.read",
label="cia402.actual_position.read",
direction=@trace.Rx,
kind=@isocontract.CanopenPdo,
index=@profile_cia402.cia402_actual_position_index,
channel="tpdo1",
),
canopen_binding(
object_id="axis.fault",
binding_id="canopen.node" + node_id.to_string() + ".sdo.603f.detected",
label="cia402.fault.detected",
direction=@trace.Rx,
kind=@isocontract.CanopenSdo,
index=@profile_cia402.cia402_fault_index,
channel="sdo-upload",
),
]
}
///|
pub fn canopen_cia402_contracts(
node_id? : Int = 1,
) -> @isocontract.ContractRegistry {
@profile_cia402.cia402_contracts_for_bindings(
canopen_cia402_bindings(node_id~),
)
}
///|
pub fn canopen_cia402_protocol() -> @protocol_ast.GlobalProtocol {
@profile_cia402.cia402_enable_fault_reset_protocol()
}
///|
pub fn canopen_cia402_observer_backend(
node_id? : Int = 1,
) -> @protocol_ast.ObserverBackend {
@protocol_ast.ObserverBackend::new(
registry=canopen_cia402_contracts(node_id~),
protocol=canopen_cia402_protocol(),
roles=[@protocol_ast.role(name="controller")],
)
}
///|
pub fn canopen_cia402_trace_fixture(node_id? : Int = 1) -> @trace.TraceLog {
let log = @trace.TraceLog::new()
log.append(
canopen_event(
1,
"canopen.nmt." + EnterOperational.label(),
@trace.Meta,
0L,
Some(EnterOperational.code()),
node_id~,
),
)
log.append(
canopen_event(
2,
"cia402.controlword.write",
@trace.Tx,
10L,
Some(0x0006),
node_id~,
),
)
log.append(
canopen_event(
3,
"cia402.statusword.read",
@trace.Rx,
20L,
Some(0x0021),
node_id~,
),
)
log.append(
canopen_event(4, "cia402.mode.write", @trace.Tx, 30L, Some(0x08), node_id~),
)
log.append(
canopen_event(
5,
"cia402.target_position.write",
@trace.Tx,
40L,
Some(607_000),
node_id~,
),
)
log.append(
canopen_event(
6,
"cia402.actual_position.read",
@trace.Rx,
50L,
Some(606_400),
node_id~,
),
)
log.append(
canopen_event(7, "cia402.fault.detected", @trace.Rx, 60L, Some(1), node_id~),
)
log.append(
canopen_event(
8,
"cia402.controlword.write",
@trace.Tx,
70L,
Some(0x0080),
node_id~,
),
)
log.append(
canopen_event(
9,
"cia402.statusword.read",
@trace.Rx,
80L,
Some(0x0040),
node_id~,
),
)
log.append(
canopen_event(
10,
"canopen.heartbeat." + Operational.label(),
@trace.Meta,
90L,
Some(Operational.code()),
node_id~,
),
)
log
}
///|
pub fn canopen_cia402_observer_report(
node_id? : Int = 1,
) -> @protocol_ast.ConformanceReport {
canopen_cia402_observer_backend(node_id~).check_trace(
canopen_cia402_trace_fixture(node_id~),
)
}
///|
fn canopen_binding(
object_id~ : String,
binding_id~ : String,
label~ : String,
direction~ : @trace.TraceDirection,
kind~ : @isocontract.BindingKind,
index~ : UInt,
channel~ : String,
) -> @isocontract.ObjectBinding {
@isocontract.ObjectBinding::make(
object_id~,
binding_id~,
medium_id=canopen_medium_id(),
label~,
direction~,
kind~,
index~,
subindex=b'\x00',
channel~,
authority=@isocontract.Role("controller"),
)
}
///|
fn canopen_event(
event_id : Int,
label : String,
direction : @trace.TraceDirection,
time_ns : Int64,
payload_digest : Int?,
node_id~ : Int,
) -> @trace.TraceEvent {
@trace.TraceEvent::make(
event_id~,
parent_id=None,
vtime=@core.VTime::from_ns(time_ns),
clock_domain="sim",
raw_ns=time_ns,
node_id="controller",
medium_id=canopen_medium_id(),
channel_id=None,
direction~,
payload_digest~,
rng_step=event_id,
seed=301 + node_id,
backend=@core.SimNative,
label~,
)
}