use crate::utils::ffi;
use crate::master::core::EtherCATMaster;
pub struct MasterConfig<'a> {
master: &'a EtherCATMaster,
}
impl<'a> MasterConfig<'a> {
pub(crate) fn new(master: &'a EtherCATMaster) -> Self {
Self { master }
}
pub fn loop_cycle(&self) -> u32 {
unsafe { ffi::GetTimingMode(self.master.index()) }
}
pub fn set_loop_cycle(&self, time_ns: u32) {
unsafe { ffi::SetMasterLoopCycleTime(self.master.index(), time_ns) };
}
pub fn frame_high_priority(&self) -> bool {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return false; }
unsafe {
let byte_ptr = ptr as *const u8;
*byte_ptr.add(OFFSET_FRAME_HIGH_PRIORITY) != 0
}
}
pub fn set_frame_high_priority(&self, value: bool) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
*byte_ptr.add(OFFSET_FRAME_HIGH_PRIORITY) = if value { 1 } else { 0 };
}
}
pub fn use_udp(&self) -> bool {
(unsafe { ffi::GetUdpMode(self.master.index()) }) != 0
}
pub fn set_use_udp(&self, value: bool) {
unsafe { ffi::SetUdpMode(self.master.index(), if value { 1 } else { 0 }) };
}
pub fn is_udp_available(&self) -> bool {
(unsafe { ffi::IsUdpAvailable(self.master.index()) }) != 0
}
pub fn vlan_id(&self) -> u16 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 0; }
unsafe {
let byte_ptr = ptr as *const u8;
u16::from_le_bytes([
*byte_ptr.add(OFFSET_VLAN_ID),
*byte_ptr.add(OFFSET_VLAN_ID + 1),
])
}
}
pub fn set_vlan_id(&self, value: u16) {
if value > 4095 { return; }
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
let bytes = value.to_le_bytes();
*byte_ptr.add(OFFSET_VLAN_ID) = bytes[0];
*byte_ptr.add(OFFSET_VLAN_ID + 1) = bytes[1];
}
}
pub fn vlan_priority(&self) -> u8 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 0; }
unsafe {
let byte_ptr = ptr as *const u8;
*byte_ptr.add(OFFSET_VLAN_PRIORITY)
}
}
pub fn set_vlan_priority(&self, value: u8) {
if value > 7 { return; }
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
*byte_ptr.add(OFFSET_VLAN_PRIORITY) = value;
}
}
pub fn overlapping_groups(&self) -> bool {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return false; }
unsafe {
let byte_ptr = ptr as *const u8;
*byte_ptr.add(OFFSET_OVERLAPPING_GROUPS) != 0
}
}
pub fn set_overlapping_groups(&self, value: bool) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
*byte_ptr.add(OFFSET_OVERLAPPING_GROUPS) = if value { 1 } else { 0 };
}
}
pub fn packed_mode(&self) -> bool {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return false; }
unsafe {
let byte_ptr = ptr as *const u8;
*byte_ptr.add(OFFSET_PACKED_MODE) != 0
}
}
pub fn set_packed_mode(&self, value: bool) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
*byte_ptr.add(OFFSET_PACKED_MODE) = if value { 1 } else { 0 };
}
}
pub fn filter_threshold(&self) -> u32 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 3; }
unsafe {
let byte_ptr = ptr as *const u8;
u32::from_le_bytes([
*byte_ptr.add(OFFSET_FILTER_THRESHOLD),
*byte_ptr.add(OFFSET_FILTER_THRESHOLD + 1),
*byte_ptr.add(OFFSET_FILTER_THRESHOLD + 2),
*byte_ptr.add(OFFSET_FILTER_THRESHOLD + 3),
])
}
}
pub fn set_filter_threshold(&self, value: u32) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
let bytes = value.to_le_bytes();
for i in 0..4 {
*byte_ptr.add(OFFSET_FILTER_THRESHOLD + i) = bytes[i];
}
}
}
pub fn frame_repeat_count(&self) -> u8 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 1; }
unsafe {
let byte_ptr = ptr as *const u8;
*byte_ptr.add(OFFSET_FRAME_REPEAT_COUNT)
}
}
pub fn set_frame_repeat_count(&self, value: u8) {
if value < 1 || value > 3 { return; }
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
*byte_ptr.add(OFFSET_FRAME_REPEAT_COUNT) = value;
}
}
pub fn mutex_protection(&self) -> bool {
(unsafe { ffi::GetMutexProtection(self.master.index()) }) != 0
}
pub fn set_mutex_protection(&self, value: bool) {
unsafe { ffi::SetMutexProtection(self.master.index(), if value { 1 } else { 0 }) };
}
pub fn process_data_watchdog_ms(&self) -> u32 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 0; }
unsafe {
let byte_ptr = ptr as *const u8;
u16::from_le_bytes([
*byte_ptr.add(OFFSET_WD_PD_TIMEOUT_MS),
*byte_ptr.add(OFFSET_WD_PD_TIMEOUT_MS + 1),
]) as u32
}
}
pub fn set_process_data_watchdog_ms(&self, value: u32) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
let clamped = value.min(6553) as u16;
unsafe {
let byte_ptr = ptr as *mut u8;
let bytes = clamped.to_le_bytes();
*byte_ptr.add(OFFSET_WD_PD_TIMEOUT_MS) = bytes[0];
*byte_ptr.add(OFFSET_WD_PD_TIMEOUT_MS + 1) = bytes[1];
}
}
pub fn pdi_watchdog_ms(&self) -> u32 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 0; }
unsafe {
let byte_ptr = ptr as *const u8;
u16::from_le_bytes([
*byte_ptr.add(OFFSET_WD_PDI_TIMEOUT_MS),
*byte_ptr.add(OFFSET_WD_PDI_TIMEOUT_MS + 1),
]) as u32
}
}
pub fn set_pdi_watchdog_ms(&self, value: u32) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
let clamped = value.min(6553) as u16;
unsafe {
let byte_ptr = ptr as *mut u8;
let bytes = clamped.to_le_bytes();
*byte_ptr.add(OFFSET_WD_PDI_TIMEOUT_MS) = bytes[0];
*byte_ptr.add(OFFSET_WD_PDI_TIMEOUT_MS + 1) = bytes[1];
}
}
pub fn adaptive_timeout_enabled(&self) -> bool {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return false; }
unsafe {
let byte_ptr = ptr as *const u8;
*byte_ptr.add(OFFSET_ADAPTIVE_TIMEOUT_ENABLED) != 0
}
}
pub fn set_adaptive_timeout_enabled(&self, value: bool) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
*byte_ptr.add(OFFSET_ADAPTIVE_TIMEOUT_ENABLED) = if value { 1 } else { 0 };
}
}
pub fn timeout_init_to_preop(&self) -> u32 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 3000; }
unsafe {
let byte_ptr = ptr as *const u8;
u32::from_le_bytes([
*byte_ptr.add(OFFSET_TIMEOUT_INIT_TO_PREOP),
*byte_ptr.add(OFFSET_TIMEOUT_INIT_TO_PREOP + 1),
*byte_ptr.add(OFFSET_TIMEOUT_INIT_TO_PREOP + 2),
*byte_ptr.add(OFFSET_TIMEOUT_INIT_TO_PREOP + 3),
])
}
}
pub fn set_timeout_init_to_preop(&self, value: u32) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
let bytes = value.to_le_bytes();
for i in 0..4 {
*byte_ptr.add(OFFSET_TIMEOUT_INIT_TO_PREOP + i) = bytes[i];
}
}
}
pub fn timeout_preop_to_safeop(&self) -> u32 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 10000; }
unsafe {
let byte_ptr = ptr as *const u8;
u32::from_le_bytes([
*byte_ptr.add(OFFSET_TIMEOUT_PREOP_TO_SAFEOP),
*byte_ptr.add(OFFSET_TIMEOUT_PREOP_TO_SAFEOP + 1),
*byte_ptr.add(OFFSET_TIMEOUT_PREOP_TO_SAFEOP + 2),
*byte_ptr.add(OFFSET_TIMEOUT_PREOP_TO_SAFEOP + 3),
])
}
}
pub fn set_timeout_preop_to_safeop(&self, value: u32) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
let bytes = value.to_le_bytes();
for i in 0..4 {
*byte_ptr.add(OFFSET_TIMEOUT_PREOP_TO_SAFEOP + i) = bytes[i];
}
}
}
pub fn timeout_safeop_to_op(&self) -> u32 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 5000; }
unsafe {
let byte_ptr = ptr as *const u8;
u32::from_le_bytes([
*byte_ptr.add(OFFSET_TIMEOUT_SAFEOP_TO_OP),
*byte_ptr.add(OFFSET_TIMEOUT_SAFEOP_TO_OP + 1),
*byte_ptr.add(OFFSET_TIMEOUT_SAFEOP_TO_OP + 2),
*byte_ptr.add(OFFSET_TIMEOUT_SAFEOP_TO_OP + 3),
])
}
}
pub fn set_timeout_safeop_to_op(&self, value: u32) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
let bytes = value.to_le_bytes();
for i in 0..4 {
*byte_ptr.add(OFFSET_TIMEOUT_SAFEOP_TO_OP + i) = bytes[i];
}
}
}
pub fn scan_revision_match(&self) -> u8 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 0; }
unsafe {
let byte_ptr = ptr as *const u8;
*byte_ptr.add(OFFSET_SCAN_REVISION_MATCH)
}
}
pub fn set_scan_revision_match(&self, mode: u8) {
if mode > 2 { return; }
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
*byte_ptr.add(OFFSET_SCAN_REVISION_MATCH) = mode;
}
}
pub fn drift_compensation(&self) -> bool {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return false; }
unsafe {
let byte_ptr = ptr as *const u8;
*byte_ptr.add(OFFSET_DRIFT_COMPENSATION) != 0
}
}
pub fn set_drift_compensation(&self, value: bool) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
*byte_ptr.add(OFFSET_DRIFT_COMPENSATION) = if value { 1 } else { 0 };
}
}
pub fn mailbox_timeout(&self) -> u32 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 5000; }
unsafe {
let byte_ptr = ptr as *const u8;
u32::from_le_bytes([
*byte_ptr.add(OFFSET_MAILBOX_TIMEOUT),
*byte_ptr.add(OFFSET_MAILBOX_TIMEOUT + 1),
*byte_ptr.add(OFFSET_MAILBOX_TIMEOUT + 2),
*byte_ptr.add(OFFSET_MAILBOX_TIMEOUT + 3),
])
}
}
pub fn set_mailbox_timeout(&self, ms: u32) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
let bytes = ms.to_le_bytes();
for i in 0..4 {
*byte_ptr.add(OFFSET_MAILBOX_TIMEOUT + i) = bytes[i];
}
}
}
pub fn mailbox_retries(&self) -> u32 {
let ptr = unsafe { ffi::GetMasterStateCache(self.master.index()) };
if ptr.is_null() { return 3; }
unsafe {
let byte_ptr = ptr as *const u8;
u32::from_le_bytes([
*byte_ptr.add(OFFSET_MAILBOX_RETRIES),
*byte_ptr.add(OFFSET_MAILBOX_RETRIES + 1),
*byte_ptr.add(OFFSET_MAILBOX_RETRIES + 2),
*byte_ptr.add(OFFSET_MAILBOX_RETRIES + 3),
])
}
}
pub fn set_mailbox_retries(&self, count: u32) {
let ptr = unsafe { ffi::GetMasterState(self.master.index()) };
if ptr.is_null() { return; }
unsafe {
let byte_ptr = ptr as *mut u8;
let bytes = count.to_le_bytes();
for i in 0..4 {
*byte_ptr.add(OFFSET_MAILBOX_RETRIES + i) = bytes[i];
}
}
}
pub fn save_config_xml(&self, path: &str) -> bool {
let loop_cycle_us = self.loop_cycle() / 1000;
let xml = format!(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\
<DarraEtherCATConfiguration version=\"1.0\">\n\
\x20 <Master>\n\
\x20 <CycleTime unit=\"us\">{}</CycleTime>\n\
\x20 <DcCycleTime unit=\"us\">{}</DcCycleTime>\n\
\x20 <FrameHighPriority>{}</FrameHighPriority>\n\
\x20 <UseUdp>{}</UseUdp>\n\
\x20 <VlanId>{}</VlanId>\n\
\x20 <VlanPriority>{}</VlanPriority>\n\
\x20 <OverlappingGroups>{}</OverlappingGroups>\n\
\x20 <PackedMode>{}</PackedMode>\n\
\x20 <MutexProtection>{}</MutexProtection>\n\
\x20 <FilterThreshold>{}</FilterThreshold>\n\
\x20 <FrameRepeatCount>{}</FrameRepeatCount>\n\
\x20 <ProcessDataWatchdogMs>{}</ProcessDataWatchdogMs>\n\
\x20 <PdiWatchdogMs>{}</PdiWatchdogMs>\n\
\x20 <AdaptiveTimeout>{}</AdaptiveTimeout>\n\
\x20 <DriftCompensation>{}</DriftCompensation>\n\
\x20 <ScanRevisionMatch>{}</ScanRevisionMatch>\n\
\x20 <MailboxTimeout>{}</MailboxTimeout>\n\
\x20 <MailboxRetries>{}</MailboxRetries>\n\
\x20 </Master>\n\
</DarraEtherCATConfiguration>",
loop_cycle_us,
loop_cycle_us,
self.frame_high_priority(),
self.use_udp(),
self.vlan_id(),
self.vlan_priority(),
self.overlapping_groups(),
self.packed_mode(),
self.mutex_protection(),
self.filter_threshold(),
self.frame_repeat_count(),
self.process_data_watchdog_ms(),
self.pdi_watchdog_ms(),
self.adaptive_timeout_enabled(),
self.drift_compensation(),
self.scan_revision_match(),
self.mailbox_timeout(),
self.mailbox_retries(),
);
std::fs::write(path, xml.as_bytes()).is_ok()
}
pub fn export_config_string(&self) -> Option<String> {
let json = format!(
"{{\n\
\x20 \"loop_cycle_ns\": {},\n\
\x20 \"frame_high_priority\": {},\n\
\x20 \"use_udp\": {},\n\
\x20 \"udp_available\": {},\n\
\x20 \"vlan_id\": {},\n\
\x20 \"vlan_priority\": {},\n\
\x20 \"overlapping_groups\": {},\n\
\x20 \"packed_mode\": {},\n\
\x20 \"mutex_protection\": {},\n\
\x20 \"filter_threshold\": {},\n\
\x20 \"frame_repeat_count\": {},\n\
\x20 \"process_data_watchdog_ms\": {},\n\
\x20 \"pdi_watchdog_ms\": {},\n\
\x20 \"adaptive_timeout_enabled\": {},\n\
\x20 \"timeout_init_to_preop\": {},\n\
\x20 \"timeout_preop_to_safeop\": {},\n\
\x20 \"timeout_safeop_to_op\": {},\n\
\x20 \"drift_compensation\": {},\n\
\x20 \"scan_revision_match\": {},\n\
\x20 \"mailbox_timeout\": {},\n\
\x20 \"mailbox_retries\": {}\n\
}}",
self.loop_cycle(),
self.frame_high_priority(),
self.use_udp(),
self.is_udp_available(),
self.vlan_id(),
self.vlan_priority(),
self.overlapping_groups(),
self.packed_mode(),
self.mutex_protection(),
self.filter_threshold(),
self.frame_repeat_count(),
self.process_data_watchdog_ms(),
self.pdi_watchdog_ms(),
self.adaptive_timeout_enabled(),
self.timeout_init_to_preop(),
self.timeout_preop_to_safeop(),
self.timeout_safeop_to_op(),
self.drift_compensation(),
self.scan_revision_match(),
self.mailbox_timeout(),
self.mailbox_retries(),
);
Some(json)
}
}
const OFFSET_FRAME_HIGH_PRIORITY: usize = 0x100;
const OFFSET_VLAN_ID: usize = 0x102;
const OFFSET_VLAN_PRIORITY: usize = 0x104;
const OFFSET_OVERLAPPING_GROUPS: usize = 0x105;
const OFFSET_PACKED_MODE: usize = 0x106;
const OFFSET_FILTER_THRESHOLD: usize = 0x108;
const OFFSET_FRAME_REPEAT_COUNT: usize = 0x10C;
const OFFSET_WD_PD_TIMEOUT_MS: usize = 0x110;
const OFFSET_WD_PDI_TIMEOUT_MS: usize = 0x112;
const OFFSET_ADAPTIVE_TIMEOUT_ENABLED: usize = 0x114;
const OFFSET_TIMEOUT_INIT_TO_PREOP: usize = 0x118;
const OFFSET_TIMEOUT_PREOP_TO_SAFEOP: usize = 0x11C;
const OFFSET_TIMEOUT_SAFEOP_TO_OP: usize = 0x120;
const OFFSET_DRIFT_COMPENSATION: usize = 0x124;
const OFFSET_SCAN_REVISION_MATCH: usize = 0x125;
const OFFSET_MAILBOX_TIMEOUT: usize = 0x128;
const OFFSET_MAILBOX_RETRIES: usize = 0x12C;
pub fn save_master_settings(path: &str, loop_cycle_us: u16, dc_cycle_us: u16, expected_slave_count: i32) -> bool {
let xml = format!(
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\
<DarraEtherCATConfiguration version=\"1.0\">\n\
\x20 <Master>\n\
\x20 <CycleTime unit=\"us\">{}</CycleTime>\n\
\x20 <DcCycleTime unit=\"us\">{}</DcCycleTime>\n\
\x20 <ExpectedSlaveCount>{}</ExpectedSlaveCount>\n\
\x20 </Master>\n\
</DarraEtherCATConfiguration>",
loop_cycle_us, dc_cycle_us, expected_slave_count
);
std::fs::write(path, xml.as_bytes()).is_ok()
}
pub fn load_master_settings(path: &str) -> (bool, u16, u16) {
let mut loop_cycle: u16 = 1000;
let mut dc_cycle: u16 = 1000;
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(_) => return (false, loop_cycle, dc_cycle),
};
if !content.contains("DarraEtherCATConfiguration") {
return (false, loop_cycle, dc_cycle);
}
if let Some(val) = extract_xml_value(&content, "CycleTime") {
if let Ok(v) = val.parse::<u16>() {
if v > 0 { loop_cycle = v; }
}
}
if let Some(val) = extract_xml_value(&content, "DcCycleTime") {
if let Ok(v) = val.parse::<u16>() {
if v > 0 { dc_cycle = v; }
}
}
(true, loop_cycle, dc_cycle)
}
fn extract_xml_value(xml: &str, tag: &str) -> Option<String> {
let open_tag = format!("<{}", tag);
let close_tag = format!("</{}>", tag);
let start_pos = xml.find(&open_tag)?;
let after_open = &xml[start_pos..];
let content_start = after_open.find('>')? + 1;
let remaining = &after_open[content_start..];
let end_pos = remaining.find(&close_tag)?;
let value = remaining[..end_pos].trim();
if value.is_empty() { None } else { Some(value.to_string()) }
}
impl<'a> std::fmt::Display for MasterConfig<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let cycle_us = self.loop_cycle() as f64 / 1000.0;
write!(f, "MasterConfig(cycle={:.0}us, mutex={}, mbx_timeout={}ms, mbx_retries={})",
cycle_us,
if self.mutex_protection() { "on" } else { "off" },
self.mailbox_timeout(),
self.mailbox_retries())
}
}