#twin-cat #ads #beckhoff #wrapper #system #bindgen #communication

bin+lib twincatads-rs

Rust 对 Beckhoff TwinCAT 提供的 TwinCAT ADS 库的包装

22 个版本 (4 个破坏性版本)

0.5.1 2024年8月15日
0.5.0 2024年5月21日
0.4.6 2024年5月17日
0.4.0 2024年4月27日
0.1.0 2022年3月31日

#56 in 硬件支持

Download history 560/week @ 2024-04-29 223/week @ 2024-05-13 170/week @ 2024-05-20 1/week @ 2024-06-03 2/week @ 2024-07-01 94/week @ 2024-07-29 115/week @ 2024-08-12

每月下载 209

MIT 许可证

195KB
2.5K SLoC

twincatads-rs

Beckhoff 提供的 TwinCAT ADS 库的 Rust 包装器。包括一个方便的客户端类,使得连接到目标变得非常简单。从目标上传符号和数据类型信息,以便轻松读取和写入结构和数组。从 0.3.2 版本开始,我们切换到 Tokio 通道进行安全、异步通信和易于集成到其他项目中。

查看 src/bin/commtest.rs 以获取完整的使用示例。请注意,commtest 一直在变化,以在不同的应用程序中进行测试。

描述

当尝试通过 ADS 连接到已安装并运行的 TwinCAT XAR 或 XAE 系统时,连接将失败。在这种情况下,您需要使用 TcAdsDll 进行连接。此 crate 使用 Bindgen 创建包装器。现在应该支持32位和64位构建。

已知限制

功能块 (FB) 和带引用的结构

包含引用变量的功能块和结构无法正确解析。这些 FB 和 Struct 中的个别项可以解析,但不能作为整体解析。

读取完整结构是 twincatads-rs 的设计目标之一。然而,当前尚未实现完整功能块的反序列化功能,也不是当前的目标。

字符串

使用 read_symbol_value 读取未声明为 T_MaxString 的字符串目前不受支持。请使用 read_symbol_string_value 代替。

使用 write_symbol_value 编写字符串也有限制。write_symbol_value 需要一个类型

    if let Err(err) = client.write_symbol_value::<MaxString>(
        "GM.sTarget",
        MaxString::from_string("I'm not even supposed to be here today.")
    ) {
        println!("An error occurred writing <MaxString> for: {}", err);
    }

简单地使用 write_symbol_string_value 更为清晰。

    if let Err(err) = client.write_symbol_string_value(
        "GM.sTarget",
        "There is no spoon."
    ) {
        println!("An error occurred writing the tag: {}", err);
    }

固定字符串是可能的

fixed_str: [u8; 32],

结构

read_symbol_valuewrite_symbol_value 需要一个只有固定宽度成员的已知类型,因此不包含任何动态类型,如 StringVec。如果结构是预定义的,则可以使用 read_symbol_valuewrite_symbol_value

示例

use zerocopy::{AsBytes, FromBytes};

#[derive(FromBytes, AsBytes, Default, Debug)]
#[repr(C)]
struct MyStruct {
    field1: u32,
    field2: u64,
}

...

let st = MyStruct {
    field1 : 123,
    field2: 456
};

if let Err(err) = client.write_symbol_value::<MyStruct> ("GVL.myStructVariable", &st) {
    println!("You blew it!");
}

然而,在运行时才知道符号或类型的动态程序并不总是有预先定义的静态类型的便利。相反,这些值存储在 mechutil::VariantValue 实例中。注册在数据更改通知上的结构符号将自动解析为 VariantValue::Object。要写入存储在 VariantValue::Object 中的结构,请使用 write_symbol_variant_value

VariantValue::Object 必须与 PLC 的结构完全相同。目标中的结构不一定与本地客户端相同地进行字节对齐;adsclient 处理调整数据对齐。

先决条件

对于构建

  • 需要运行在 32 位或 64 位 Microsoft Windows 上的 TwinCAT XAE。无论如何,你都不会在其他情况下使用这个库。
    • 我们使用版本 3.1.4024.XX
    • TwinCAT XAE 可从 beckhoff.com 免费下载。
  • Bindgen 需要 LLVM。请参阅下面的 "构建"。

对于运行

  • TwinCAT XAE、XAR 或甚至只是 ADS 路由器(TC1000)都足够了。

安装

  • 将依赖项添加到 Cargo.toml

twincatads-rs= "0.3"

构建

Bindgen 绑定需要 LLVM。

在 Bindgen 的早期版本中,我们需要专门安装 LLVM。您可以从这里下载最新的 LLVM:[https://releases.llvm.org/download.html](https://releases.llvm.org/download.html)

在发布页面上找到 LLVM-XX.XX.XX-win64.exe。在构建之前,请确保已设置 LIBCLANG_PATH。

示例: SET LIBCLANG_PATH=c:\Program Files\LLVM\bin

然而,我们并不需要与 Bindgen 更新版本的最新版本,尽管 Bindgen 的文档仍然列出此要求。我们的建议是按需安装。

  • 使用 cargo 构建

构建与您的机器的默认目标匹配(通常是 64 位)

cargobuild

具体构建 64 位版本: cargo build --target x86_64-pc-windows-msvc

构建 32 位版本

cargobuild --目标i686-pc-windows-msvc

演示

有关更完整的示例,请参阅 src/bin/commtest.rs。

使用推荐的客户端库,包括通知通道。

    let (mut client, rx) = AdsClient::new();
    // Supply AMS Address. If not set, localhost is used.
    client.set_address("192.168.127.1.1.1");
    
    // Supply ADS port. If not set, the default of 851 is used.
    // You should generally use the default.
    client.set_port(851);
     
    // Make the connection to the ADS router
    client.initialize();
    
    // Write a value to a symbol in the PLC
    if let Err(err) = client.write_symbol_string_value(
        "GM.sTarget",
        "There is no spoon."
    ) {
        println!("An error occurred writing the tag: {}", err);
    }

    match client.read_symbol_value::<MaxString>("GM.sEcho") {
        Ok(val) => info!("STRING VALUE: {:?}", val.to_string()),
        Err(err) => error!("I failed to read the string tag: {}", err)
    }

    match client.read_symbol_value::<MaxString>("GK.sReadTest") {
        Ok(val) => info!("STRING VALUE: {:?}", val.to_string()),
        Err(err) => error!("I failed to read the string tag: {}", err)
    }    
    
    match client.read_symbol_string_value("GM.sEcho") {
        Ok(val) => info!("STRING VALUE: {:?}", val.to_string()),
        Err(err) => error!("I failed to read the string tag: {}", err)
    }    
    

    const NOTIFY_TAG :&str = "GM.fReal";
    
    if let Err(err) = client.register_symbol(NOTIFY_TAG) {
        error!("Failed to register symbol: {}", err);
    }
    else {
        let mut line = String::new();


        // On the parent context (e.g., main thread), listen for notifications
        thread::spawn(move || {

            //
            // When you call .iter() on a Receiver, you get an iterator that blocks waiting for new messages, 
            // and it continues until the channel is closed.
            //
            for notification in rx.iter() {
                println!("Notification received: {} : {}", notification.name, notification.value);
                // Handle the notification...
            }
        });


        std::io::stdin().read_line(&mut line).expect("Failed to read line");

        // if let Err(err) = client.unregister_symbol(NOTIFY_TAG) {
        //     error!("Failed to unregister symbol: {} ", err);
        // }
    }
    


    // Make sure the ADS client is closed.
    client.finalize();
        

直接使用 Beckhoff API 的简单示例。

use std::{time};
use twincatads_rs::*;




pub fn hello_ads() {
    println!("Hello, ADS!");

    unsafe {
        let val = AdsGetDllVersion();

        println!("The ADS Dll version is {}", val);

        let client_port = AdsPortOpen();

        println!("The ADS client port is {}", client_port);


        let mut local_addr = AmsAddr::new();

        AdsGetLocalAddress(&mut local_addr);

        println!("local addr is {}.{}.{}.{}.{}.{}", 
            local_addr.netId.b[0], 
            local_addr.netId.b[1], 
            local_addr.netId.b[2], 
            local_addr.netId.b[3], 
            local_addr.netId.b[4], 
            local_addr.netId.b[5]
        );

        std::thread::sleep(time::Duration::from_millis(1000));


        AdsPortClose();

        println!("Goodbye...");

    }
}

许可证

MIT

本授权协议据此授予任何获得本软件及其相关文档副本(“软件”)的人无条件的、免费的权限,以使用、复制、修改、合并、发布、分发、再许可和/或出售软件副本,并允许向软件提供方提供软件的人这样做,但受以下条件的约束

上述版权声明和本许可协议应包含在软件的所有副本或主要部分中。

免责声明

我和我的公司与 Beckhoff 没有关系。

软件按“原样”提供,不提供任何形式的保证,无论是明示的、暗示的,还是关于适销性、特定用途的适用性或非侵权的保证。在任何情况下,作者或版权所有者均不对任何索赔、损害或其他责任负责,无论此类索赔、损害或其他责任是基于合同、侵权或其他方式,并由此产生、产生于或与软件或其使用或其他方式有关。

依赖关系

~8-19MB
~251K SLoC