Skip to content

Commit f7bb119

Browse files
committed
wip modbus test tool
1 parent b826212 commit f7bb119

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

src/bin/modbus_client.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use clap::Parser;
2+
use std::error::Error;
3+
use tokio::time::Duration;
4+
use tokio_modbus::prelude::*;
5+
6+
/// Modbus client for reading input registers from a photoacoustic analyzer
7+
#[derive(Parser, Debug)]
8+
#[clap(author, version, about)]
9+
struct Args {
10+
/// Modbus server address
11+
#[clap(long, default_value = "127.0.0.1")]
12+
address: String,
13+
14+
/// Modbus server port
15+
#[clap(long, default_value = "502")]
16+
port: u16,
17+
18+
/// Starting input register address
19+
#[clap(long, default_value = "0")]
20+
input_register: u16,
21+
22+
/// Number of registers to read
23+
#[clap(long, default_value = "6")]
24+
quantity: u16,
25+
}
26+
27+
#[tokio::main]
28+
async fn main() -> Result<(), Box<dyn Error>> {
29+
// Initialize logging
30+
env_logger::init_from_env(
31+
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
32+
);
33+
34+
// Parse command line arguments
35+
let args = Args::parse();
36+
37+
// Format server address
38+
let socket_addr = format!("{}:{}", args.address, args.port);
39+
println!("Connecting to Modbus server at {}", socket_addr);
40+
41+
// Create TCP transport
42+
let mut ctx = tcp::connect_slave(&socket_addr, Slave(1)).await?;
43+
44+
// Set request timeout
45+
ctx.set_timeout(Duration::from_secs(1));
46+
47+
// Read input registers
48+
println!(
49+
"Reading {} input registers starting at address {}",
50+
args.quantity, args.input_register
51+
);
52+
let response = ctx
53+
.read_input_registers(args.input_register, args.quantity)
54+
.await?;
55+
56+
// Display raw results
57+
println!("Raw register values: {:?}", response);
58+
59+
// Display formatted results based on our register map
60+
// This matches the register map defined in your modbus_server.rs
61+
for (i, value) in response.iter().enumerate() {
62+
let register = args.input_register + i as u16;
63+
match register {
64+
0 => println!(
65+
"Register 0: Resonance Frequency = {:.1} Hz",
66+
*value as f32 / 10.0
67+
),
68+
1 => println!(
69+
"Register 1: Signal Amplitude = {:.3}",
70+
*value as f32 / 1000.0
71+
),
72+
2 => println!(
73+
"Register 2: Water Vapor Concentration = {:.1} ppm",
74+
*value as f32 / 10.0
75+
),
76+
3 => println!("Register 3: Timestamp Low Word = {}", value),
77+
4 => println!("Register 4: Timestamp High Word = {}", value),
78+
5 => {
79+
let status = match value {
80+
0 => "Normal",
81+
1 => "Warning",
82+
2 => "Error",
83+
_ => "Unknown",
84+
};
85+
println!("Register 5: Status Code = {} ({})", value, status);
86+
}
87+
_ => println!("Register {}: Value = {}", register, value),
88+
}
89+
}
90+
91+
// If we read both timestamp registers, compute the full timestamp
92+
if args.input_register <= 3 && args.input_register + args.quantity > 4 {
93+
let low_word_idx = 3 - args.input_register;
94+
let high_word_idx = 4 - args.input_register;
95+
96+
if low_word_idx < response.len() as u16 && high_word_idx < response.len() as u16 {
97+
let low_word = response[low_word_idx as usize] as u32;
98+
let high_word = response[high_word_idx as usize] as u32;
99+
let timestamp = (high_word << 16) | low_word;
100+
101+
// Format timestamp as human-readable date/time
102+
let datetime = chrono::NaiveDateTime::from_timestamp_opt(timestamp as i64, 0)
103+
.unwrap_or_else(|| chrono::NaiveDateTime::from_timestamp_opt(0, 0).unwrap());
104+
println!("Full Timestamp: {} ({})", timestamp, datetime);
105+
}
106+
}
107+
108+
Ok(())
109+
}

0 commit comments

Comments
 (0)