RoboPLC I/O connector for IEC 60870-5
# Introduction
[IEC 60870-5](https://en.wikipedia.org/wiki/IEC_60870-5) is a set of standards
for telecontrol, teleprotection, and associated telecommunications for electric
power systems, widely used in the European Union, the United Kingdom and other
locations.
This crate provides I/O connector for [RoboPLC](https://www.roboplc.com/).
The crate IS NOT FREE for any commercial or production use. Please refer to
for
more information.
The 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.
# Example
## IEC 60870-5 101 (Serial)
The crate does not provide any client for IEC 60870-5 101 (Serial) as such does
not require re-connection or a special telegram processing logic. Any
communication library plus [IEC 60870-5](https://crates.io/crates/iec60870-5)
crate can be used to create/parse IEC 60870-5 101 telegrams.
## IEC 60870-5 104 (TCP)
### Connecting a client
```rust,no_run
use roboplc::comm::Timeouts;
use roboplc_io_iec60870_5::iec104::{Client, PingKind};
use std::time::Duration;
// Create a new IEC 60870-5 104 client
let (client, reader) = Client::new("192.168.1.100:2404", Timeouts::default(), 1024).unwrap();
// Get the telegram receiver
let telegram_rx = reader.get_telegram_receiver();
// The reader must be run in a separate thread
std::thread::spawn(move || reader.run());
// Create an optional pinger worker, which can send test/ack frames or
// automatically re-connect the socket
let pinger = client.pinger(PingKind::Test, Duration::from_secs(1));
std::thread::spawn(move || pinger.run());
```
### Handling incoming telegrams
```rust,ignore
use iec60870_5::{
telegram104::{Telegram104, Telegram104_I},
types::{datatype::{DataType, M_EP_TA_1}, COT},
};
use roboplc::prelude::*;
use std::time::Duration;
while let Ok(telegram) = telegram_rx.recv() {
println!("{:?}", telegram);
if let Telegram104::I(i) = telegram {
if i.data_type() == DataType::M_EP_TA_1 && i.cot() == COT::Cyclic {
for iou in i.iou() {
let v: M_EP_TA_1 = iou.value().into();
let dt = Timestamp::try_from(v.time)
.unwrap()
.try_into_datetime_local()
.unwrap();
dbg!(v.sep.es, Duration::from(v.elapsed), dt);
}
}
}
}
```
### Sending telegrams
The client provides two methods to send telegrams:
- [`iec104::Client::send`] for sending a telegram with no reply expected
- [`iec104::Client::command`] for sending a command telegram and waiting for a reply
```rust,ignore
use iec60870_5::{
telegram104::{Telegram104, Telegram104_I},
types::{datatype::{DataType, SelectExecute, C_RC_TA_1, QU, RCO, RCS}, COT},
};
use roboplc::prelude::*;
let mut telegram: Telegram104_I = Telegram104_I::new(DataType::C_RC_TA_1, COT::Act, 47);
telegram.append_iou(
12,
C_RC_TA_1 {
rco: RCO {
rcs: RCS::Increment,
se: SelectExecute::Execute,
qu: QU::Persistent,
},
time: Timestamp::now().try_into().unwrap(),
},
);
let request: Telegram104 = telegram.into();
let reply = client.command(request).unwrap();
println!("COMMAND REPLY: {:?}", reply);
```
## Locking policy
The crate locking policy is set using the same features as RoboPLC locking
policy. The selected policy must be the same.