///|
/// Local zenoh object mirror skeleton for W4A. This package is offline-only:
/// it models keys, contracts, and trace monitor inputs without running zenohd.
///|
pub fn package_id() -> String {
"fieldbus_core/zenoh_mirror"
}
///|
pub fn zenoh_mirror_medium_id() -> String {
"zenoh_mirror"
}
///|
pub(all) struct ZenohMirrorCapabilities {
medium_id : String
object_mirror : Bool
protocol_session : Bool
partitionable : Bool
bounded_delay : Bool
total_order_delivery : Bool
hard_realtime_sink : Bool
validation_scope : String
} derive(Eq, Debug)
///|
pub fn ZenohMirrorCapabilities::local_non_live() -> ZenohMirrorCapabilities {
{
medium_id: zenoh_mirror_medium_id(),
object_mirror: true,
protocol_session: false,
partitionable: true,
bounded_delay: false,
total_order_delivery: false,
hard_realtime_sink: false,
validation_scope: "offline object mirror; no zenohd, no zenoh-pico, no live transport",
}
}
///|
pub fn ZenohMirrorCapabilities::is_hard_realtime_evidence(
self : ZenohMirrorCapabilities,
) -> Bool {
self.bounded_delay && self.hard_realtime_sink
}
///|
pub(all) enum ZenohMirrorKind {
SignalMirror
CommandBuffer
QueryableMirror
EventStreamMirror
LifecycleMirror
} derive(Eq, Debug)
///|
pub fn ZenohMirrorKind::label(self : ZenohMirrorKind) -> String {
match self {
SignalMirror => "signal_mirror"
CommandBuffer => "command_buffer"
QueryableMirror => "queryable_mirror"
EventStreamMirror => "event_stream_mirror"
LifecycleMirror => "lifecycle_mirror"
}
}
///|
pub(all) struct ZenohCia402Mirror {
object_id : String
label : String
key : String
mirror_kind : ZenohMirrorKind
direction : @trace.TraceDirection
source_medium_id : String
behavior : String
} derive(Eq, Debug)
///|
pub(all) struct ZenohMirrorReport {
capabilities : ZenohMirrorCapabilities
object_count : Int
binding_count : Int
trace_event_count : Int
checked_events : Int
matched_steps : Int
hard_realtime_evidence : Bool
conformance : @protocol_ast.ConformanceReport
} derive(Debug)
///|
pub fn ZenohMirrorReport::passes(self : ZenohMirrorReport) -> Bool {
self.conformance.passes() &&
self.object_count == 6 &&
self.binding_count == 6 &&
self.trace_event_count == 11 &&
self.checked_events == 8 &&
self.matched_steps == 8 &&
!self.hard_realtime_evidence
}
///|
pub fn zenoh_cia402_mirrors(axis_id? : Int = 1) -> Array[ZenohCia402Mirror] {
[
mirror_asset(
object_id="axis.controlword",
label="cia402.controlword.write",
key=axis_key(axis_id, "command/controlword"),
mirror_kind=CommandBuffer,
direction=@trace.Tx,
behavior="soft command buffer for mirrored 0x6040 controlword; not a hard realtime PDO path",
),
mirror_asset(
object_id="axis.statusword",
label="cia402.statusword.read",
key=axis_key(axis_id, "state/statusword"),
mirror_kind=SignalMirror,
direction=@trace.Rx,
behavior="drive statusword mirror for observation and diagnostics",
),
mirror_asset(
object_id="axis.mode",
label="cia402.mode.write",
key=axis_key(axis_id, "command/mode"),
mirror_kind=CommandBuffer,
direction=@trace.Tx,
behavior="soft command buffer for mirrored 0x6060 mode of operation",
),
mirror_asset(
object_id="axis.target_position",
label="cia402.target_position.write",
key=axis_key(axis_id, "command/target-position"),
mirror_kind=CommandBuffer,
direction=@trace.Tx,
behavior="soft setpoint mirror for supervision; bounded delay is intentionally false",
),
mirror_asset(
object_id="axis.actual_position",
label="cia402.actual_position.read",
key=axis_key(axis_id, "state/actual-position"),
mirror_kind=SignalMirror,
direction=@trace.Rx,
behavior="actual position state mirror for observer replay and dashboard use",
),
mirror_asset(
object_id="axis.fault",
label="cia402.fault.detected",
key=axis_key(axis_id, "event/fault"),
mirror_kind=EventStreamMirror,
direction=@trace.Rx,
behavior="diagnostic fault event mirror; delivery order is not treated as realtime proof",
),
]
}
///|
pub fn zenoh_cia402_bindings(
axis_id? : Int = 1,
) -> Array[@isocontract.ObjectBinding] {
let bindings : Array[@isocontract.ObjectBinding] = []
for mirror in zenoh_cia402_mirrors(axis_id~) {
bindings.push(zenoh_binding(mirror, axis_id))
}
bindings
}
///|
pub fn zenoh_cia402_contracts(
axis_id? : Int = 1,
) -> @isocontract.ContractRegistry {
@profile_cia402.cia402_contracts_for_bindings(zenoh_cia402_bindings(axis_id~))
}
///|
pub fn zenoh_cia402_protocol() -> @protocol_ast.GlobalProtocol {
@profile_cia402.cia402_enable_fault_reset_protocol()
}
///|
pub fn zenoh_cia402_observer_backend(
axis_id? : Int = 1,
) -> @protocol_ast.ObserverBackend {
@protocol_ast.ObserverBackend::new(
registry=zenoh_cia402_contracts(axis_id~),
protocol=zenoh_cia402_protocol(),
roles=[@protocol_ast.role(name="controller")],
)
}
///|
pub fn zenoh_cia402_trace_fixture(axis_id? : Int = 1) -> @trace.TraceLog {
let log = @trace.TraceLog::new()
log.append(
zenoh_event(1, "zenoh.mirror.session.open", @trace.Meta, 0L, None, axis_id~),
)
log.append(
zenoh_event(
2,
"cia402.controlword.write",
@trace.Tx,
10L,
Some(0x0006),
axis_id~,
),
)
log.append(
zenoh_event(
3,
"cia402.statusword.read",
@trace.Rx,
20L,
Some(0x0021),
axis_id~,
),
)
log.append(
zenoh_event(4, "cia402.mode.write", @trace.Tx, 30L, Some(0x08), axis_id~),
)
log.append(
zenoh_event(
5,
"cia402.target_position.write",
@trace.Tx,
40L,
Some(607_000),
axis_id~,
),
)
log.append(
zenoh_event(
6,
"cia402.actual_position.read",
@trace.Rx,
50L,
Some(606_400),
axis_id~,
),
)
log.append(
zenoh_event(
7,
"zenoh.mirror.diagnostic.partitionable",
@trace.Meta,
60L,
None,
axis_id~,
),
)
log.append(
zenoh_event(8, "cia402.fault.detected", @trace.Rx, 70L, Some(1), axis_id~),
)
log.append(
zenoh_event(
9,
"cia402.controlword.write",
@trace.Tx,
80L,
Some(0x0080),
axis_id~,
),
)
log.append(
zenoh_event(
10,
"cia402.statusword.read",
@trace.Rx,
90L,
Some(0x0040),
axis_id~,
),
)
log.append(
zenoh_event(
11,
"zenoh.mirror.session.close",
@trace.Meta,
100L,
None,
axis_id~,
),
)
log
}
///|
pub fn zenoh_cia402_observer_report(
axis_id? : Int = 1,
) -> @protocol_ast.ConformanceReport {
zenoh_cia402_observer_backend(axis_id~).check_trace(
zenoh_cia402_trace_fixture(axis_id~),
)
}
///|
pub fn zenoh_cia402_mirror_report(axis_id? : Int = 1) -> ZenohMirrorReport {
let capabilities = ZenohMirrorCapabilities::local_non_live()
let mirrors = zenoh_cia402_mirrors(axis_id~)
let bindings = zenoh_cia402_bindings(axis_id~)
let trace = zenoh_cia402_trace_fixture(axis_id~)
let conformance = zenoh_cia402_observer_backend(axis_id~).check_trace(trace)
{
capabilities,
object_count: mirrors.length(),
binding_count: bindings.length(),
trace_event_count: trace.len(),
checked_events: conformance.checked_events,
matched_steps: conformance.matched_steps,
hard_realtime_evidence: capabilities.is_hard_realtime_evidence(),
conformance,
}
}
///|
fn mirror_asset(
object_id~ : String,
label~ : String,
key~ : String,
mirror_kind~ : ZenohMirrorKind,
direction~ : @trace.TraceDirection,
behavior~ : String,
) -> ZenohCia402Mirror {
{
object_id,
label,
key,
mirror_kind,
direction,
source_medium_id: zenoh_mirror_medium_id(),
behavior,
}
}
///|
fn zenoh_binding(
mirror : ZenohCia402Mirror,
axis_id : Int,
) -> @isocontract.ObjectBinding {
@isocontract.ObjectBinding::make(
object_id=mirror.object_id,
binding_id="zenoh.axis" + axis_id.to_string() + "." + mirror.object_id,
medium_id=zenoh_mirror_medium_id(),
label=mirror.label,
direction=mirror.direction,
kind=@isocontract.ZenohKey,
key=mirror.key,
authority=@isocontract.Role("controller"),
)
}
///|
fn axis_key(axis_id : Int, suffix : String) -> String {
"cell/axis/" + axis_id.to_string() + "/" + suffix
}
///|
fn zenoh_event(
event_id : Int,
label : String,
direction : @trace.TraceDirection,
time_ns : Int64,
payload_digest : Int?,
axis_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=zenoh_mirror_medium_id(),
channel_id=None,
direction~,
payload_digest~,
rng_step=event_id,
seed=440 + axis_id,
backend=@core.SimNative,
label~,
)
}