///|
fn uuid_from_seed(seed : Int, tag : Int) -> @rpc.Uuid {
@rpc.Uuid::{
bytes: Bytes::from_array([
Int::to_byte(0xDE),
Int::to_byte(0xA0),
Int::to_byte(tag),
Int::to_byte((seed >> 24) & 0xFF),
Int::to_byte((seed >> 16) & 0xFF),
Int::to_byte((seed >> 8) & 0xFF),
Int::to_byte(seed & 0xFF),
Int::to_byte(0x11),
Int::to_byte(0x82),
Int::to_byte(0x71),
Int::to_byte(0x00),
Int::to_byte(0xA0),
Int::to_byte(0x24),
Int::to_byte(0x42),
Int::to_byte(0xDF),
Int::to_byte(0x7D),
]),
}
}
///|
fn parse_uuid_arg(text : String, label : String) -> @rpc.Uuid? {
try @rpc.Uuid::from_hex(text) catch {
err => {
println("Invalid \{label}: \{to_repr(err)}")
None
}
} noraise {
uuid => Some(uuid)
}
}
///|
fn read_u16_be_local(data : Bytes, offset : Int) -> Int {
(data[offset].to_int() << 8) + data[offset + 1].to_int()
}
///|
fn push_u16_be_local(output : Array[Byte], value : Int) -> Unit {
output.push(((value >> 8) & 0xFF).to_byte())
output.push((value & 0xFF).to_byte())
}
///|
fn append_bytes_local(output : Array[Byte], data : Bytes) -> Unit {
for byte in data {
output.push(byte)
}
}
///|
pub fn encode_rt_ethernet_frame(
destination : @frame.MacAddress,
source : @frame.MacAddress,
cyclic : @io.IoCyclicFrame,
vlan_tci? : Int = rt_output_vlan_tci_default(),
) -> Bytes {
let output : Array[Byte] = []
append_bytes_local(output, destination.octets())
append_bytes_local(output, source.octets())
push_u16_be_local(output, 0x8100)
push_u16_be_local(output, vlan_tci)
push_u16_be_local(output, 0x8892)
append_bytes_local(output, @io.encode_cyclic_frame(cyclic))
Bytes::from_array(output)
}
///|
fn ca400_output_payload() -> Bytes {
ca400_output_payload_from_words([])
}
///|
fn ca400_output_payload_from_words(words : Array[Int]) -> Bytes {
let payload : Array[Byte] = []
for i = 0; i < 5; i = i + 1 {
let word = match words.get(i) {
Some(value) => value & 0xFFFF
None => 0
}
payload.push(Int::to_byte((word >> 8) & 0xFF))
payload.push(Int::to_byte(word & 0xFF))
}
payload.push(b'\x80')
payload.push(b'\x80')
for _ in 12..<40 {
payload.push(b'\x00')
}
Bytes::from_array(payload)
}
///|
fn payload_u16_words(payload : Bytes, count : Int) -> Array[Int] {
let words : Array[Int] = []
let available = payload.length() / 2
let limit = if count < available { count } else { available }
for i = 0; i < limit; i = i + 1 {
words.push(read_u16_be_local(payload, i * 2))
}
words
}
///|
fn format_u16_words(words : Array[Int]) -> String {
let parts : Array[String] = []
for word in words {
parts.push("0x" + @frame.uint16_to_hex(word & 0xFFFF))
}
parts.join(",")
}
///|
fn payload_u16_words_text(payload : Bytes, count : Int) -> String {
format_u16_words(payload_u16_words(payload, count))
}
///|
fn rt_deadline_resync_lag_periods() -> Int64 {
4
}
///|
pub fn rt_resync_deadline(
next_deadline_us : Int64,
now_us : Int64,
interval_us : Int,
) -> (Int64, Bool) {
if now_us - next_deadline_us >
interval_us.to_int64() * rt_deadline_resync_lag_periods() {
(now_us, true)
} else {
(next_deadline_us, false)
}
}
///|
fn rt_update_interval_us(send_clock_factor : Int, reduction_ratio : Int) -> Int {
send_clock_factor * reduction_ratio * 125 / 4
}
///|
pub fn rt_cycle_counter_step(
send_clock_factor : Int,
reduction_ratio : Int,
) -> Int {
send_clock_factor * reduction_ratio
}
///|
fn ca400_rt_send_clock_factor() -> Int {
32
}
///|
fn ca400_rt_reduction_ratio() -> Int {
2
}
///|
fn ca400_rt_update_interval_us() -> Int {
rt_update_interval_us(
ca400_rt_send_clock_factor(),
ca400_rt_reduction_ratio(),
)
}
///|
struct RtClockPlan {
mode : String
period_us : Int
}
///|
struct IocrPeriodPlan {
enabled : Bool
send_clock_factor : Int
reduction_ratio : Int
period_us : Int
}
///|
struct RtDataHoldPlan {
mode : String
factor : Int
time_us : Int
}
///|
struct RtOutputVlanPlan {
mode : String
tci : Int
}
///|
struct WindowsRealtimePlan {
enabled : Bool
process_mask : Int
thread_mask : Int
thread_priority : Int
}
///|
struct RtClockCliArgs {
args : Array[String]
clock : RtClockPlan
clock_explicit : Bool
iocr_period : IocrPeriodPlan
data_hold : RtDataHoldPlan
output_vlan : RtOutputVlanPlan
windows_realtime : WindowsRealtimePlan
}
///|
fn iocr_period_default() -> IocrPeriodPlan {
{
enabled: false,
send_clock_factor: ca400_rt_send_clock_factor(),
reduction_ratio: ca400_rt_reduction_ratio(),
period_us: ca400_rt_update_interval_us(),
}
}
///|
fn rt_data_hold_auto() -> RtDataHoldPlan {
{ mode: "auto-mandatory", factor: 0, time_us: 0 }
}
///|
fn rt_data_hold_explicit_factor(factor : Int) -> RtDataHoldPlan {
{ mode: "explicit-factor", factor, time_us: 0 }
}
///|
fn rt_data_hold_explicit_time_us(time_us : Int) -> RtDataHoldPlan {
{ mode: "explicit-ms", factor: 0, time_us }
}
///|
fn rt_output_vlan_tci_default() -> Int {
0xC000
}
///|
fn rt_output_vlan_default() -> RtOutputVlanPlan {
{ mode: "default-pcp6", tci: rt_output_vlan_tci_default() }
}
///|
fn rt_output_vlan_explicit_pcp(pcp : Int) -> RtOutputVlanPlan {
{ mode: "explicit-pcp", tci: (pcp & 0x07) << 13 }
}
///|
fn rt_output_vlan_explicit_tci(tci : Int) -> RtOutputVlanPlan {
{ mode: "explicit-tci", tci: tci & 0xFFFF }
}
///|
fn iocr_period_from_us(period_us : Int) -> IocrPeriodPlan? {
let scaled = period_us * 4
if scaled % 125 != 0 {
println(
"Invalid --period-us. Expected a period exactly representable by IOCR 31.25us units.",
)
None
} else {
let (send_clock_factor, reduction_ratio) = if period_us %
ca400_rt_update_interval_us() ==
0 {
(
ca400_rt_send_clock_factor(),
period_us / rt_update_interval_us(ca400_rt_send_clock_factor(), 1),
)
} else if period_us % rt_update_interval_us(ca400_rt_send_clock_factor(), 1) ==
0 {
(
ca400_rt_send_clock_factor(),
period_us / rt_update_interval_us(ca400_rt_send_clock_factor(), 1),
)
} else {
(scaled / 125, 1)
}
if send_clock_factor <= 0 ||
send_clock_factor > 0xFFFF ||
reduction_ratio <= 0 ||
reduction_ratio > 0xFFFF {
println(
"Invalid --period-us. Expected a period representable by IOCR send_clock_factor/reduction_ratio.",
)
None
} else {
Some({ enabled: true, send_clock_factor, reduction_ratio, period_us })
}
}
}
///|
fn iocr_period_for_clock(
_clock : RtClockPlan,
iocr_period : IocrPeriodPlan,
) -> IocrPeriodPlan {
if iocr_period.enabled {
iocr_period
} else {
iocr_period_default()
}
}
///|
fn rt_iocr_target_data_hold_us() -> Int {
12_000
}
///|
fn data_rtc_data_hold_factor_min() -> Int {
3
}
///|
fn data_rtc_data_hold_factor_mandatory_max() -> Int {
0x00FF
}
///|
fn data_rtc_data_hold_factor_max() -> Int {
0x1E00
}
///|
fn data_rtc_data_hold_time_max_us() -> Int {
1_920_000
}
///|
fn iocr_data_hold_factor(
iocr_period : IocrPeriodPlan,
data_hold? : RtDataHoldPlan = rt_data_hold_auto(),
) -> Int {
let period_us = iocr_period.period_us
if period_us <= 0 {
data_rtc_data_hold_factor_min()
} else if data_hold.mode == "explicit-factor" {
data_hold.factor
} else {
let target_us = if data_hold.mode == "explicit-ms" {
data_hold.time_us
} else {
rt_iocr_target_data_hold_us()
}
let max_factor = if data_hold.mode == "explicit-ms" {
data_rtc_data_hold_factor_max()
} else {
data_rtc_data_hold_factor_mandatory_max()
}
let factor = (target_us + period_us - 1) / period_us
if factor < data_rtc_data_hold_factor_min() {
data_rtc_data_hold_factor_min()
} else if factor > max_factor {
max_factor
} else {
factor
}
}
}
///|
fn rt_iocr_data_hold_time_us(
iocr_period : IocrPeriodPlan,
data_hold? : RtDataHoldPlan = rt_data_hold_auto(),
) -> Int {
iocr_data_hold_factor(iocr_period, data_hold~) * iocr_period.period_us
}
///|
fn rt_data_hold_requested_factor(
iocr_period : IocrPeriodPlan,
data_hold : RtDataHoldPlan,
) -> Int {
let period_us = iocr_period.period_us
if period_us <= 0 {
data_rtc_data_hold_factor_min()
} else if data_hold.mode == "explicit-factor" {
data_hold.factor
} else {
let target_us = if data_hold.mode == "explicit-ms" {
data_hold.time_us
} else {
rt_iocr_target_data_hold_us()
}
(target_us + period_us - 1) / period_us
}
}
///|
fn rt_data_hold_validate_for_period(
iocr_period : IocrPeriodPlan,
data_hold : RtDataHoldPlan,
) -> Bool {
let requested_factor = rt_data_hold_requested_factor(iocr_period, data_hold)
if data_hold.mode == "explicit-ms" &&
requested_factor < data_rtc_data_hold_factor_min() {
println(
"Invalid --data-hold-ms. Requested time is below the minimum Data-RTC factor for the selected period.",
)
false
} else if requested_factor > data_rtc_data_hold_factor_max() {
println(
"Invalid --data-hold-ms. Requested time exceeds the maximum Data-RTC factor for the selected period.",
)
false
} else if data_hold.mode == "explicit-factor" &&
data_hold.factor * iocr_period.period_us > data_rtc_data_hold_time_max_us() {
println(
"Invalid --data-hold-factor. Effective DataHoldTime exceeds 1920 ms.",
)
false
} else {
true
}
}
///|
pub fn rt_local_dht_timeout_us(
iocr_period : IocrPeriodPlan,
data_hold? : RtDataHoldPlan = rt_data_hold_auto(),
) -> Int64 {
rt_iocr_data_hold_time_us(iocr_period, data_hold~).to_int64()
}
///|
pub fn rt_local_dht_expired(
now_us : Int64,
last_input_us : Int64,
timeout_us : Int64,
seen_input : Bool,
) -> Bool {
seen_input && now_us - last_input_us >= timeout_us
}
///|
fn rt_clock_receive_paced(
period_us? : Int = ca400_rt_update_interval_us(),
) -> RtClockPlan {
{ mode: "receive-paced", period_us }
}
///|
fn rt_clock_monotonic_ms(
period_us? : Int = ca400_rt_update_interval_us(),
) -> RtClockPlan {
{ mode: "monotonic-ms", period_us }
}
///|
fn rt_clock_highres(
period_us? : Int = ca400_rt_update_interval_us(),
) -> RtClockPlan {
{ mode: "highres", period_us }
}
///|
pub fn rt_clock_timing_resolution(clock : RtClockPlan) -> String {
if clock.mode == "highres" {
"highres_us"
} else {
"monotonic_ms"
}
}
///|
fn rt_clock_counter_bits(clock : RtClockPlan) -> Int {
if clock.mode == "highres" {
64
} else {
32
}
}
///|
fn rt_clock_wrap_window_ms(clock : RtClockPlan) -> String {
if clock.mode == "highres" {
"int64"
} else {
"2147483647"
}
}
///|
fn rt_clock_wrap_window_days(clock : RtClockPlan) -> String {
if clock.mode == "highres" {
"int64"
} else {
"24.8"
}
}
///|
fn rt_clock_wrap_risk(clock : RtClockPlan) -> String {
if clock.mode == "highres" {
"none-practical-int64"
} else {
"32-bit-ms-wrap-around-24.8d"
}
}
///|
fn rt_clock_long_run_role(clock : RtClockPlan) -> String {
if clock.mode == "highres" {
"recommended"
} else if clock.mode == "monotonic-ms" {
"comparison-only"
} else {
"deprecated-comparison-only"
}
}
///|
fn rt_clock_long_run_recommendation(clock : RtClockPlan) -> String {
if clock.mode == "highres" {
"highres"
} else {
"use-highres"
}
}
///|
pub fn append_rt_clock_long_run_summary(
prefix : String,
clock : RtClockPlan,
lines : Array[String],
) -> Unit {
lines.push(
prefix + "_counter_bits=" + rt_clock_counter_bits(clock).to_string(),
)
lines.push(prefix + "_wrap_window_ms=" + rt_clock_wrap_window_ms(clock))
lines.push(prefix + "_wrap_window_days=" + rt_clock_wrap_window_days(clock))
lines.push(prefix + "_wrap_risk=" + rt_clock_wrap_risk(clock))
lines.push(prefix + "_long_run_role=" + rt_clock_long_run_role(clock))
lines.push(
prefix +
"_long_run_recommendation=" +
rt_clock_long_run_recommendation(clock),
)
}
///|
fn windows_realtime_disabled() -> WindowsRealtimePlan {
{ enabled: false, process_mask: 0, thread_mask: 0, thread_priority: 31 }
}
///|
fn parse_windows_realtime_mask_arg(text : String, label : String) -> Int? {
match parse_number_arg(text, label) {
Some(value) =>
if value <= 0 {
println("Invalid \{label}. Expected a positive CPU affinity mask.")
None
} else {
Some(value)
}
None => None
}
}
///|
fn parse_windows_realtime_priority_arg(text : String) -> Int? {
match parse_number_arg(text, "win-rt-thread-priority") {
Some(value) =>
if value < 16 || value > 31 {
println("Invalid win-rt-thread-priority. Expected a value in 16..31.")
None
} else {
Some(value)
}
None => None
}
}
///|
fn windows_realtime_mask_text(mask : Int) -> String {
"0x" + @frame.uint32_to_hex(mask)
}
///|
fn apply_windows_realtime_plan(plan : WindowsRealtimePlan) -> Array[String] {
let lines : Array[String] = []
if plan.enabled {
lines.push("win_rt_enabled=true")
lines.push("win_rt_scope=provider_harness")
lines.push(
"win_rt_process_affinity_mask=" +
windows_realtime_mask_text(plan.process_mask),
)
lines.push(
"win_rt_thread_affinity_mask=" +
windows_realtime_mask_text(plan.thread_mask),
)
lines.push(
"win_rt_thread_base_priority=" + plan.thread_priority.to_string(),
)
lines.push("win_rt_live_apply_attempted=false")
}
lines
}
///|
fn emit_windows_realtime_plan(plan : WindowsRealtimePlan) -> Unit {
for line in apply_windows_realtime_plan(plan) {
println(line)
}
}
///|
fn parse_u16_words_arg(text : String, label : String) -> Array[Int]? {
let words : Array[Int] = []
for part in text.split(",") {
let token = part.trim().to_owned()
if token == "" {
println("Invalid \{label}. Empty word in comma-separated list.")
return None
}
match parse_number_arg(token, label) {
Some(value) =>
if value < 0 || value > 0xFFFF {
println("Invalid \{label}. Each word must be in 0..0xFFFF.")
return None
} else {
words.push(value)
}
None => return None
}
}
if words.length() == 0 || words.length() > 5 {
println("Invalid \{label}. Expected 1 to 5 comma-separated U16 words.")
None
} else {
Some(words)
}
}
///|
fn rt_data_hold_is_auto(plan : RtDataHoldPlan) -> Bool {
plan.mode == "auto-mandatory"
}
///|
fn parse_data_hold_factor_arg(text : String) -> RtDataHoldPlan? {
match parse_number_arg(text, "data-hold-factor") {
Some(value) =>
if value < data_rtc_data_hold_factor_min() ||
value > data_rtc_data_hold_factor_max() {
println(
"Invalid --data-hold-factor. Expected a Data-RTC factor in 0x0003..0x1E00; 0x0000 cannot disable the watchdog.",
)
None
} else {
Some(rt_data_hold_explicit_factor(value))
}
None => None
}
}
///|
fn parse_data_hold_ms_arg(text : String) -> RtDataHoldPlan? {
match parse_number_arg(text, "data-hold-ms") {
Some(value) =>
if value <= 0 || value > data_rtc_data_hold_time_max_us() / 1000 {
println(
"Invalid --data-hold-ms. Expected a positive value up to 1920 ms for Data-RTC.",
)
None
} else {
Some(rt_data_hold_explicit_time_us(value * 1000))
}
None => None
}
}
///|
fn rt_output_vlan_is_default(plan : RtOutputVlanPlan) -> Bool {
plan.mode == "default-pcp6"
}
///|
fn parse_rt_output_vlan_pcp_arg(text : String) -> RtOutputVlanPlan? {
match parse_number_arg(text, "rt-output-vlan-pcp") {
Some(value) =>
if value < 0 || value > 7 {
println("Invalid --rt-output-vlan-pcp. Expected a value in 0..7.")
None
} else {
Some(rt_output_vlan_explicit_pcp(value))
}
None => None
}
}
///|
fn parse_rt_output_vlan_tci_arg(text : String) -> RtOutputVlanPlan? {
match parse_number_arg(text, "rt-output-vlan-tci") {
Some(value) =>
if value < 0 || value > 0xFFFF {
println(
"Invalid --rt-output-vlan-tci. Expected a value in 0x0000..0xFFFF.",
)
None
} else {
Some(rt_output_vlan_explicit_tci(value))
}
None => None
}
}
///|
fn parse_rt_clock_cli_args(args : Array[String]) -> RtClockCliArgs? {
let filtered : Array[String] = []
let mut mode = "highres"
let mut clock_explicit = false
let mut period_us = ca400_rt_update_interval_us()
let mut iocr_period = iocr_period_default()
let mut data_hold = rt_data_hold_auto()
let mut output_vlan = rt_output_vlan_default()
let mut windows_realtime = windows_realtime_disabled()
let mut index = 0
while index < args.length() {
if args[index] == "--clock" {
if index + 1 >= args.length() {
println(
"Invalid --clock. Expected highres, monotonic-ms, or receive-paced (deprecated).",
)
return None
}
let value = args[index + 1]
clock_explicit = true
if value == "receive-paced" ||
value == "monotonic-ms" ||
value == "highres" {
mode = value
if value == "receive-paced" {
println(
"rt_clock_mode_deprecated=receive-paced hint=use --clock highres",
)
}
} else {
println(
"Invalid --clock. Expected highres, monotonic-ms, or receive-paced (deprecated).",
)
return None
}
index = index + 2
} else if args[index] == "--period-us" {
if index + 1 >= args.length() {
println("Invalid --period-us. Expected a positive decimal integer.")
return None
}
match parse_number_arg(args[index + 1], "period-us") {
Some(value) =>
if value <= 0 {
println("Invalid --period-us. Expected a positive decimal integer.")
return None
} else {
match iocr_period_from_us(value) {
Some(plan) => iocr_period = plan
None => return None
}
period_us = value
}
None => return None
}
index = index + 2
} else if args[index] == "--rt-period-us" {
if index + 1 >= args.length() {
println("Invalid --rt-period-us. Expected a positive decimal integer.")
return None
}
match parse_number_arg(args[index + 1], "rt-period-us") {
Some(value) =>
if value <= 0 {
println(
"Invalid --rt-period-us. Expected a positive decimal integer.",
)
return None
} else {
period_us = value
}
None => return None
}
index = index + 2
} else if args[index] == "--data-hold-factor" {
if index + 1 >= args.length() {
println("Invalid --data-hold-factor. Expected a numeric factor.")
return None
}
if rt_data_hold_is_auto(data_hold) == false {
println(
"Invalid RT data hold options. Use only one of --data-hold-factor or --data-hold-ms.",
)
return None
}
match parse_data_hold_factor_arg(args[index + 1]) {
Some(plan) => data_hold = plan
None => return None
}
index = index + 2
} else if args[index] == "--data-hold-ms" {
if index + 1 >= args.length() {
println(
"Invalid --data-hold-ms. Expected a positive millisecond value.",
)
return None
}
if rt_data_hold_is_auto(data_hold) == false {
println(
"Invalid RT data hold options. Use only one of --data-hold-factor or --data-hold-ms.",
)
return None
}
match parse_data_hold_ms_arg(args[index + 1]) {
Some(plan) => data_hold = plan
None => return None
}
index = index + 2
} else if args[index] == "--rt-output-vlan-pcp" {
if index + 1 >= args.length() {
println("Invalid --rt-output-vlan-pcp. Expected a value in 0..7.")
return None
}
if rt_output_vlan_is_default(output_vlan) == false {
println(
"Invalid RT output VLAN options. Use only one of --rt-output-vlan-pcp or --rt-output-vlan-tci.",
)
return None
}
match parse_rt_output_vlan_pcp_arg(args[index + 1]) {
Some(plan) => output_vlan = plan
None => return None
}
index = index + 2
} else if args[index] == "--rt-output-vlan-tci" {
if index + 1 >= args.length() {
println(
"Invalid --rt-output-vlan-tci. Expected a value in 0x0000..0xFFFF.",
)
return None
}
if rt_output_vlan_is_default(output_vlan) == false {
println(
"Invalid RT output VLAN options. Use only one of --rt-output-vlan-pcp or --rt-output-vlan-tci.",
)
return None
}
match parse_rt_output_vlan_tci_arg(args[index + 1]) {
Some(plan) => output_vlan = plan
None => return None
}
index = index + 2
} else if args[index] == "--win-rt" || args[index] == "--windows-iot-rt" {
windows_realtime = {
enabled: true,
process_mask: windows_realtime.process_mask,
thread_mask: windows_realtime.thread_mask,
thread_priority: windows_realtime.thread_priority,
}
index = index + 1
} else if args[index] == "--win-rt-mask" {
if index + 1 >= args.length() {
println("Invalid --win-rt-mask. Expected a CPU affinity mask.")
return None
}
match parse_windows_realtime_mask_arg(args[index + 1], "win-rt-mask") {
Some(mask) =>
windows_realtime = {
enabled: true,
process_mask: mask,
thread_mask: mask,
thread_priority: windows_realtime.thread_priority,
}
None => return None
}
index = index + 2
} else if args[index] == "--win-rt-process-mask" {
if index + 1 >= args.length() {
println("Invalid --win-rt-process-mask. Expected a CPU affinity mask.")
return None
}
match
parse_windows_realtime_mask_arg(args[index + 1], "win-rt-process-mask") {
Some(mask) =>
windows_realtime = {
enabled: true,
process_mask: mask,
thread_mask: windows_realtime.thread_mask,
thread_priority: windows_realtime.thread_priority,
}
None => return None
}
index = index + 2
} else if args[index] == "--win-rt-thread-mask" {
if index + 1 >= args.length() {
println("Invalid --win-rt-thread-mask. Expected a CPU affinity mask.")
return None
}
match
parse_windows_realtime_mask_arg(args[index + 1], "win-rt-thread-mask") {
Some(mask) =>
windows_realtime = {
enabled: true,
process_mask: windows_realtime.process_mask,
thread_mask: mask,
thread_priority: windows_realtime.thread_priority,
}
None => return None
}
index = index + 2
} else if args[index] == "--win-rt-thread-priority" {
if index + 1 >= args.length() {
println("Invalid --win-rt-thread-priority. Expected a value in 16..31.")
return None
}
match parse_windows_realtime_priority_arg(args[index + 1]) {
Some(priority) =>
windows_realtime = {
enabled: true,
process_mask: windows_realtime.process_mask,
thread_mask: windows_realtime.thread_mask,
thread_priority: priority,
}
None => return None
}
index = index + 2
} else {
filtered.push(args[index])
index = index + 1
}
}
let clock = if mode == "highres" {
rt_clock_highres(period_us~)
} else if mode == "monotonic-ms" {
rt_clock_monotonic_ms(period_us~)
} else {
rt_clock_receive_paced(period_us~)
}
let effective_iocr_period = iocr_period_for_clock(clock, iocr_period)
if rt_data_hold_validate_for_period(effective_iocr_period, data_hold) == false {
return None
}
Some({
args: filtered,
clock,
clock_explicit,
iocr_period,
data_hold,
output_vlan,
windows_realtime,
})
}
///|
fn parse_rt_cycle_count_or_unbounded(args : Array[String], index : Int) -> Int? {
if args.length() <= index {
Some(rt_cycle_count_unbounded())
} else {
match parse_number_arg(args[index], "cycle-count") {
Some(value) =>
if value <= 0 {
println(
"Invalid cycle-count. Expected a positive integer, or omit it for continuous mode.",
)
None
} else {
Some(value)
}
None => None
}
}
}
///|
struct RtOutputUpdatePlan {
enabled : Bool
mode : String
change_cycle : Int
before_payload : Bytes
after_payload : Bytes
}
///|
struct RtInputReadPlan {
enabled : Bool
sample_every : Int
}
///|
fn rt_cycle_count_unbounded() -> Int {
-1
}
///|
fn rt_cycle_count_is_unbounded(cycle_count : Int) -> Bool {
cycle_count < 0
}
///|
fn rt_cycle_count_text(cycle_count : Int) -> String {
if rt_cycle_count_is_unbounded(cycle_count) {
"unbounded"
} else {
cycle_count.to_string()
}
}
///|
fn rt_output_update_disabled() -> RtOutputUpdatePlan {
{
enabled: false,
mode: "disabled",
change_cycle: 0,
before_payload: ca400_output_payload(),
after_payload: ca400_output_payload(),
}
}
///|
fn rt_output_update_from_words(
before_words : Array[Int],
after_words : Array[Int],
change_cycle : Int,
) -> RtOutputUpdatePlan {
{
enabled: true,
mode: "update",
change_cycle,
before_payload: ca400_output_payload_from_words(before_words),
after_payload: ca400_output_payload_from_words(after_words),
}
}
///|
fn rt_output_write_from_words(words : Array[Int]) -> RtOutputUpdatePlan {
{
enabled: true,
mode: "write",
change_cycle: 0,
before_payload: ca400_output_payload_from_words(words),
after_payload: ca400_output_payload_from_words(words),
}
}
///|
fn rt_input_read_disabled() -> RtInputReadPlan {
{ enabled: false, sample_every: 0 }
}
///|
fn rt_input_read_every(sample_every : Int) -> RtInputReadPlan {
{
enabled: true,
sample_every: if sample_every <= 0 {
1
} else {
sample_every
},
}
}
///|
fn data_status_inline_summary(status : Byte) -> String {
let fields = @io.decode_data_status(status)
",data_status_state:" +
fields.state +
",data_status_redundancy:" +
fields.redundancy +
",data_status_data_valid:" +
fields.data_valid +
",data_status_provider_state:" +
fields.provider_state +
",data_status_station_problem:" +
fields.station_problem +
",data_status_ignore:" +
fields.ignore
}
///|
pub fn rt_input_sample_line(index : Int, frame : @io.IoCyclicFrame) -> String {
"rt_input_sample_" +
index.to_string() +
"=frame_id:0x" +
@frame.uint16_to_hex(frame.frame_id) +
",cycle:" +
frame.cycle_counter.to_string() +
",data_status:0x" +
@frame.uint16_to_hex(frame.data_status.to_int()) +
data_status_inline_summary(frame.data_status) +
",transfer_status:0x" +
@frame.uint16_to_hex(frame.transfer_status.to_int()) +
",words:" +
payload_u16_words_text(frame.payload, 5)
}
///|
fn profinet_frame_offset(frame : Bytes) -> Int? {
if frame.length() >= 16 && read_frame_u16_be(frame, 12) == 0x8892 {
Some(14)
} else if frame.length() >= 20 &&
read_frame_u16_be(frame, 12) == 0x8100 &&
read_frame_u16_be(frame, 16) == 0x8892 {
Some(18)
} else {
None
}
}
///|
pub fn profinet_frame_id(frame : Bytes) -> Int? {
match profinet_frame_offset(frame) {
Some(offset) => Some(read_frame_u16_be(frame, offset))
None => None
}
}
///|
pub fn profinet_frame_is_vlan(frame : Bytes) -> Bool {
frame.length() >= 20 &&
read_frame_u16_be(frame, 12) == 0x8100 &&
read_frame_u16_be(frame, 16) == 0x8892
}
///|
fn vlan_tci_pcp(tci : Int) -> Int {
(tci >> 13) & 0x07
}
///|
fn vlan_tci_dei(tci : Int) -> Bool {
((tci >> 12) & 0x01) == 1
}
///|
fn vlan_tci_vid(tci : Int) -> Int {
tci & 0x0FFF
}
///|
fn append_rt_output_vlan_summary(
prefix : String,
plan : RtOutputVlanPlan,
lines : Array[String],
) -> Unit {
lines.push(prefix + "_source=" + plan.mode)
lines.push(prefix + "_tci=0x" + @frame.uint16_to_hex(plan.tci))
lines.push(prefix + "_pcp=" + vlan_tci_pcp(plan.tci).to_string())
lines.push(prefix + "_dei=" + vlan_tci_dei(plan.tci).to_string())
lines.push(prefix + "_vid=" + vlan_tci_vid(plan.tci).to_string())
}
///|
pub fn is_rpc_control_opnum(opnum : Int) -> Bool {
opnum == 4 || opnum == 0x0400
}
///|
fn is_rt_cyclic_frame_id(frame_id : Int) -> Bool {
frame_id >= 0x8000 && frame_id < 0xC000
}
///|
pub fn should_accept_rt_input_frame(
frame_id : Int,
expected_input_frame_id : Int,
) -> Bool {
is_rt_cyclic_frame_id(frame_id) &&
(expected_input_frame_id < 0 || frame_id == expected_input_frame_id)
}
///|
pub fn append_data_status_summary(
prefix : String,
status : Byte,
lines : Array[String],
) -> Unit {
let fields = @io.decode_data_status(status)
lines.push(prefix + "_state=" + fields.state)
lines.push(prefix + "_redundancy=" + fields.redundancy)
lines.push(prefix + "_data_valid=" + fields.data_valid)
lines.push(prefix + "_provider_state=" + fields.provider_state)
lines.push(prefix + "_station_problem=" + fields.station_problem)
lines.push(prefix + "_ignore=" + fields.ignore)
}
///|
fn provider_harness_rt_pcap_handoff_text(
command : String,
protocol_stage : String,
legacy_surface : String,
fields : Array[String],
) -> String {
let lines : Array[String] = [
"status=moved_to_provider_harness",
"command=" + command,
"provider_harness=fieldbus_core/profinet_master_harness",
"provider_stack=profinet_master",
"provider_harness_entry=profinet_rt_pcap_live_command_handoffs",
"protocol_stage=" + protocol_stage,
"legacy_surface=" + legacy_surface,
"lockwire_dependency_in_profinet=false",
"live_open_attempted=false",
"next_task=D63D-PROFINET-FFI-PACKAGE-REMOVAL",
]
for field in fields {
lines.push(field)
}
lines.join("\n")
}
///|
fn append_uuid_fields(
fields : Array[String],
ar_uuid : @rpc.Uuid,
activity_uuid : @rpc.Uuid,
object_uuid : @rpc.Uuid,
) -> Unit {
fields.push("ar_uuid=" + ar_uuid.to_hex())
fields.push("activity_uuid=" + activity_uuid.to_hex())
fields.push("object_uuid=" + object_uuid.to_hex())
}
///|
fn append_rt_plan_fields(
fields : Array[String],
clock : RtClockPlan,
iocr_period : IocrPeriodPlan,
data_hold : RtDataHoldPlan,
output_vlan : RtOutputVlanPlan,
windows_realtime : WindowsRealtimePlan,
) -> Unit {
fields.push("rt_cli_clock_mode=" + clock.mode)
fields.push("rt_cli_period_us=" + clock.period_us.to_string())
fields.push("rt_cli_iocr_period_sync=" + iocr_period.enabled.to_string())
fields.push(
"rt_cli_iocr_send_clock_factor=" + iocr_period.send_clock_factor.to_string(),
)
fields.push(
"rt_cli_iocr_reduction_ratio=" + iocr_period.reduction_ratio.to_string(),
)
fields.push("rt_cli_data_hold_source=" + data_hold.mode)
fields.push(
"rt_cli_data_hold_factor=" +
iocr_data_hold_factor(iocr_period_for_clock(clock, iocr_period), data_hold~).to_string(),
)
append_rt_output_vlan_summary("rt_cli_output_vlan", output_vlan, fields)
fields.push("win_rt_requested=" + windows_realtime.enabled.to_string())
fields.push("win_rt_live_apply_attempted=false")
}
///|
fn default_handoff_uuid(tag : Int) -> @rpc.Uuid {
uuid_from_seed(0, tag)
}
///|
fn run_rpc_connect(args : Array[String]) -> Unit {
guard args.length() >= 3 && args[0] == "--iface" else {
println(
"rpc-connect --iface [station-name] [timeout-ms] [ar-uuid] [activity-uuid] [object-uuid]",
)
return
}
let device_ip = @frame.IPv4Address::from_text(args[2][:]) catch {
err => {
println("Invalid device IP: \{to_repr(err)}")
return
}
}
let station_name = if args.length() >= 4 { args[3] } else { "controller" }
let timeout_ms = match parse_timeout_arg(args, 4, 5000) {
Some(value) => value
None => return
}
let ar_uuid = if args.length() >= 6 {
match parse_uuid_arg(args[5], "ar-uuid") {
Some(uuid) => uuid
None => return
}
} else {
default_handoff_uuid(1)
}
let activity_uuid = if args.length() >= 7 {
match parse_uuid_arg(args[6], "activity-uuid") {
Some(uuid) => uuid
None => return
}
} else {
default_handoff_uuid(2)
}
let object_uuid = if args.length() >= 8 {
match parse_uuid_arg(args[7], "object-uuid") {
Some(uuid) => uuid
None => return
}
} else {
@rpc.Uuid::empty()
}
let fields : Array[String] = [
"interface=" + args[1],
"device_ip=" + device_ip.to_text(),
"station_name=" + station_name,
"timeout_ms=" + timeout_ms.to_string(),
]
append_uuid_fields(fields, ar_uuid, activity_uuid, object_uuid)
println(
provider_harness_rt_pcap_handoff_text(
"rpc-connect", "Connect", "@rawnet.RawSocket::open + @udpnet.UdpSocket::open",
fields,
),
)
}
///|
fn run_rpc_release(args : Array[String]) -> Unit {
run_rpc_record_handoff(args, "rpc-release", "release")
}
///|
fn run_rpc_read_iam0(args : Array[String]) -> Unit {
run_rpc_record_handoff(args, "rpc-read-iam0", "record read I&M0")
}
///|
fn run_rpc_read_record(args : Array[String]) -> Unit {
run_rpc_record_handoff(args, "rpc-read-record", "record read")
}
///|
fn run_rpc_write_iam1(args : Array[String]) -> Unit {
run_rpc_record_handoff(args, "rpc-write-iam1", "record write I&M1")
}
///|
fn run_modify_iam1(args : Array[String]) -> Unit {
run_rpc_record_handoff(args, "modify-iam1", "record write I&M1 convenience")
}
///|
fn run_rpc_record_handoff(
args : Array[String],
command : String,
protocol_stage : String,
) -> Unit {
guard args.length() >= 3 && args[0] == "--iface" else {
println(command + " --iface ...")
return
}
let device_ip = @frame.IPv4Address::from_text(args[2][:]) catch {
err => {
println("Invalid device IP: \{to_repr(err)}")
return
}
}
let fields : Array[String] = [
"interface=" + args[1],
"device_ip=" + device_ip.to_text(),
"legacy_arg_count=" + args.length().to_string(),
"ar_session_semantics=host_branch",
]
println(
provider_harness_rt_pcap_handoff_text(
command, protocol_stage, "@rawnet.RawSocket::open + @udpnet.UdpSocket::open",
fields,
),
)
}
///|
fn run_rpc_b4_mode(
args : Array[String],
alarm_ack_enabled : Bool,
command_name : String,
) -> Unit {
let parsed = match parse_rt_clock_cli_args(args) {
Some(value) => value
None => return
}
let args = parsed.args
guard args.length() >= 3 && args[0] == "--iface" else {
println(
command_name +
" --iface [station-name] [timeout-ms] [finite-cycle-count] ...",
)
return
}
let device_ip = @frame.IPv4Address::from_text(args[2][:]) catch {
err => {
println("Invalid device IP: \{to_repr(err)}")
return
}
}
let station_name = if args.length() >= 4 { args[3] } else { "controller" }
let timeout_ms = match parse_timeout_arg(args, 4, 5000) {
Some(value) => value
None => return
}
let cycle_count = match parse_rt_cycle_count_or_unbounded(args, 5) {
Some(value) => value
None => return
}
let ar_uuid = if args.length() >= 7 {
match parse_uuid_arg(args[6], "ar-uuid") {
Some(uuid) => uuid
None => return
}
} else {
default_handoff_uuid(1)
}
let activity_uuid = if args.length() >= 8 {
match parse_uuid_arg(args[7], "activity-uuid") {
Some(uuid) => uuid
None => return
}
} else {
default_handoff_uuid(2)
}
let object_uuid = if args.length() >= 9 {
match parse_uuid_arg(args[8], "object-uuid") {
Some(uuid) => uuid
None => return
}
} else {
@rpc.Uuid::empty()
}
emit_windows_realtime_plan(parsed.windows_realtime)
let fields : Array[String] = [
"interface=" + args[1],
"device_ip=" + device_ip.to_text(),
"station_name=" + station_name,
"timeout_ms=" + timeout_ms.to_string(),
"cycle_count=" + rt_cycle_count_text(cycle_count),
"alarm_ack_enabled=" + alarm_ack_enabled.to_string(),
"ar_session_semantics=host_branch",
]
append_uuid_fields(fields, ar_uuid, activity_uuid, object_uuid)
append_rt_plan_fields(
fields,
parsed.clock,
parsed.iocr_period,
parsed.data_hold,
parsed.output_vlan,
parsed.windows_realtime,
)
println(
provider_harness_rt_pcap_handoff_text(
command_name, "RT exchange", "@rawnet.RawSocket::open + @udpnet.UdpSocket::open + @rawnet.highres_micros/sleep_until_micros",
fields,
),
)
}
///|
fn run_rpc_b4(args : Array[String]) -> Unit {
run_rpc_b4_mode(args, false, "rpc-b4")
}
///|
fn run_rpc_b4_update_io(args : Array[String]) -> Unit {
let parsed = match parse_rt_clock_cli_args(args) {
Some(value) => value
None => return
}
let args = parsed.args
guard args.length() >= 5 && args[0] == "--iface" else {
println(
"rpc-b4-update-io --iface ...",
)
return
}
let before_words = match parse_u16_words_arg(args[3], "before-words") {
Some(words) => words
None => return
}
let after_words = match parse_u16_words_arg(args[4], "after-words") {
Some(words) => words
None => return
}
let change_cycle = if args.length() >= 9 {
match parse_number_arg(args[8], "change-cycle") {
Some(value) => value
None => return
}
} else {
64
}
run_rpc_b4_output_handoff(
args,
parsed,
"rpc-b4-update-io",
rt_output_update_from_words(before_words, after_words, change_cycle),
rt_input_read_disabled(),
)
}
///|
fn run_rpc_b4_io_write(args : Array[String]) -> Unit {
let parsed = match parse_rt_clock_cli_args(args) {
Some(value) => value
None => return
}
let args = parsed.args
guard args.length() >= 4 && args[0] == "--iface" else {
println("rpc-b4-io-write --iface ...")
return
}
let output_words = match parse_u16_words_arg(args[3], "output-words") {
Some(words) => words
None => return
}
run_rpc_b4_output_handoff(
args,
parsed,
"rpc-b4-io-write",
rt_output_write_from_words(output_words),
rt_input_read_disabled(),
)
}
///|
fn run_rpc_b4_alarm(args : Array[String]) -> Unit {
run_rpc_b4_mode(args, true, "rpc-b4-alarm")
}
///|
fn run_rpc_b4_read_io(args : Array[String]) -> Unit {
let parsed = match parse_rt_clock_cli_args(args) {
Some(value) => value
None => return
}
let args = parsed.args
guard args.length() >= 3 && args[0] == "--iface" else {
println("rpc-b4-read-io --iface ...")
return
}
let sample_every = if args.length() >= 7 {
match parse_number_arg(args[6], "sample-every") {
Some(value) => value
None => return
}
} else {
16
}
run_rpc_b4_output_handoff(
args,
parsed,
"rpc-b4-read-io",
rt_output_update_disabled(),
rt_input_read_every(sample_every),
)
}
///|
fn run_rpc_b4_output_handoff(
args : Array[String],
parsed : RtClockCliArgs,
command_name : String,
update : RtOutputUpdatePlan,
input_read : RtInputReadPlan,
) -> Unit {
let device_ip = @frame.IPv4Address::from_text(args[2][:]) catch {
err => {
println("Invalid device IP: \{to_repr(err)}")
return
}
}
let station_name = if args.length() >= 5 && command_name == "rpc-b4-io-write" {
args[4]
} else if args.length() >= 6 && command_name == "rpc-b4-update-io" {
args[5]
} else if args.length() >= 4 {
args[3]
} else {
"controller"
}
let timeout_index = if command_name == "rpc-b4-update-io" {
6
} else if command_name == "rpc-b4-io-write" {
5
} else {
4
}
let timeout_ms = match parse_timeout_arg(args, timeout_index, 5000) {
Some(value) => value
None => return
}
let cycle_index = if command_name == "rpc-b4-update-io" {
7
} else if command_name == "rpc-b4-io-write" {
6
} else {
5
}
let cycle_count = match parse_rt_cycle_count_or_unbounded(args, cycle_index) {
Some(value) => value
None => return
}
emit_windows_realtime_plan(parsed.windows_realtime)
let fields : Array[String] = [
"interface=" + args[1],
"device_ip=" + device_ip.to_text(),
"station_name=" + station_name,
"timeout_ms=" + timeout_ms.to_string(),
"cycle_count=" + rt_cycle_count_text(cycle_count),
"output_update_enabled=" + update.enabled.to_string(),
"output_update_mode=" + update.mode,
"output_update_change_cycle=" + update.change_cycle.to_string(),
"output_before_words=" + payload_u16_words_text(update.before_payload, 5),
"output_after_words=" + payload_u16_words_text(update.after_payload, 5),
"input_read_enabled=" + input_read.enabled.to_string(),
"input_read_sample_every=" + input_read.sample_every.to_string(),
"ar_session_semantics=host_branch",
]
append_rt_plan_fields(
fields,
parsed.clock,
parsed.iocr_period,
parsed.data_hold,
parsed.output_vlan,
parsed.windows_realtime,
)
println(
provider_harness_rt_pcap_handoff_text(
command_name, "RT exchange", "@rawnet.RawSocket::open + @udpnet.UdpSocket::open + @rawnet.highres_micros/sleep_until_micros",
fields,
),
)
}