提交 4f8f3e2c authored 作者: Serhij S's avatar Serhij S

PLC example

上级 cd2d0f00
......@@ -8,6 +8,13 @@ description = "Kit for PLCs and real-time micro-services"
repository = "https://github.com/eva-ics/roboplc"
keywords = ["realtime", "robots", "plc", "industrial"]
readme = "README.md"
autoexamples = false
[package.metadata.docs.rs]
features = []
[package.metadata.playground]
features = []
[dependencies]
binrw = "0.13.3"
......@@ -20,12 +27,20 @@ oneshot = { version = "0.1.6", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
pin-project = "1.1.5"
rmodbus = { version = "0.9.2" }
roboplc-derive = { version = "0.1.0" }
roboplc-derive = { version = "0.1.1" }
serde = { version = "1.0.197", features = ["derive", "rc"] }
serial = "0.4.0"
sysinfo = "0.30.6"
thiserror = "1.0.57"
[dev-dependencies]
env_logger = "0.11.3"
insta = "1.36.1"
log = "0.4.21"
tokio = { version = "1.36.0", features = ["rt", "macros", "time"] }
tracing = { version = "0.1.40", features = ["log"] }
[[example]]
name = "plc-modbus"
path = "examples/plc-modbus.rs"
use roboplc::{
comm::{tcp, Client},
time::interval,
};
use roboplc::{io::modbus::prelude::*, prelude::*};
use tracing::{error, info};
const MODBUS_TIMEOUT: Duration = Duration::from_secs(1);
// Do not make any decision if the sensor is older than this
const ENV_DATA_TTL: Duration = Duration::from_millis(1);
// A shared traditional PLC context
#[derive(Default)]
struct Variables {
temperature: f32,
}
// A structure, fetched from Modbus registers
#[derive(Clone, Debug)]
#[binrw]
struct EnvironmentSensors {
temperature: f32,
}
// A structue, to be written to Modbus registers
#[binrw]
struct Relay1 {
fan1: u8,
fan2: u8,
}
// Controller message type
#[derive(Clone, DataPolicy, Debug)]
enum Message {
#[data_delivery(single)]
#[data_expires(TtlCell::is_expired)]
SensorData(TtlCell<EnvironmentSensors>),
}
// First worker, to pull data from Modbus
#[derive(WorkerOpts)]
#[worker_opts(name = "puller", cpu = 1, scheduling = "fifo", priority = 80)]
struct ModbusPuller1 {
sensor_mapping: ModbusMapping,
}
impl ModbusPuller1 {
fn create(modbus_client: &Client) -> Result<Self, Box<dyn std::error::Error>> {
let sensor_mapping = ModbusMapping::create(modbus_client, 2, "h0", 2)?;
Ok(Self { sensor_mapping })
}
}
// A worker implementation, contains a single function to run which has got access to the
// controller context
impl Worker<Message, Variables> for ModbusPuller1 {
fn run(&mut self, context: &Context<Message, Variables>) {
let hc = context
.hub()
.register(self.worker_name(), event_matches!(Message::SensorData(_)))
.unwrap();
for _ in interval(Duration::from_millis(500)) {
match self.sensor_mapping.read::<EnvironmentSensors>() {
Ok(v) => {
context.variables().lock().temperature = v.temperature;
hc.send(Message::SensorData(TtlCell::new_with_value(
ENV_DATA_TTL,
v,
)));
}
Err(e) => {
error!(worker=self.worker_name(), err=%e, "Modbus pull error");
}
}
}
}
}
// Second worker, to control relays
#[derive(WorkerOpts)]
#[worker_opts(name = "relays", cpu = 2, scheduling = "fifo", priority = 80)]
struct ModbusRelays1 {
fan_mapping: ModbusMapping,
}
impl ModbusRelays1 {
fn create(modbus_client: &Client) -> Result<Self, Box<dyn std::error::Error>> {
let fan_mapping = ModbusMapping::create(modbus_client, 3, "c2", 2)?;
Ok(Self { fan_mapping })
}
}
impl Worker<Message, Variables> for ModbusRelays1 {
fn run(&mut self, context: &Context<Message, Variables>) {
let hc = context
.hub()
.register(self.worker_name(), event_matches!(Message::SensorData(_)))
.unwrap();
while let Ok(msg) = hc.recv() {
match msg {
Message::SensorData(mut cell) => {
if let Some(s) = cell.take() {
info!(worker=self.worker_name(), value=%s.temperature,
elapsed=?cell.set_at().elapsed());
let relay = if s.temperature > 30.0 {
Some(Relay1 { fan1: 1, fan2: 1 })
} else if s.temperature < 25.0 {
Some(Relay1 { fan1: 0, fan2: 0 })
} else {
None
};
if let Some(r) = relay {
if let Err(e) = self.fan_mapping.write(&r) {
error!(worker=self.worker_name(), err=%e, "Modbus send error");
}
}
}
}
}
}
}
}
// Main function, to start the controller and workers
fn main() -> Result<(), Box<dyn std::error::Error>> {
roboplc::thread_rt::set_simulated();
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();
let modbus_tcp_client = tcp::connect("10.90.34.111:5505", MODBUS_TIMEOUT)?;
let mut controller: Controller<Message, Variables> = Controller::new();
let worker = ModbusPuller1::create(&modbus_tcp_client)?;
controller.spawn_worker(worker)?;
let worker = ModbusRelays1::create(&modbus_tcp_client)?;
controller.spawn_worker(worker)?;
controller.block_while_online();
Ok(())
}
......@@ -14,9 +14,11 @@ use crate::{
DataDeliveryPolicy, Error,
};
use parking_lot::Mutex;
pub use roboplc_derive::WorkerOpts;
pub mod prelude {
pub use super::{Context, Controller, Worker, WorkerOptions};
pub use roboplc_derive::WorkerOpts;
}
const SLEEP_SLEEP: Duration = Duration::from_millis(100);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论