How to implement a server where modbus data is changed by another thread, something like a global variable
gustavowd opened this issue · 6 comments
Hi,
In the tokio-modbus example the input registers are generated on the call function, as a local variable.
fn call(&self, req: Self::Request) -> Self::Future {
match req {
Request::ReadInputRegisters(_addr, cnt) => {
let mut registers = vec![0; cnt.into()];
registers[0] = 77;
future::ready(Ok(Response::ReadInputRegisters(registers)))
}
_ => unimplemented!(),
}
}
However, in a real implementation the registers data can be changed in another program flow. How can I declare the registers in a way that i can change the registers content in another functions?
Thanks in advance,
Gustavo
Maybe the best solution is a context with holding registers, input registers, etc in the Server struct:
pub struct Server {
socket_addr: SocketAddr,
}
I was able to create a rtu client to access a struct that can be used by another tokio thread like that:
#[tokio::main]
async fn main() -> Result<(), Box> {
let socket_addr = "127.0.0.1:8443".parse().unwrap();
let uart_addr = "/dev/ttyACM0";
let models: Arc<Mutex<Models>> = Arc::new(Mutex::new(Models::new()));
let models_tmp = Arc::clone(&models);
tokio::spawn(Box::pin(async move {
client_context(uart_addr, models_tmp).await;
}));
let models_tmp_srv = Arc::clone(&models);
tokio::select! {
_ = server_context(socket_addr, models_tmp_srv) => unreachable!(),
}
}
async fn client_context(uart_addr: &str, _models: Arc<tokio::sync::Mutex>) {
use tokio_serial::SerialStream;
let tty_path = uart_addr;
let slave = Slave(0x03);
let builder = tokio_serial::new(tty_path, 115200);
let builder = builder.parity(tokio_serial::Parity::Even);
let port = SerialStream::open(&builder).unwrap();
let mut ctx = rtu::connect_slave(port, slave).await.unwrap();
loop {
println!("Reading a sensor value");
let rsp = ctx.read_holding_registers(32001, 2).await.unwrap();
println!("Sensor value is: {rsp:?}");
tokio::time::sleep(Duration::from_secs(1)).await;
println!("Reading a sensor value");
let rsp = ctx.read_holding_registers(32262, 2).await.unwrap();
println!("Sensor value is: {rsp:?}");
tokio::time::sleep(Duration::from_secs(1)).await;
}
}
However, I can´t send the same shared struct to the server. Any ideas of how to do that?
This question seems to be unrelated to tokio-modbus
and refers to how you share (mutable) data in a multi-threaded environment in Rust. The are many options with different trade-offs. As a fallback you could always share it as a Arc<Mutex<_>>
, I don't see any reason why this should be impossible with the current design.
Please re-open with a concrete use case and example if the library imposes any insurmountable restrictions.
Let me elaborate on the problem a little better. I already used in several situations the Arc<Mutex<_>>. However, to use this solution in the current design would require an additional parameter in the fn call(&self, req: Self::Request) -> Self::Future function of the trait service. The problem is that the call function is not async. The mutex for tokio async functions cannot be used in a non-async function. This makes trick to manage shared resources with another async tasks. The best solution with the current library design is to use a static variable with OneCell for example, which is not the best solution in rust. A device with modbus communication usually associates relevant information to registers that are usually calculated in other program flows. For example, an inverter will have information on voltages, currents, power, usually calculated in another program flow. The library example of generating the request data locally in the call function is not a real modbus use case. That's my point. Maybe I still have little knowledge in rust, but I know communication protocols well and especially modbus. I believe it would be important to have a way to access registers and other modbus objects in the Call function and not generate them locally. For now i am using a static variable with OneCell to manage the concurrency. For now I have implemented a gateway with a RTU client and a tcp Server, using a static variable to share data between threads.
An implementation of the Service
trait could hold every possible state it needs, accessible by &self
. Sorry, but I don't get it.
Now I got It. Thanks for the clear answer.