#ads #plc #real-time #industrial #twincat

roboplc-io-ads

RoboPLC I/O connector for TwinCAT/ADS

2 stable releases

1.0.1 Apr 26, 2024

#222 in Hardware support

Download history 164/week @ 2024-04-26

164 downloads per month

Custom license

160KB
3.5K SLoC

RoboPLC I/O connector for TwinCAT/ADS crates.io page docs.rs page

Introduction

ADS is the native protocol used by programmable logic controllers (PLCs) and the TwinCAT automation system produced by Beckhoff GmbH.

The ADS specification can be found on Beckhoff Information System pages.

This crate provides I/O connector for RoboPLC.

The crate IS NOT FREE for any commercial or production use. Please refer to https://github.com/roboplc/roboplc-io-ads/blob/main/LICENSE.md for more information.

Example

RoboPLC I/O mapping:

use ads::client::Client;
use roboplc::{comm::Timeouts, io::IoMapping, prelude::binrw};
use roboplc_io_ads as ads;
use std::time::Duration;

#[binrw]
struct MyStruct {
    field1: u32,
    field2: f32,
    field3: [u8; 8],
    field4: f64
}

// Open a connection to an ADS device identified by hostname/IP and port.
// For TwinCAT devices, a route must be set to allow the client to connect.
// The source AMS address is automatically generated from the local IP,
// but can be explicitly specified as the third argument.

// The socket is automatically reconnected if the connection is lost.
let (client, reader) = Client::new(("plchost", ads::PORT),
    Timeouts::new(Duration::from_secs(1)),
    ads::Source::Auto).unwrap();

// The reader thread MUST be started manually. Apply real-time settings if needed.
std::thread::spawn(move || { reader.run(); });

// Specify the target ADS device to talk to, by NetID and AMS port.
// Port 851 usually refers to the first PLC instance.
let device = client.device(ads::AmsAddr::new([5, 32, 116, 5, 1, 1].into(), 851));

// Create a mapping for a symbol. The mapping contains a handle (automatically recreated on
// each reconnect) as well as a pre-allocated buffer.
let mut mapping = device.mapping("MY_SYMBOL", 24);

// Read a structure from the PLC.
let mut data: MyStruct = mapping.read().unwrap();
data.field1 += 1;
// Write the modified structure back to the PLC.
mapping.write(&data).unwrap();

Example

Direct usage:

use ads::client::Client;
use roboplc::comm::Timeouts;
use roboplc_io_ads as ads;
use std::time::Duration;

let (client, reader) = Client::new(("plchost", ads::PORT),
    Timeouts::new(Duration::from_secs(1)),
    ads::Source::Auto).unwrap();

std::thread::spawn(move || { reader.run(); });

let device = client.device(ads::AmsAddr::new([5, 32, 116, 5, 1, 1].into(), 851));

// Ensure that the PLC instance is running.
assert!(device.get_state().unwrap().0 == ads::AdsState::Run);

// Request a handle to a named symbol in the PLC instance.
let handle = ads::Handle::new(&device, "MY_SYMBOL").unwrap();

// Read data in form of an u32 from the handle.
let value: u32 = handle.read_value().unwrap();
println!("MY_SYMBOL value is {}", value);

The API slightly differs from the free version as many methods have been rewritten to less-panic and thread-safe code. Certain internal types have been replaced with RoboPLC defaults.

The crate code is based on https://github.com/birkenfeld/ads-rs project, (c) Georg Brandl, Serhij Symonenko and other contributors.

The commercial client additionally supports:

  • Auto-reconnects
  • Multi-threading
  • Real-time safety
  • Enterprise support from the vendor

Note: as the client has got an asynchronous-manner reader loop, it is HIGHLY RECOMMENDED to use timeouts. In case if a remote does not respond, a request with no timeout gets stuck forever.

Dependencies

~9–41MB
~588K SLoC