Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

example: tcp server additions for read and write, error handling #163

Merged
merged 3 commits into from
Mar 12, 2023

Conversation

cdbennett
Copy link
Contributor

Attempting to expand the TCP server example by adding support for holding register read/write support, plus demonstrating error handling on both client and server side.

Need help to figure out how to send specific Modbus exception responses on the server side, and how to identify the exception code on the client side. For instance, in this example we test accessing a register address that is not supported by the server, so the server should respond with the Modbus exception code IllegalDataAddress.

Copy link
Member

@uklotzde uklotzde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Very helpful addition for getting started. And it also revealed some gaps.

Only some minor comments.

examples/tcp-server.rs Show resolved Hide resolved
examples/tcp-server.rs Outdated Show resolved Hide resolved
@ladvoc
Copy link

ladvoc commented Mar 11, 2023

This updated example for the TCP server has clarified many questions I had. However, one question I still have is how to store an Arc cloned from somewhere else when the service is created. For instance, is it possible to clone an existing Arc<Mutex<HashMap<u16, u16>> to store in the input_registers property when the Service is created? The reason for doing something like this is to enable the contents of the hash map to be fetched from some external source on another thread. Based on the requirements for NewService, I cannot figure out a way to accomplish this. I am relatively new to Rust, so I apologize if there is something obvious I have overlooked.

@gustavowd
Copy link
Contributor

gustavowd commented Mar 11, 2023

Insted o call the new() function of the Service, you can create the data objects in the main function and clone it to your threads.
For example:

#[tokio::main]
async fn main() -> Result<(), Box> {
let socket_addr = "127.0.0.1:5502".parse().unwrap();

// Insert some test data as register values.
let mut input_registers = HashMap::new();
input_registers.insert(0, 1234);
input_registers.insert(1, 5678);
let mut holding_registers = HashMap::new();
holding_registers.insert(0, 10);
holding_registers.insert(1, 20);
holding_registers.insert(2, 30);
holding_registers.insert(3, 40);
let ir = Arc::new(Mutex::new(input_registers));
let hr = Arc::new(Mutex::new(holding_registers));

tokio::select! {
    _ = server_context(socket_addr,Arc::clone(&ir), Arc::clone(&hr)) => unreachable!(),
    _ = another_thread_context(Arc::clone(&ir)) => println!("Exiting"),
}

Ok(())

}

async fn server_context(socket_addr: SocketAddr, ir: Arc<Mutex<HashMap<u16, u16>>>, hr: Arc<Mutex<HashMap<u16, u16>>>) -> anyhow::Result<()> {
println!("Starting up server on {socket_addr}");
let listener = tokio_modbus::server::tls::listener(socket_addr, 1).unwrap();
let server = Server::new(listener);
let new_service = |_socket_addr| Some(Service {input_registers: Arc::clone(&ir), holding_registers:Arc::clone(&hr)});
let on_process_error = |err| {
eprintln!("{err}");
};
server.serve(&new_service, on_process_error).await?;
Ok(())
}

fn another_thread_context( ir: Arc<Mutex<HashMap<u16, u16>>>) {
...

Or implement a new() function of the Service that receive the data instead of create inside the new function.

@gustavowd
Copy link
Contributor

gustavowd commented Mar 11, 2023

I think this library is great, just lacking some better examples and documentation. It will came with time. I made myself an example where holding registers are structures with several different data such floating point and strings which uses a from trair to convert the scructs to Vec. This structs generate data in different threads using something like I suggest previouslly.

@gustavowd
Copy link
Contributor

How to implement service to create the registers HashMap in the main function:

impl Service {
fn new(ir: Arc<Mutex<HashMap<u16, u16>>>, hr: Arc<Mutex<HashMap<u16, u16>>>) -> Self {
Self {
input_registers: ir,
holding_registers: hr,
}
}
}

@uklotzde uklotzde merged commit ea45e55 into slowtec:main Mar 12, 2023
@cdbennett cdbennett deleted the server-example branch March 13, 2023 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants