use roboplc::locking::Mutex;
use roboplc::{
comm::{tcp, Client},
time::interval,
};
use roboplc::{io::modbus::prelude::*, prelude::*};
use rtsc::cell::TtlCell;
use tracing::{error, info, warn};
const MODBUS_TIMEOUT: Duration = Duration::from_secs(1);
const ENV_DATA_TTL: Duration = Duration::from_millis(1);
type Variables = Mutex<VariablesData>;
#[derive(Default)]
struct VariablesData {
temperature: f32,
}
#[derive(Clone, Debug)]
#[binrw]
struct EnvironmentSensors {
temperature: f32,
}
#[binrw]
struct Relay1 {
fan1: u8,
fan2: u8,
}
#[derive(Clone, DataPolicy, Debug)]
enum Message {
#[data_delivery(single)]
#[data_expires(TtlCell::is_expired)]
EnvSensorData(TtlCell<EnvironmentSensors>),
}
#[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 })
}
}
impl Worker<Message, Variables> for ModbusPuller1 {
fn run(&mut self, context: &Context<Message, Variables>) -> WResult {
let hub = context.hub();
for _ in interval(Duration::from_millis(500)).take_while(|_| context.is_online()) {
match self.sensor_mapping.read::<EnvironmentSensors>() {
Ok(v) => {
context.variables().lock().temperature = v.temperature;
hub.send(Message::EnvSensorData(TtlCell::new_with_value(
ENV_DATA_TTL,
v,
)));
}
Err(e) => {
error!(worker=self.worker_name(), err=%e, "Modbus pull error");
}
}
}
Ok(())
}
}
#[derive(WorkerOpts)]
#[worker_opts(
name = "relays",
cpu = 2,
scheduling = "fifo",
priority = 80,
blocking = true
)]
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>) -> WResult {
let hc = context
.hub()
.register(
self.worker_name(),
event_matches!(Message::EnvSensorData(_)),
)
.unwrap();
for msg in hc {
match msg {
Message::EnvSensorData(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");
}
}
} else {
warn!(worker=self.worker_name(), elapsed=?cell.set_at().elapsed(), "Expired data");
}
}
}
}
Ok(())
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
roboplc::setup_panic();
roboplc::configure_logger(roboplc::LevelFilter::Info);
roboplc::set_simulated();
let mut controller: Controller<Message, Variables> = Controller::new();
let modbus_tcp_client = tcp::connect("10.90.34.111:5505", MODBUS_TIMEOUT)?;
let worker = ModbusPuller1::create(&modbus_tcp_client)?;
controller.spawn_worker(worker)?;
let worker = ModbusRelays1::create(&modbus_tcp_client)?;
controller.spawn_worker(worker)?;
controller.register_signals(Duration::from_secs(5))?;
controller.block();
Ok(())
}