use std::collections::HashMap;
use std::io::BufRead;
use quick_xml::events::Event;
use quick_xml::reader::Reader;
pub const ENI_NAMESPACE: &str = "";
pub const DENI_NAMESPACE: &str = "http://www.darra.com/ethercat/deni";
const DENI_PREFIX: &str = "deni";
pub const ESI_NAMESPACE: &str = "http://www.ethercat.org/esi";
pub fn parse_uint_hex_or_dec(s: &str) -> Option<u32> {
let s = s.trim();
if s.is_empty() {
return None;
}
if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
return u32::from_str_radix(hex, 16).ok();
}
if let Some(hex) = s.strip_prefix("#x").or_else(|| s.strip_prefix("#X")) {
return u32::from_str_radix(hex, 16).ok();
}
if let Some(hex) = s.strip_prefix('#') {
return u32::from_str_radix(hex, 16).ok();
}
if let Ok(val) = s.parse::<u32>() {
return Some(val);
}
if s.chars().all(|c| c.is_ascii_hexdigit()) && s.chars().any(|c| c.is_ascii_alphabetic()) {
return u32::from_str_radix(s, 16).ok();
}
None
}
pub fn parse_u16_hex_or_dec(s: &str) -> Option<u16> {
parse_uint_hex_or_dec(s).map(|v| v as u16)
}
pub fn parse_u8_hex_or_dec(s: &str) -> Option<u8> {
parse_uint_hex_or_dec(s).and_then(|v| {
if v <= 255 { Some(v as u8) } else { None }
})
}
pub fn parse_i32_hex_or_dec(s: &str) -> Option<i32> {
let s = s.trim();
if s.is_empty() {
return None;
}
if let Some(rest) = s.strip_prefix('-') {
return parse_uint_hex_or_dec(rest).map(|v| -(v as i32));
}
parse_uint_hex_or_dec(s).map(|v| v as i32)
}
pub fn bytes_to_hex_string(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
pub fn hex_string_to_bytes(hex: &str) -> Option<Vec<u8>> {
let hex = hex.trim();
let hex = hex.strip_prefix("0x").or_else(|| hex.strip_prefix("0X")).unwrap_or(hex);
if hex.len() % 2 != 0 {
return None;
}
let mut bytes = Vec::with_capacity(hex.len() / 2);
for i in (0..hex.len()).step_by(2) {
let byte_str = &hex[i..i + 2];
match u8::from_str_radix(byte_str, 16) {
Ok(b) => bytes.push(b),
Err(_) => return None,
}
}
Some(bytes)
}
fn parse_bool_str(s: &str) -> bool {
let s = s.trim().to_lowercase();
matches!(s.as_str(), "true" | "1" | "yes")
}
#[derive(Debug, Clone, Default)]
pub struct QosConfiguration {
pub vlan_id: u16,
pub vlan_priority: u8,
pub frame_high_priority: bool,
pub enable_dscp: bool,
pub dscp_value: u8,
}
#[derive(Debug, Clone, Default)]
pub struct StartupSdoConfig {
pub index: u16,
pub sub_index: u8,
pub data: Vec<u8>,
pub transition: String,
pub comment: String,
pub complete_access: bool,
pub timeout_ms: u32,
}
#[derive(Debug, Clone)]
pub struct MasterXmlConfiguration {
pub version: String,
pub cycle_time_us: u32,
pub dc_cycle_ns: u32,
pub adapter_name: String,
pub adapter_description: String,
pub adapter_mac: String,
pub redundant_adapter_name: String,
pub redundant_adapter_description: String,
pub redundant_adapter_mac: String,
pub redundancy_enabled: bool,
pub expected_slave_count: i32,
pub use_udp: bool,
pub vlan_id: u16,
pub vlan_priority: u8,
pub enable_overlapping_groups: bool,
pub packed_mode: bool,
pub frame_high_priority: bool,
pub cpu_affinity: i32,
pub adaptive_timeout: bool,
pub frame_repeat_count: u8,
pub filter_threshold: u32,
pub mutex_protection: bool,
pub dc_enabled: bool,
pub drift_compensation: bool,
pub timeout_init_to_preop: i32,
pub timeout_preop_to_safeop: i32,
pub timeout_safeop_to_op: i32,
pub process_data_watchdog_ms: u32,
pub pdi_watchdog_ms: i32,
pub group_configs: Vec<GroupConfigData>,
pub slave_group_assignments: HashMap<u16, u8>,
pub slave_dc_configs: Vec<SlaveDcConfig>,
pub slaves: Vec<SlaveXmlConfiguration>,
pub saved_date: String,
pub saved_by: String,
pub machine_name: String,
pub dll_version: String,
pub qos: QosConfiguration,
}
impl Default for MasterXmlConfiguration {
fn default() -> Self {
Self {
version: "3.0".to_string(),
cycle_time_us: 1000,
dc_cycle_ns: 1000000,
adapter_name: String::new(),
adapter_description: String::new(),
adapter_mac: String::new(),
redundant_adapter_name: String::new(),
redundant_adapter_description: String::new(),
redundant_adapter_mac: String::new(),
redundancy_enabled: false,
expected_slave_count: 0,
use_udp: false,
vlan_id: 0,
vlan_priority: 0,
enable_overlapping_groups: false,
packed_mode: false,
frame_high_priority: false,
cpu_affinity: -1,
adaptive_timeout: false,
frame_repeat_count: 1,
filter_threshold: 3,
mutex_protection: true,
dc_enabled: false,
drift_compensation: false,
timeout_init_to_preop: 0,
timeout_preop_to_safeop: 0,
timeout_safeop_to_op: 0,
process_data_watchdog_ms: 0,
pdi_watchdog_ms: 0,
group_configs: Vec::new(),
slave_group_assignments: HashMap::new(),
slave_dc_configs: Vec::new(),
slaves: Vec::new(),
saved_date: String::new(),
saved_by: String::new(),
machine_name: String::new(),
dll_version: String::new(),
qos: QosConfiguration::default(),
}
}
}
pub fn apply_xml_configuration(config: &MasterXmlConfiguration, master_index: u16) -> bool {
unsafe {
if config.cycle_time_us > 0 {
crate::utils::ffi::SetMasterLoopCycleTime(master_index, config.cycle_time_us * 1000);
}
if config.cpu_affinity >= 0 {
crate::utils::ffi::SetMasterCpuAffinity(master_index, config.cpu_affinity);
}
crate::utils::ffi::SetMutexProtection(master_index, if config.mutex_protection { 1 } else { 0 });
}
true
}
#[derive(Debug, Clone, Default)]
pub struct GroupConfigData {
pub group_index: u8,
pub enabled: bool,
pub cycle_divider: u8,
}
#[derive(Debug, Clone, Default)]
pub struct SlaveDcConfig {
pub slave_index: u16,
pub vendor_id: u32,
pub product_code: u32,
pub sync0_cycle_ns: u32,
pub sync1_cycle_ns: u32,
pub shift_ns: i32,
}
#[derive(Debug, Clone, Default)]
pub struct XmlConfigurationResult {
pub success: bool,
pub config_slave_count: i32,
pub actual_slave_count: i32,
pub matched_count: i32,
pub slave_details: Vec<String>,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct SyncManagerConfig {
pub index: i32,
pub start_addr: u16,
pub length: u16,
pub flags: u32,
pub sm_type: u8,
pub pdo_indices: Vec<u16>,
pub watchdog: i32,
}
#[derive(Debug, Clone, Default)]
pub struct PdoEntryConfig {
pub index: u16,
pub sub_index: u8,
pub bit_length: u8,
pub name: String,
pub data_type: String,
pub offset: i32,
pub pdo_index: u16,
}
#[derive(Debug, Clone, Default)]
pub struct SlaveXmlConfiguration {
pub index: i32,
pub name: String,
pub drive_name: String,
pub vendor_id: u32,
pub product_code: u32,
pub revision: u32,
pub config_addr: u16,
pub input_size: i32,
pub output_size: i32,
pub input_pdos: Vec<PdoEntryConfig>,
pub output_pdos: Vec<PdoEntryConfig>,
pub sdo_init: Vec<StartupSdoConfig>,
pub serial_no: u32,
pub alias_addr: u16,
pub mbx_write_offset: u16,
pub mbx_read_offset: u16,
pub mbx_write_length: u16,
pub mbx_read_length: u16,
pub mbx_poll_time: u16,
pub cia402_profile_no: u16,
pub bootstrap_send_offset: u16,
pub bootstrap_send_length: u16,
pub bootstrap_recv_offset: u16,
pub bootstrap_recv_length: u16,
pub has_dc: bool,
pub sync0_cycle_ns: u32,
pub sync1_cycle_ns: u32,
pub shift_ns: i32,
pub sync_managers: Vec<SyncManagerConfig>,
pub should_write_pdo_assignment: bool,
pub should_write_pdo_configuration: bool,
pub supports_complete_access: bool,
pub has_mdp: bool,
pub block_lrw: bool,
pub dtype: u16,
pub has_coe: bool,
pub has_foe: bool,
pub has_eoe: bool,
pub has_soe: bool,
pub has_aoe: bool,
pub has_voe: bool,
pub mailbox_data_link_layer: bool,
pub is_optional: bool,
pub wd_time_pd: u16,
pub wd_time_pdi: u16,
pub wd_divider: u16,
pub topology: u8,
pub active_ports: u8,
pub supports_frame_repeat: bool,
}
#[derive(Debug, Clone)]
struct XmlNode {
local_name: String,
prefix: String,
attributes: Vec<(String, String)>,
text: String,
children: Vec<XmlNode>,
}
impl XmlNode {
fn new(local_name: &str) -> Self {
Self {
local_name: local_name.to_string(),
prefix: String::new(),
attributes: Vec::new(),
text: String::new(),
children: Vec::new(),
}
}
fn find_child(&self, name: &str) -> Option<&XmlNode> {
self.children.iter().find(|c| c.local_name.eq_ignore_ascii_case(name))
}
fn find_children(&self, name: &str) -> Vec<&XmlNode> {
self.children.iter().filter(|c| c.local_name.eq_ignore_ascii_case(name)).collect()
}
fn find_child_ns(&self, name: &str, prefix: &str) -> Option<&XmlNode> {
self.children.iter().find(|c| {
c.local_name.eq_ignore_ascii_case(name) && c.prefix == prefix
})
}
fn text_trimmed(&self) -> &str {
self.text.trim()
}
fn attr(&self, name: &str) -> Option<&str> {
self.attributes.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(name))
.map(|(_, v)| v.as_str())
}
fn child_text(&self, name: &str) -> Option<&str> {
self.find_child(name).map(|c| c.text_trimmed())
}
fn child_text_ns(&self, name: &str, prefix: &str) -> Option<&str> {
self.find_child_ns(name, prefix).map(|c| c.text_trimmed())
}
}
fn build_dom<R: BufRead>(reader: &mut Reader<R>) -> Result<XmlNode, String> {
let mut buf = Vec::new();
let mut stack: Vec<XmlNode> = Vec::new();
let mut root: Option<XmlNode> = None;
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) => {
let (prefix, local) = split_qname(e.name().as_ref());
let mut node = XmlNode::new(&local);
node.prefix = prefix;
for attr_result in e.attributes() {
if let Ok(attr) = attr_result {
let key = String::from_utf8_lossy(attr.key.as_ref()).to_string();
let val = attr.unescape_value().unwrap_or_default().to_string();
node.attributes.push((key, val));
}
}
stack.push(node);
}
Ok(Event::End(_)) => {
if let Some(node) = stack.pop() {
if let Some(parent) = stack.last_mut() {
parent.children.push(node);
} else {
root = Some(node);
}
}
}
Ok(Event::Empty(ref e)) => {
let (prefix, local) = split_qname(e.name().as_ref());
let mut node = XmlNode::new(&local);
node.prefix = prefix;
for attr_result in e.attributes() {
if let Ok(attr) = attr_result {
let key = String::from_utf8_lossy(attr.key.as_ref()).to_string();
let val = attr.unescape_value().unwrap_or_default().to_string();
node.attributes.push((key, val));
}
}
if let Some(parent) = stack.last_mut() {
parent.children.push(node);
} else {
root = Some(node);
}
}
Ok(Event::Text(ref e)) => {
let text = e.unescape().unwrap_or_default().to_string();
if let Some(current) = stack.last_mut() {
current.text.push_str(&text);
}
}
Ok(Event::CData(ref e)) => {
let text = String::from_utf8_lossy(e.as_ref()).to_string();
if let Some(current) = stack.last_mut() {
current.text.push_str(&text);
}
}
Ok(Event::Eof) => break,
Ok(_) => {} Err(e) => return Err(format!("XML 解析错误: {}", e)),
}
buf.clear();
}
root.ok_or_else(|| "XML 文件为空或格式无效".to_string())
}
fn split_qname(name: &[u8]) -> (String, String) {
let name_str = String::from_utf8_lossy(name);
if let Some(pos) = name_str.find(':') {
(name_str[..pos].to_string(), name_str[pos + 1..].to_string())
} else {
(String::new(), name_str.to_string())
}
}
pub fn load_xml_configuration(path: &str) -> Result<MasterXmlConfiguration, String> {
let content = std::fs::read(path)
.map_err(|e| format!("读取文件失败 '{}': {}", path, e))?;
let xml_str = detect_and_decode(&content);
let mut reader = Reader::from_str(&xml_str);
reader.config_mut().trim_text(true);
let root = build_dom(&mut reader)?;
match root.local_name.as_str() {
"EtherCATConfig" => load_eni_configuration(&root),
"DarraEtherCATConfiguration" => load_legacy_deni_configuration(&root),
other => Err(format!(
"不支持的配置格式: {}\n支持的格式: EtherCATConfig (ENI/DENI) 或 DarraEtherCATConfiguration (旧版)",
other
)),
}
}
fn detect_and_decode(bytes: &[u8]) -> String {
if let Ok(s) = std::str::from_utf8(bytes) {
return s.to_string();
}
let (decoded, _, had_errors) = encoding_rs::GBK.decode(bytes);
if !had_errors {
return decoded.to_string();
}
String::from_utf8_lossy(bytes).to_string()
}
fn load_eni_configuration(root: &XmlNode) -> Result<MasterXmlConfiguration, String> {
let mut config = MasterXmlConfiguration::default();
let config_el = match root.find_child("Config") {
Some(el) => el,
None => {
root
}
};
if let Some(master_el) = config_el.find_child("Master") {
if let Some(deni_master) = master_el.find_child_ns("MasterConfig", DENI_PREFIX) {
parse_deni_master_config(deni_master, &mut config);
}
}
let slave_els = config_el.find_children("Slave");
for (i, slave_el) in slave_els.iter().enumerate() {
let mut slave = parse_eni_slave(slave_el);
if slave.index == 0 {
slave.index = (i + 1) as i32;
}
config.slaves.push(slave);
}
if let Some(cyclic_el) = config_el.find_child("Cyclic") {
parse_cyclic_config(cyclic_el, &mut config);
}
Ok(config)
}
fn load_legacy_deni_configuration(root: &XmlNode) -> Result<MasterXmlConfiguration, String> {
let mut config = MasterXmlConfiguration::default();
config.version = root.attr("version").unwrap_or("2.0").to_string();
if let Some(master_el) = root.find_child("Master") {
if let Some(v) = master_el.child_text("CycleTimeUs") {
config.cycle_time_us = v.parse().unwrap_or(1000);
}
if let Some(v) = master_el.child_text("DcCycleNs") {
config.dc_cycle_ns = v.parse().unwrap_or(1000000);
}
if let Some(v) = master_el.child_text("ExpectedSlaveCount") {
config.expected_slave_count = v.parse().unwrap_or(0);
}
}
if let Some(network_el) = root.find_child("Network") {
config.adapter_name = network_el.child_text("AdapterName").unwrap_or("").to_string();
config.adapter_description = network_el.child_text("AdapterDescription").unwrap_or("").to_string();
config.adapter_mac = network_el.child_text("AdapterMAC").unwrap_or("").to_string();
config.use_udp = network_el.child_text("UseUDP").map_or(false, parse_bool_str);
config.redundancy_enabled = network_el.child_text("RedundancyEnabled").map_or(false, parse_bool_str);
config.redundant_adapter_name = network_el.child_text("RedundantAdapterName").unwrap_or("").to_string();
}
if let Some(opt_el) = root.find_child("Optimization") {
config.frame_high_priority = opt_el.child_text("FrameHighPriority").map_or(false, parse_bool_str);
config.enable_overlapping_groups = opt_el.child_text("OverlappingGroups").map_or(false, parse_bool_str);
config.packed_mode = opt_el.child_text("PackedMode").map_or(false, parse_bool_str);
config.cpu_affinity = opt_el.child_text("CpuAffinity")
.and_then(|v| v.parse().ok()).unwrap_or(-1);
config.adaptive_timeout = opt_el.child_text("AdaptiveTimeout").map_or(false, parse_bool_str);
config.frame_repeat_count = opt_el.child_text("FrameRepeatCount")
.and_then(|v| v.parse().ok()).unwrap_or(1);
config.mutex_protection = opt_el.child_text("MutexProtection").map_or(true, parse_bool_str);
}
Ok(config)
}
fn parse_deni_master_config(deni: &XmlNode, config: &mut MasterXmlConfiguration) {
config.adapter_name = deni.child_text_ns("Interface", DENI_PREFIX)
.or_else(|| deni.child_text("Interface"))
.unwrap_or("").to_string();
config.adapter_description = deni.child_text_ns("InterfaceDescription", DENI_PREFIX)
.or_else(|| deni.child_text("InterfaceDescription"))
.unwrap_or("").to_string();
config.adapter_mac = deni.child_text_ns("InterfaceMAC", DENI_PREFIX)
.or_else(|| deni.child_text("InterfaceMAC"))
.unwrap_or("").to_string();
let cycle_node = deni.find_child_ns("CycleTime", DENI_PREFIX)
.or_else(|| deni.find_child("CycleTime"));
if let Some(node) = cycle_node {
if let Ok(val) = node.text_trimmed().parse::<u32>() {
let unit = node.attr("unit").unwrap_or("us");
if unit == "ns" {
config.cycle_time_us = val / 1000;
} else {
config.cycle_time_us = val;
}
}
}
let dc_cycle_node = deni.find_child_ns("DcCycleTime", DENI_PREFIX)
.or_else(|| deni.find_child("DcCycleTime"));
if let Some(node) = dc_cycle_node {
if let Ok(val) = node.text_trimmed().parse::<u32>() {
let unit = node.attr("unit").unwrap_or("ns");
if unit == "ns" {
config.dc_cycle_ns = val;
} else {
config.dc_cycle_ns = val * 1000;
}
}
}
if let Some(v) = deni.child_text_ns("ExpectedSlaveCount", DENI_PREFIX)
.or_else(|| deni.child_text("ExpectedSlaveCount"))
{
config.expected_slave_count = v.parse().unwrap_or(0);
}
config.redundant_adapter_name = deni.child_text_ns("RedundantInterface", DENI_PREFIX)
.or_else(|| deni.child_text("RedundantInterface"))
.unwrap_or("").to_string();
config.redundant_adapter_description = deni.child_text_ns("RedundantInterfaceDescription", DENI_PREFIX)
.or_else(|| deni.child_text("RedundantInterfaceDescription"))
.unwrap_or("").to_string();
config.redundant_adapter_mac = deni.child_text_ns("RedundantInterfaceMAC", DENI_PREFIX)
.or_else(|| deni.child_text("RedundantInterfaceMAC"))
.unwrap_or("").to_string();
if let Some(v) = deni.child_text_ns("RedundancyEnabled", DENI_PREFIX)
.or_else(|| deni.child_text("RedundancyEnabled"))
{
config.redundancy_enabled = parse_bool_str(v);
}
let transport = deni.find_child_ns("Transport", DENI_PREFIX)
.or_else(|| deni.find_child("Transport"));
if let Some(t) = transport {
if let Some(v) = t.child_text_ns("UseUdp", DENI_PREFIX)
.or_else(|| t.child_text("UseUdp"))
{
config.use_udp = parse_bool_str(v);
}
if let Some(v) = t.child_text_ns("VlanId", DENI_PREFIX)
.or_else(|| t.child_text("VlanId"))
{
config.vlan_id = v.parse().unwrap_or(0);
}
if let Some(v) = t.child_text_ns("VlanPriority", DENI_PREFIX)
.or_else(|| t.child_text("VlanPriority"))
{
config.vlan_priority = v.parse().unwrap_or(0);
}
}
let perf = deni.find_child_ns("Performance", DENI_PREFIX)
.or_else(|| deni.find_child("Performance"));
if let Some(p) = perf {
if let Some(v) = p.child_text_ns("OverlappingGroups", DENI_PREFIX)
.or_else(|| p.child_text("OverlappingGroups"))
{
config.enable_overlapping_groups = parse_bool_str(v);
}
if let Some(v) = p.child_text_ns("PackedMode", DENI_PREFIX)
.or_else(|| p.child_text("PackedMode"))
{
config.packed_mode = parse_bool_str(v);
}
if let Some(v) = p.child_text_ns("FrameHighPriority", DENI_PREFIX)
.or_else(|| p.child_text("FrameHighPriority"))
{
config.frame_high_priority = parse_bool_str(v);
}
if let Some(v) = p.child_text_ns("CpuAffinity", DENI_PREFIX)
.or_else(|| p.child_text("CpuAffinity"))
{
config.cpu_affinity = v.parse().unwrap_or(-1);
}
if let Some(v) = p.child_text_ns("AdaptiveTimeout", DENI_PREFIX)
.or_else(|| p.child_text("AdaptiveTimeout"))
{
config.adaptive_timeout = parse_bool_str(v);
}
if let Some(v) = p.child_text_ns("FrameRepeatCount", DENI_PREFIX)
.or_else(|| p.child_text("FrameRepeatCount"))
{
config.frame_repeat_count = v.parse().unwrap_or(1);
}
if let Some(v) = p.child_text_ns("FilterThreshold", DENI_PREFIX)
.or_else(|| p.child_text("FilterThreshold"))
{
config.filter_threshold = v.parse().unwrap_or(3);
}
if let Some(v) = p.child_text_ns("MutexProtection", DENI_PREFIX)
.or_else(|| p.child_text("MutexProtection"))
{
config.mutex_protection = parse_bool_str(v);
}
}
let timeout = deni.find_child_ns("StateTransitionTimeouts", DENI_PREFIX)
.or_else(|| deni.find_child("StateTransitionTimeouts"));
if let Some(t) = timeout {
if let Some(v) = t.child_text_ns("InitToPreOp", DENI_PREFIX)
.or_else(|| t.child_text("InitToPreOp"))
{
config.timeout_init_to_preop = v.parse().unwrap_or(0);
}
if let Some(v) = t.child_text_ns("PreOpToSafeOp", DENI_PREFIX)
.or_else(|| t.child_text("PreOpToSafeOp"))
{
config.timeout_preop_to_safeop = v.parse().unwrap_or(0);
}
if let Some(v) = t.child_text_ns("SafeOpToOp", DENI_PREFIX)
.or_else(|| t.child_text("SafeOpToOp"))
{
config.timeout_safeop_to_op = v.parse().unwrap_or(0);
}
}
let watchdog = deni.find_child_ns("Watchdog", DENI_PREFIX)
.or_else(|| deni.find_child("Watchdog"));
if let Some(w) = watchdog {
if let Some(v) = w.child_text_ns("ProcessDataWatchdogMs", DENI_PREFIX)
.or_else(|| w.child_text("ProcessDataWatchdogMs"))
{
config.process_data_watchdog_ms = v.parse().unwrap_or(0);
}
if let Some(v) = w.child_text_ns("PdiWatchdogMs", DENI_PREFIX)
.or_else(|| w.child_text("PdiWatchdogMs"))
{
config.pdi_watchdog_ms = v.parse().unwrap_or(0);
}
}
if let Some(v) = deni.child_text_ns("DriftCompensation", DENI_PREFIX)
.or_else(|| deni.child_text("DriftCompensation"))
{
config.drift_compensation = parse_bool_str(v);
}
let group_config = deni.find_child_ns("GroupConfig", DENI_PREFIX)
.or_else(|| deni.find_child("GroupConfig"));
if let Some(gc) = group_config {
for group_el in gc.children.iter().filter(|c| c.local_name.eq_ignore_ascii_case("Group")) {
let mut gd = GroupConfigData::default();
if let Some(v) = group_el.attr("Index") {
gd.group_index = v.parse().unwrap_or(0);
}
if let Some(v) = group_el.attr("Enabled") {
gd.enabled = parse_bool_str(v);
}
if let Some(v) = group_el.attr("CycleDivider") {
gd.cycle_divider = v.parse().unwrap_or(1);
}
if gd.group_index > 0 {
config.group_configs.push(gd);
}
}
let sga = gc.find_child_ns("SlaveGroupAssignment", DENI_PREFIX)
.or_else(|| gc.find_child("SlaveGroupAssignment"));
if let Some(sga_el) = sga {
for slave_el in sga_el.children.iter().filter(|c| c.local_name.eq_ignore_ascii_case("Slave")) {
if let (Some(idx_str), Some(grp_str)) = (slave_el.attr("Index"), slave_el.attr("Group")) {
if let (Ok(idx), Ok(grp)) = (idx_str.parse::<u16>(), grp_str.parse::<u8>()) {
config.slave_group_assignments.insert(idx, grp);
}
}
}
}
}
let dc = deni.find_child_ns("DistributedClocks", DENI_PREFIX)
.or_else(|| deni.find_child("DistributedClocks"));
if let Some(dc_el) = dc {
if let Some(v) = dc_el.attr("enabled") {
config.dc_enabled = parse_bool_str(v);
}
for slave_dc_el in dc_el.children.iter().filter(|c| c.local_name.eq_ignore_ascii_case("SlaveDC")) {
let mut sdc = SlaveDcConfig::default();
if let Some(v) = slave_dc_el.attr("index") {
sdc.slave_index = v.parse().unwrap_or(0);
}
if let Some(v) = slave_dc_el.attr("vendorId") {
sdc.vendor_id = parse_uint_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = slave_dc_el.attr("productCode") {
sdc.product_code = parse_uint_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = slave_dc_el.child_text_ns("Sync0CycleNs", DENI_PREFIX)
.or_else(|| slave_dc_el.child_text("Sync0CycleNs"))
{
sdc.sync0_cycle_ns = v.parse().unwrap_or(0);
}
if let Some(v) = slave_dc_el.child_text_ns("Sync1CycleNs", DENI_PREFIX)
.or_else(|| slave_dc_el.child_text("Sync1CycleNs"))
{
sdc.sync1_cycle_ns = v.parse().unwrap_or(0);
}
if let Some(v) = slave_dc_el.child_text_ns("ShiftNs", DENI_PREFIX)
.or_else(|| slave_dc_el.child_text("ShiftNs"))
{
sdc.shift_ns = v.parse().unwrap_or(0);
}
config.slave_dc_configs.push(sdc);
}
}
let metadata = deni.find_child_ns("Metadata", DENI_PREFIX)
.or_else(|| deni.find_child("Metadata"));
if let Some(m) = metadata {
config.saved_date = m.child_text_ns("SavedDate", DENI_PREFIX)
.or_else(|| m.child_text("SavedDate"))
.unwrap_or("").to_string();
config.saved_by = m.child_text_ns("SavedBy", DENI_PREFIX)
.or_else(|| m.child_text("SavedBy"))
.unwrap_or("").to_string();
config.machine_name = m.child_text_ns("MachineName", DENI_PREFIX)
.or_else(|| m.child_text("MachineName"))
.unwrap_or("").to_string();
config.dll_version = m.child_text_ns("DllVersion", DENI_PREFIX)
.or_else(|| m.child_text("DllVersion"))
.unwrap_or("").to_string();
}
config.qos.vlan_id = config.vlan_id;
config.qos.vlan_priority = config.vlan_priority;
config.qos.frame_high_priority = config.frame_high_priority;
}
fn parse_cyclic_config(cyclic: &XmlNode, config: &mut MasterXmlConfiguration) {
if config.cycle_time_us == 1000 {
if let Some(ct_node) = cyclic.find_child("CycleTime") {
if let Ok(val) = ct_node.text_trimmed().parse::<u32>() {
let unit = ct_node.attr("unit").unwrap_or("us");
if unit == "ns" || val > 100000 {
config.cycle_time_us = val / 1000;
} else {
config.cycle_time_us = val;
}
}
}
}
if !config.dc_enabled {
let dc_el = cyclic.find_child("Dc").or_else(|| cyclic.find_child("DC"));
if let Some(dc) = dc_el {
config.dc_enabled = true;
let dc_cycle_el = dc.find_child("CycleTime")
.or_else(|| dc.find_child("CycleTimeNs"));
if let Some(node) = dc_cycle_el {
if let Ok(val) = node.text_trimmed().parse::<u32>() {
let unit = node.attr("unit").unwrap_or("ns");
if unit == "ns" || val > 100000 {
config.dc_cycle_ns = val;
} else {
config.dc_cycle_ns = val * 1000;
}
}
}
}
}
let frames = cyclic.find_children("Frame");
let mut lrw_frame_count = 0;
for frame in &frames {
for cmd_el in frame.find_children("Cmd") {
if let Some(inner_cmd) = cmd_el.child_text("Cmd") {
if let Ok(cmd_type) = inner_cmd.parse::<i32>() {
if cmd_type == 12 {
lrw_frame_count += 1;
}
}
}
}
}
if lrw_frame_count > 1 {
config.enable_overlapping_groups = true;
}
}
fn parse_eni_slave(slave_el: &XmlNode) -> SlaveXmlConfiguration {
let mut slave = SlaveXmlConfiguration::default();
if let Some(info) = slave_el.find_child("Info") {
slave.name = info.child_text("Name").unwrap_or("").to_string();
if let Some(v) = info.child_text("PhysAddr") {
slave.config_addr = parse_u16_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = info.child_text("VendorId") {
slave.vendor_id = parse_uint_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = info.child_text("ProductCode") {
slave.product_code = parse_uint_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = info.child_text("RevisionNo") {
slave.revision = parse_uint_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = info.child_text("SerialNo") {
slave.serial_no = parse_uint_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = info.child_text("ProductRevision") {
slave.drive_name = v.to_string();
}
}
if let Some(pd) = slave_el.find_child("ProcessData") {
if let Some(send) = pd.find_child("Send") {
if let Some(v) = send.child_text("BitLength") {
if let Ok(bits) = v.parse::<i32>() {
slave.output_size = bits / 8;
}
}
}
if let Some(recv) = pd.find_child("Recv") {
if let Some(v) = recv.child_text("BitLength") {
if let Ok(bits) = v.parse::<i32>() {
slave.input_size = bits / 8;
}
}
}
}
if let Some(mbx) = slave_el.find_child("Mailbox") {
if let Some(send) = mbx.find_child("Send") {
if let Some(v) = send.child_text("Start") {
slave.mbx_write_offset = parse_u16_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = send.child_text("Length") {
slave.mbx_write_length = v.parse().unwrap_or(0);
}
}
if let Some(recv) = mbx.find_child("Recv") {
if let Some(v) = recv.child_text("Start") {
slave.mbx_read_offset = parse_u16_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = recv.child_text("Length") {
slave.mbx_read_length = v.parse().unwrap_or(0);
}
if let Some(v) = recv.child_text("PollTime") {
slave.mbx_poll_time = v.parse().unwrap_or(0);
}
}
if let Some(boot) = mbx.find_child("Bootstrap") {
if let Some(s) = boot.find_child("Send") {
if let Some(v) = s.child_text("Start") {
slave.bootstrap_send_offset = parse_u16_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = s.child_text("Length") {
slave.bootstrap_send_length = v.parse().unwrap_or(0);
}
}
if let Some(r) = boot.find_child("Recv") {
if let Some(v) = r.child_text("Start") {
slave.bootstrap_recv_offset = parse_u16_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = r.child_text("Length") {
slave.bootstrap_recv_length = v.parse().unwrap_or(0);
}
}
}
if let Some(coe) = mbx.find_child("CoE") {
slave.has_coe = true;
if let Some(init_cmds) = coe.find_child("InitCmds") {
for init_cmd in init_cmds.find_children("InitCmd") {
if let Some(sdo) = parse_coe_init_cmd(init_cmd) {
slave.sdo_init.push(sdo);
}
}
}
if let Some(profile) = coe.find_child("Profile") {
if let Some(ci) = profile.find_child("ChannelInfo") {
if let Some(v) = ci.child_text("ProfileNo") {
slave.cia402_profile_no = v.parse().unwrap_or(0);
}
}
}
}
if let Some(v) = mbx.attr("DataLinkLayer") {
slave.mailbox_data_link_layer = parse_bool_str(v);
}
if mbx.find_child("SoE").is_some() { slave.has_soe = true; }
if mbx.find_child("AoE").is_some() { slave.has_aoe = true; }
if mbx.find_child("EoE").is_some() { slave.has_eoe = true; }
if mbx.find_child("FoE").is_some() { slave.has_foe = true; }
if mbx.find_child("VoE").is_some() { slave.has_voe = true; }
}
let dc_el = slave_el.find_child("Dc").or_else(|| slave_el.find_child("DC"));
if let Some(dc) = dc_el {
slave.has_dc = true;
if let Some(v) = dc.child_text("CycleTime0") {
slave.sync0_cycle_ns = v.parse().unwrap_or(0);
}
if let Some(v) = dc.child_text("CycleTime1") {
slave.sync1_cycle_ns = v.parse().unwrap_or(0);
}
if let Some(v) = dc.child_text("ShiftTime") {
slave.shift_ns = parse_i32_hex_or_dec(v).unwrap_or(0);
}
}
let deni_slave = slave_el.find_child_ns("SlaveConfig", DENI_PREFIX)
.or_else(|| slave_el.children.iter().find(|c| c.local_name == "SlaveConfig" && !c.prefix.is_empty()));
if let Some(ds) = deni_slave {
parse_deni_slave_config(ds, &mut slave);
}
slave
}
fn parse_coe_init_cmd(cmd: &XmlNode) -> Option<StartupSdoConfig> {
let mut sdo = StartupSdoConfig::default();
sdo.transition = cmd.child_text("Transition").unwrap_or("PS").to_string();
sdo.comment = cmd.child_text("Comment").unwrap_or("").to_string();
if let Some(v) = cmd.child_text("Timeout") {
sdo.timeout_ms = v.parse().unwrap_or(0);
}
if let Some(v) = cmd.child_text("CompleteAccess") {
sdo.complete_access = parse_bool_str(v);
}
if let Some(v) = cmd.child_text("Index") {
sdo.index = parse_u16_hex_or_dec(v)?;
}
if let Some(v) = cmd.child_text("SubIndex") {
sdo.sub_index = parse_u8_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = cmd.child_text("Data") {
sdo.data = hex_string_to_bytes(v).unwrap_or_default();
}
Some(sdo)
}
fn parse_deni_slave_config(ds: &XmlNode, slave: &mut SlaveXmlConfiguration) {
if let Some(v) = ds.child_text_ns("Position", DENI_PREFIX).or_else(|| ds.child_text("Position")) {
slave.index = v.parse().unwrap_or(0);
}
if let Some(v) = ds.child_text_ns("DriveName", DENI_PREFIX).or_else(|| ds.child_text("DriveName")) {
slave.drive_name = v.to_string();
}
if let Some(v) = ds.child_text_ns("SerialNumber", DENI_PREFIX).or_else(|| ds.child_text("SerialNumber")) {
slave.serial_no = parse_uint_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = ds.child_text_ns("AliasAddress", DENI_PREFIX).or_else(|| ds.child_text("AliasAddress")) {
slave.alias_addr = parse_u16_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = ds.child_text_ns("Optional", DENI_PREFIX).or_else(|| ds.child_text("Optional")) {
slave.is_optional = parse_bool_str(v);
}
if let Some(v) = ds.child_text_ns("SupportsFrameRepeat", DENI_PREFIX).or_else(|| ds.child_text("SupportsFrameRepeat")) {
slave.supports_frame_repeat = parse_bool_str(v);
}
let mbx = ds.find_child_ns("MailboxConfig", DENI_PREFIX).or_else(|| ds.find_child("MailboxConfig"));
if let Some(m) = mbx {
if let Some(v) = m.child_text_ns("WriteOffset", DENI_PREFIX).or_else(|| m.child_text("WriteOffset")) {
slave.mbx_write_offset = parse_u16_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = m.child_text_ns("ReadOffset", DENI_PREFIX).or_else(|| m.child_text("ReadOffset")) {
slave.mbx_read_offset = parse_u16_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = m.child_text_ns("WriteLength", DENI_PREFIX).or_else(|| m.child_text("WriteLength")) {
slave.mbx_write_length = v.parse().unwrap_or(0);
}
if let Some(v) = m.child_text_ns("ReadLength", DENI_PREFIX).or_else(|| m.child_text("ReadLength")) {
slave.mbx_read_length = v.parse().unwrap_or(0);
}
}
let dc = ds.find_child_ns("DC", DENI_PREFIX).or_else(|| ds.find_child("DC"));
if let Some(dc_el) = dc {
if let Some(v) = dc_el.child_text_ns("HasDC", DENI_PREFIX).or_else(|| dc_el.child_text("HasDC")) {
slave.has_dc = parse_bool_str(v);
}
if let Some(v) = dc_el.child_text_ns("Sync0CycleNs", DENI_PREFIX).or_else(|| dc_el.child_text("Sync0CycleNs")) {
slave.sync0_cycle_ns = v.parse().unwrap_or(0);
}
if let Some(v) = dc_el.child_text_ns("Sync1CycleNs", DENI_PREFIX).or_else(|| dc_el.child_text("Sync1CycleNs")) {
slave.sync1_cycle_ns = v.parse().unwrap_or(0);
}
if let Some(v) = dc_el.child_text_ns("ShiftNs", DENI_PREFIX).or_else(|| dc_el.child_text("ShiftNs")) {
slave.shift_ns = v.parse().unwrap_or(0);
}
}
if dc.is_none() {
if let Some(v) = ds.child_text_ns("HasDC", DENI_PREFIX).or_else(|| ds.child_text("HasDC")) {
slave.has_dc = parse_bool_str(v);
}
}
let wd = ds.find_child_ns("Watchdog", DENI_PREFIX).or_else(|| ds.find_child("Watchdog"));
if let Some(w) = wd {
if let Some(v) = w.child_text_ns("WdDivider", DENI_PREFIX).or_else(|| w.child_text("WdDivider")) {
slave.wd_divider = v.parse().unwrap_or(0);
}
if let Some(v) = w.child_text_ns("WdTimePdi", DENI_PREFIX).or_else(|| w.child_text("WdTimePdi")) {
slave.wd_time_pdi = v.parse().unwrap_or(0);
}
if let Some(v) = w.child_text_ns("WdTimePd", DENI_PREFIX).or_else(|| w.child_text("WdTimePd")) {
slave.wd_time_pd = v.parse().unwrap_or(0);
}
}
let sm_list = ds.find_child_ns("SyncManagers", DENI_PREFIX).or_else(|| ds.find_child("SyncManagers"));
if let Some(sml) = sm_list {
for sm_el in sml.children.iter().filter(|c| c.local_name.eq_ignore_ascii_case("SM")) {
let mut sm = SyncManagerConfig::default();
if let Some(v) = sm_el.attr("index") {
sm.index = v.parse().unwrap_or(0);
}
if let Some(v) = sm_el.child_text_ns("StartAddr", DENI_PREFIX).or_else(|| sm_el.child_text("StartAddr")) {
sm.start_addr = parse_u16_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = sm_el.child_text_ns("Length", DENI_PREFIX).or_else(|| sm_el.child_text("Length")) {
sm.length = v.parse().unwrap_or(0);
}
if let Some(v) = sm_el.child_text_ns("Flags", DENI_PREFIX).or_else(|| sm_el.child_text("Flags")) {
sm.flags = parse_uint_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = sm_el.child_text_ns("Type", DENI_PREFIX).or_else(|| sm_el.child_text("Type")) {
sm.sm_type = v.parse().unwrap_or(0);
}
slave.sync_managers.push(sm);
}
}
let pdo_cfg = ds.find_child_ns("PDOConfig", DENI_PREFIX).or_else(|| ds.find_child("PDOConfig"));
if let Some(pc) = pdo_cfg {
if let Some(v) = pc.child_text_ns("WriteAssignment", DENI_PREFIX).or_else(|| pc.child_text("WriteAssignment")) {
slave.should_write_pdo_assignment = parse_bool_str(v);
}
if let Some(v) = pc.child_text_ns("WriteConfiguration", DENI_PREFIX).or_else(|| pc.child_text("WriteConfiguration")) {
slave.should_write_pdo_configuration = parse_bool_str(v);
}
if let Some(v) = pc.child_text_ns("CompleteAccess", DENI_PREFIX).or_else(|| pc.child_text("CompleteAccess")) {
slave.supports_complete_access = parse_bool_str(v);
}
}
let dev_info = ds.find_child_ns("DeviceInfo", DENI_PREFIX).or_else(|| ds.find_child("DeviceInfo"));
if let Some(di) = dev_info {
if let Some(v) = di.child_text_ns("HasMDP", DENI_PREFIX).or_else(|| di.child_text("HasMDP")) {
slave.has_mdp = parse_bool_str(v);
}
if let Some(v) = di.child_text_ns("BlockLRW", DENI_PREFIX).or_else(|| di.child_text("BlockLRW")) {
slave.block_lrw = parse_bool_str(v);
}
if let Some(v) = di.child_text_ns("Dtype", DENI_PREFIX).or_else(|| di.child_text("Dtype")) {
slave.dtype = v.parse().unwrap_or(0);
}
if let Some(v) = di.child_text_ns("Topology", DENI_PREFIX).or_else(|| di.child_text("Topology")) {
slave.topology = v.parse().unwrap_or(0);
}
if let Some(v) = di.child_text_ns("ActivePorts", DENI_PREFIX).or_else(|| di.child_text("ActivePorts")) {
slave.active_ports = v.parse().unwrap_or(0);
}
if let Some(v) = di.child_text_ns("SupportsFrameRepeat", DENI_PREFIX).or_else(|| di.child_text("SupportsFrameRepeat")) {
slave.supports_frame_repeat = parse_bool_str(v);
}
} else {
if let Some(v) = ds.child_text_ns("HasMDP", DENI_PREFIX).or_else(|| ds.child_text("HasMDP")) {
slave.has_mdp = parse_bool_str(v);
}
}
let protocols = ds.find_child_ns("Protocols", DENI_PREFIX).or_else(|| ds.find_child("Protocols"));
if let Some(p) = protocols {
if let Some(v) = p.child_text_ns("CoE", DENI_PREFIX).or_else(|| p.child_text("CoE")) {
slave.has_coe = parse_bool_str(v);
}
if let Some(v) = p.child_text_ns("FoE", DENI_PREFIX).or_else(|| p.child_text("FoE")) {
slave.has_foe = parse_bool_str(v);
}
if let Some(v) = p.child_text_ns("EoE", DENI_PREFIX).or_else(|| p.child_text("EoE")) {
slave.has_eoe = parse_bool_str(v);
}
if let Some(v) = p.child_text_ns("SoE", DENI_PREFIX).or_else(|| p.child_text("SoE")) {
slave.has_soe = parse_bool_str(v);
}
if let Some(v) = p.child_text_ns("AoE", DENI_PREFIX).or_else(|| p.child_text("AoE")) {
slave.has_aoe = parse_bool_str(v);
}
if let Some(v) = p.child_text_ns("VoE", DENI_PREFIX).or_else(|| p.child_text("VoE")) {
slave.has_voe = parse_bool_str(v);
}
}
let pdo_mappings = ds.find_child_ns("PDOMappings", DENI_PREFIX).or_else(|| ds.find_child("PDOMappings"));
if let Some(pm) = pdo_mappings {
for pdo_el in &pm.children {
let is_input = pdo_el.local_name == "TxPdo";
let target = if is_input { &mut slave.input_pdos } else { &mut slave.output_pdos };
let deni_pdo_idx = pdo_el.attr("index")
.and_then(parse_u16_hex_or_dec)
.unwrap_or(0);
for entry_el in pdo_el.find_children("Entry") {
let mut entry = PdoEntryConfig::default();
entry.pdo_index = deni_pdo_idx;
if let Some(v) = entry_el.attr("index") {
entry.index = parse_u16_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = entry_el.attr("subIndex") {
entry.sub_index = parse_u8_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = entry_el.attr("bitLength") {
entry.bit_length = v.parse().unwrap_or(0);
}
entry.name = entry_el.attr("name").unwrap_or("").to_string();
entry.data_type = entry_el.attr("dataType").unwrap_or("").to_string();
target.push(entry);
}
}
}
let startup_params = ds.find_child_ns("StartupParameters", DENI_PREFIX)
.or_else(|| ds.find_child("StartupParameters"));
if let Some(sp) = startup_params {
for param_el in sp.children.iter().filter(|c| c.local_name.eq_ignore_ascii_case("StartupParam")) {
let mut sdo = StartupSdoConfig::default();
if let Some(v) = param_el.attr("Index") {
sdo.index = parse_u16_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = param_el.attr("SubIndex") {
sdo.sub_index = parse_u8_hex_or_dec(v).unwrap_or(0);
}
if let Some(v) = param_el.attr("Data") {
sdo.data = hex_string_to_bytes(v).unwrap_or_default();
}
if let Some(v) = param_el.attr("Transition") {
sdo.transition = v.to_string();
}
if let Some(v) = param_el.attr("CompleteAccess") {
sdo.complete_access = parse_bool_str(v);
}
sdo.comment = param_el.attr("Comment").unwrap_or("").to_string();
slave.sdo_init.push(sdo);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_uint_hex_or_dec() {
assert_eq!(parse_uint_hex_or_dec("0x1234"), Some(0x1234));
assert_eq!(parse_uint_hex_or_dec("#x00001234"), Some(0x1234));
assert_eq!(parse_uint_hex_or_dec("1000"), Some(1000));
assert_eq!(parse_uint_hex_or_dec(""), None);
}
#[test]
fn test_hex_string_round_trip() {
let data = vec![0x01, 0x02, 0xAB, 0xCD];
let hex = bytes_to_hex_string(&data);
assert_eq!(hex, "0102abcd");
let back = hex_string_to_bytes(&hex).unwrap();
assert_eq!(back, data);
}
}