|
| 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