darra-ethercat-master 2.1.0

商业 EtherCAT 主站协议栈 · 实时内核驱动 · 抖动 1µs · Windows + Linux · 多编程语言 · 全协议 · 支持复杂拓扑 + 热插拔 · ethercat.darra.xyz · Commercial EtherCAT Master protocol stack · Real-time kernel driver · 1µs jitter · Multi-platform · Multi-language · Complex topology + hot-plug.
//! 辅助工具函数
//!
//! 对应 C# Utils/Help.cs
//! 提供字节数组到字符串的安全转换 (EtherCAT 设备字符串多编码兜底).

/// 将字节数组转换为字符串 (EtherCAT 设备字符串多编码兜底)
///
/// 对应 C# Help.ConvertByteArrayToString (2026-05-08 修复版).
///
/// EtherCAT SII / SDO 字符串协议规定是 ASCII / Latin-1, 不会用 GBK/GB2312.
/// 旧顺序 {UTF-8, GBK, GB2312, UTF-16LE, ASCII} 有严重 bug:
///   GBK/GB2312 几乎能解码任意字节序列 (从不抛错), 但会把 Latin-1 字节
///   解读成 "看似 Chinese" 的乱码 → UI 显示乱码.
///
/// 新优先级 (严格优先, 最后用 Latin-1 永不失败兜底):
///   1. UTF-8 严格 (合法 UTF-8 才接受) — 现代从站
///   2. ASCII 严格 (byte ≤ 127 才接受) — 最常见
///   3. ISO-8859-1 (Latin-1) — 兜底, 1:1 映射 byte → U+00xx,
///      支持厂商名带 ™ © ± 等扩展字符. 永远不返回乱码.
///
/// GBK / GB2312 / UTF-16LE 完全移除 — EtherCAT 规范不用.
pub fn convert_byte_array_to_string(data: &[u8]) -> String {
    if data.is_empty() {
        return String::new();
    }

    // 1. 查找 null 终止符
    let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
    let valid = &data[..end];
    if valid.is_empty() {
        return String::new();
    }

    decode_ethercat_string(valid)
}

/// 解码 EtherCAT 设备字符串 (UTF-8 严格 → ASCII 严格 → Latin-1 1:1 兜底)
///
/// 调用前请先剥掉 trailing NUL, 此函数对入参不再做 NUL 检测.
/// 永远不返回 Err — Latin-1 兜底 1:1 映射 byte → U+00xx, 总能解码.
///
/// 用于读取 EtherCAT SII / SDO / SoE / OD names / group_name / device_name 等
/// 设备字符串字段. 替换原本对这些字段使用的 `String::from_utf8_lossy` /
/// `from_utf8` 调用 (后者无法保留 Latin-1 扩展字符 ™ © ± 等).
pub fn decode_ethercat_string(data: &[u8]) -> String {
    if data.is_empty() {
        return String::new();
    }

    // 1. UTF-8 严格 (含非法 byte 直接拒绝)
    if let Ok(s) = std::str::from_utf8(data) {
        if has_meaningful_content(s) {
            return clean_string(s);
        }
    }

    // 2. ASCII 严格 (任何 byte > 127 直接拒绝)
    if data.iter().all(|&b| b < 128) {
        // 安全: 全 ASCII 必为合法 UTF-8
        let s = std::str::from_utf8(data).unwrap_or("");
        if has_meaningful_content(s) {
            return clean_string(s);
        }
    }

    // 3. Latin-1 (ISO-8859-1) 永不失败 — 1:1 byte → U+00xx 映射
    let latin1: String = data.iter().map(|&b| b as char).collect();
    clean_string(&latin1)
}

/// 判断字符串是否有 "有意义的内容" (不全是控制字符).
/// 对齐 C# Help.HasMeaningfulContent.
fn has_meaningful_content(s: &str) -> bool {
    if s.is_empty() {
        return false;
    }
    s.chars().any(|c| !c.is_control() || c == ' ')
}

/// 清理字符串: 去尾部 NUL/CR/LF/TAB, 去前导控制字符 (保留空格).
/// 对齐 C# Help.CleanString.
fn clean_string(s: &str) -> String {
    // 去尾部 NUL/CR/LF/TAB
    let trimmed_end = s.trim_end_matches(|c: char| c == '\0' || c == '\r' || c == '\n' || c == '\t');
    // 去前导控制字符 (保留空格)
    let result = trimmed_end.trim_start_matches(|c: char| c.is_control() && c != ' ');
    result.to_string()
}

/// 将固定长度字节数组转换为字符串
///
/// 对应 C# Help.ConvertByteArrayToString(FixedName)
pub fn convert_fixed_name_to_string(bytes: &[u8; 41]) -> String {
    convert_byte_array_to_string(bytes)
}

/// 格式化字节数组为十六进制显示字符串 (大写, 空格分隔)
///
/// 例如: [0x01, 0xAB, 0xFF] → "01 AB FF"
/// 注意: xml 模块中的 bytes_to_hex_string 是紧凑格式 (小写, 无分隔)
pub fn bytes_to_hex_display(data: &[u8]) -> String {
    data.iter()
        .map(|b| format!("{:02X}", b))
        .collect::<Vec<_>>()
        .join(" ")
}

/// 格式化 IP 地址
///
/// 例如: [192, 168, 1, 1] → "192.168.1.1"
pub fn ip_to_string(ip: &[u8; 4]) -> String {
    format!("{}.{}.{}.{}", ip[0], ip[1], ip[2], ip[3])
}