Skip to content

Commit

Permalink
added support for one-shot mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin Huang committed Jan 11, 2022
1 parent 7bce96f commit 81cd82f
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 42 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "yams"
description = "Yet Another Modbus Simulator"
version = "0.7.0"
version = "0.8.0"
authors = ["Justin Huang <[email protected]>"]
license = "MIT/Apache-2.0"
readme = "README.md"
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ simulator works before running the simulator, including:

## Quick Demo:

[DEMO](https://asciinema.org/a/453058)
[DEMO](https://asciinema.org/a/453058) (slightly outdated, but good enough as a demo :))

## Configurable Items
See [YAML based Configurations](yaml.based.configurations.md)
Expand All @@ -45,7 +45,7 @@ See [YAML based Configurations](yaml.based.configurations.md)
- [x] implement support for repeated request(s)
- [x] implement support for delay before request(s)
- [x] implement error handling
- [ ] implement one-shot mode without config files
- [x] implement one-shot mode without config files
- [x] implement verbose mode to print out more detail/data
- [x] implement request/response counts printout
- [ ] implement all data support of all current functions, with tests
Expand Down
3 changes: 3 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub async fn start_modbus_client(
println!("failed in reading request file {}", request_file.display());
}
}
if let Some(r) = request.request {
rlist.push(r);
}

let mut section_repeat_times = request.repeat_times.or_else(|| Some(1)).unwrap();
#[allow(unused_parens)]
Expand Down
128 changes: 121 additions & 7 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,94 @@ use std::{fs, net::SocketAddr, path::PathBuf};
use tokio_serial::{SerialPort, SerialStream};

#[derive(Parser, Debug)]
#[clap(version = "0.7", author = "Justin Huang <[email protected]>")]
#[clap(version = "0.8", author = "Justin Huang <[email protected]>")]
pub struct Opts {
/// Sets the simulator type: Client or Server
#[clap(arg_enum, short, long, required_unless_present("config-file"))]
device_type: Option<DeviceType>,
// TODO: complete the list of manual options
/// Sets the configuration file to load
#[clap(short, long, required_unless_present_all(&["device-type"]))]
config_file: Option<String>,

/// Verbose mode
#[clap(short, long)]
pub verbose_mode: bool,

/// the modbus protocol type
#[clap(arg_enum, short('p'), long, required_unless_present("config-file"))]
pub protocol_type: Option<ProtocolType>,
/// the device type (only 'client' is supported in one-shot mode)
#[clap(arg_enum, short('t'), long, required_unless_present("config-file"))]
pub device_type: Option<DeviceType>,
/// the id of the client/server
#[clap(short('i'), long, required_unless_present("config-file"))]
pub device_id: Option<u8>,
/// the socket address when using Modbus TCP
#[clap(short('a'), long, required_if_eq("protocol-type", "tcp"))]
pub ip_address: Option<SocketAddr>,
/// the serial port when using Modbus RTU
#[clap(short('s'), long, required_if_eq("protocol-type", "rtu"))]
pub serial_port: Option<String>,
/// the baudrate when using Modbus RTU
#[clap(short('b'), long, required_if_eq("protocol-type", "rtu"))]
pub serial_baudrate: Option<u32>,
/// the parity of the serial port
#[clap(arg_enum, short('r'), long, required_if_eq("protocol-type", "rtu"))]
pub serial_parity: Option<ParityType>,
/// the stop bits of the serial port
#[clap(arg_enum, short('o'), long, required_if_eq("protocol-type", "rtu"))]
pub serial_stop_bits: Option<StopBitsType>,
/// the data bits of the serial port
#[clap(arg_enum, short('d'), long, required_if_eq("protocol-type", "rtu"))]
pub serial_data_bits: Option<DataBitsType>,
/// the function code to use in one-shot mode
#[clap(arg_enum, short('f'), long, required_unless_present("config-file"))]
pub function_code: Option<FunctionCode>,
/// the start register address to use in one-shot mode
#[clap(short('e'), long, required_if_eq_any(&[("function-code", "write-single-register"),
("function-code", "write-multiple-registers"),
("function-code", "write-single-coil"),
("function-code", "write-multiple-coils"),
("function-code", "read-coils"),
("function-code", "read-discrete-inputs"),
("function-code", "read-holding-registers"),
("function-code", "read-input-registers"),
("function-code", "write-multiple-coils")]))]
pub start_address: Option<u16>,
/// the quantity-of-registers-to-access to use in one-shot mode
#[clap(short('q'), long, required_if_eq_any(&[("function-code", "write-single-register"),
("function-code", "write-multiple-registers"),
("function-code", "write-single-coil"),
("function-code", "write-multiple-coils"),
("function-code", "read-coils"),
("function-code", "read-discrete-inputs"),
("function-code", "read-holding-registers"),
("function-code", "read-input-registers"),
("function-code", "write-multiple-coils")]))]
pub quantity: Option<u16>,
/// the new values to set in one-shot mode
#[clap(short('n'), long, required_if_eq_any(&[("function-code", "write-single-register"),
("function-code", "write-multiple-registers"),
("function-code", "write-single-coil"),
("function-code", "write-multiple-coils")]))]
pub new_values: Option<Vec<String>>,
/// the times to repeat the request in one-shot mode
#[clap(short('g'), long, required_unless_present("config-file"))]
pub repeat_times: Option<u16>,
/// the time (number of 100ms) to delay the request in one-shot mode
#[clap(short('y'), long, required_unless_present("config-file"))]
pub delay: Option<u64>,
/// the data type used in one-shot mode
#[clap(arg_enum, short('j'), long, required_if_eq_any(&[("function-code", "write-single-register"),
("function-code", "write-multiple-registers")]))]
pub data_type: Option<DataType>,
/// the server id used in one-shot mode
#[clap(short('k'), long, required_if_eq("device-type", "client"))]
pub server_id: Option<u8>,
/// the server address used in one-shot mode
#[clap(short('l'), long, required_if_eq_any(&[("device-type", "client"),
("protocol-type", "tcp")]))]
pub server_address: Option<SocketAddr>,
}


#[derive(Debug, Deserialize)]
pub struct ModbusRequest {
/// description of the request
Expand Down Expand Up @@ -75,6 +148,8 @@ pub struct ModbusClientRequest {
pub repeat_times: Option<u16>,
/// request files
pub request_files: Vec<PathBuf>,
/// a single request
pub request: Option<ModbusRequest>,
}

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -115,14 +190,53 @@ fn parse_config_str(config_str: &str) -> anyhow::Result<ModbusDeviceConfig> {
serde_yaml::from_str(&config_str).with_context(|| format!("failed to parse the config string"))
}

pub fn configure(opts: &Opts) -> anyhow::Result<ModbusDeviceConfig> {
pub fn configure(opts: &mut Opts) -> anyhow::Result<ModbusDeviceConfig> {
if let Some(config_file) = &opts.config_file {
match fs::read_to_string(config_file) {
Ok(config_str) => parse_config_str(&config_str),
Err(e) => Err(e.into()),
}
} else {
todo!()
if opts.device_type == Some(DeviceType::Client) {
Ok(ModbusDeviceConfig {
common: ModbusCommonConfig {
protocol_type: opts.protocol_type.unwrap(),
device_type: DeviceType::Client,
device_id: opts.device_id.unwrap(),
ip_address: opts.ip_address,
serial_baudrate: opts.serial_baudrate,
serial_data_bits: opts.serial_data_bits,
serial_stop_bits: opts.serial_stop_bits,
serial_parity: opts.serial_parity,
serial_port: opts.serial_port.take(),
},
server: None,
client: Some(ModbusClientConfig {
requests: vec![ ModbusClientRequest {
server_id: opts.server_id,
server_address: opts.server_address,
repeat_times: None,
request_files: vec![],
request: Some(ModbusRequest {
description: "".to_string(),
function_code: opts.function_code.unwrap(),
access_start_address: opts.start_address.unwrap(),
access_quantity: opts.quantity.unwrap(),
new_values: opts.new_values.take(),
repeat_times: opts.repeat_times,
delay: opts.delay,
data_type: opts.data_type,
}),
}],
register_data: None,
}),
verbose_mode: opts.verbose_mode,
})
} else {
println!("server is not supported in one-shot mode");
std::process::exit(1);
}

}
}

Expand Down
28 changes: 16 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,22 @@ use crate::{client::*, config::*, server::*, types::*};

#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
let opts = Opts::parse();
let mut config = configure(&opts).expect("failed to load the configuraion");

config.verbose_mode = opts.verbose_mode;
if config.common.device_type == DeviceType::Server {
if let Err(e) = start_modbus_server(config).await {
println!("exit with error: {}", e);
}
} else {
if let Err(e) = start_modbus_client(config).await {
println!("exit with error: {}", e);
}
let mut opts = Opts::parse();
match configure(&mut opts) {
Ok(mut config) => {
config.verbose_mode = opts.verbose_mode;
if config.common.device_type == DeviceType::Server {
if let Err(e) = start_modbus_server(config).await {
println!("exit with error: {}", e);
}
} else {
if let Err(e) = start_modbus_client(config).await {
println!("exit with error: {}", e);
}
}
},
Err(e) => println!("failed with error: {}", e),
}

Ok(())
}
4 changes: 2 additions & 2 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ pub enum DeviceType {
Server,
}

#[derive(ArgEnum, Clone, PartialEq, Debug, Deserialize)]
#[derive(ArgEnum, Clone, Copy, PartialEq, Debug, Deserialize)]
pub enum ProtocolType {
RTU,
TCP,
}

#[derive(ArgEnum, Clone, PartialEq, Debug, Deserialize)]
#[derive(ArgEnum, Clone, Copy, PartialEq, Debug, Deserialize)]
pub enum DataType {
Float32,
Float64,
Expand Down
36 changes: 18 additions & 18 deletions test/modbus.tcp.client.normal.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
device_id: 1
client:
requests: [
#{
# server_id: 3,
# server_address: 127.0.0.1:5502,
# repeat_times: 2,
# request_files: [
# test/requests/read.multiple.request.yaml,
# test/requests/read.pi.request.yaml,
# test/requests/read.golden.ratio.request.yaml,
# ]
#},
{
server_id: 3,
server_address: 127.0.0.1:5502,
repeat_times: 2,
request_files: [
test/requests/read.multiple.request.yaml,
test/requests/read.pi.request.yaml,
test/requests/read.golden.ratio.request.yaml,
]
},
{
server_id: 2,
server_address: 127.0.0.1:5503,
Expand All @@ -23,16 +23,16 @@
test/requests/write.device.id.request.yaml,
test/requests/read.device.id.request.yaml,
test/requests/read.flowrate.request.yaml,
#test/requests/read.resettable.totalizer.request.yaml,
#test/requests/read.serial.number.request.yaml,
#test/requests/read.led.power.state.coil.request.yaml,
#test/requests/read.serial.number.lowest.4.bits.coil.request.yaml,
test/requests/read.resettable.totalizer.request.yaml,
test/requests/read.serial.number.request.yaml,
test/requests/read.led.power.state.coil.request.yaml,
test/requests/read.serial.number.lowest.4.bits.coil.request.yaml,
test/requests/write.flowrate.request.yaml,
#test/requests/write.led.power.state.coil.request.yaml,
#test/requests/write.serial.number.lowest.4.bits.coil.request.yaml,
test/requests/write.led.power.state.coil.request.yaml,
test/requests/write.serial.number.lowest.4.bits.coil.request.yaml,
test/requests/read.flowrate.request.yaml,
#test/requests/read.serial.number.request.yaml,
#test/requests/read.led.power.state.coil.request.yaml,
test/requests/read.serial.number.request.yaml,
test/requests/read.led.power.state.coil.request.yaml,
]
},
]
Expand Down

0 comments on commit 81cd82f

Please sign in to comment.