use std::fs::File;
use std::io::{self, BufWriter, Write};
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
fn generate_vrcode() -> String {
let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyz0123456789".chars().collect();
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let mut code = String::with_capacity(9);
let mut rng = seed;
for _ in 0..9 {
rng = rng.wrapping_mul(1664525).wrapping_add(1013904223);
let index = (rng as usize) % chars.len();
code.push(chars[index]);
}
code
}
#[derive(Clone)]
struct AppConfig {
savedir: Option<String>,
play_sound: bool,
cut_by: Option<String>,
pt_waiting_time: Option<u32>,
cut_file: Option<bool>,
cut_hourly: Option<bool>,
record_when_start: bool,
send_web: bool,
vrcode: String,
server_ip: Option<String>,
show_dsa: bool,
}
impl Default for AppConfig {
fn default() -> Self {
AppConfig {
savedir: None,
play_sound: false,
cut_by: None,
pt_waiting_time: None,
cut_file: None,
cut_hourly: None,
record_when_start: false,
send_web: false,
vrcode: generate_vrcode(),
server_ip: None,
show_dsa: true,
}
}
}
#[derive(Clone)]
struct Device {
name: String,
device_type: String,
port: Option<String>,
readonly: bool,
wavs: Option<String>,
waveonly: Option<bool>,
bedname: Option<String>,
event: Option<bool>,
skip: Option<bool>,
}
#[derive(Clone)]
struct Bed {
name: String,
devices: Vec<Device>,
}
struct VRConfig {
app_config: AppConfig,
beds: Vec<Bed>,
}
fn main() {
println!("╔══════════════════════════════════════════════╗");
println!("║ Vital Recorder Configuration Generator ║");
println!("╚══════════════════════════════════════════════╝");
println!();
let mut config = VRConfig {
app_config: AppConfig::default(),
beds: Vec::new(),
};
loop {
println!("\n═══ Main Menu ═══");
println!("1. Configure Application Settings");
println!("2. Configure Beds and Devices");
println!("3. Generate Configuration File");
println!("4. Exit");
print!("\nSelect option: ");
io::stdout().flush().unwrap();
let choice = read_line();
match choice.trim() {
"1" => configure_app_settings(&mut config.app_config),
"2" => configure_beds(&mut config.beds),
"3" => generate_config(&config),
"4" => {
println!("Exiting...");
break;
}
_ => println!("Invalid option! Please try again."),
}
}
}
fn configure_app_settings(config: &mut AppConfig) {
loop {
println!("\n═══ Application Settings ═══");
println!("1. Set Save Directory (current: {:?})", config.savedir);
println!("2. Toggle Play Sound (current: {})", config.play_sound);
println!("3. Configure File Cutting");
println!(
"4. Set Record on Start (current: {})",
config.record_when_start
);
println!("5. Configure Network Settings");
println!("6. Toggle Show DSA/ECG/EEG (current: {})", config.show_dsa);
println!("7. Back to Main Menu");
print!("\nSelect option: ");
io::stdout().flush().unwrap();
let choice = read_line();
match choice.trim() {
"1" => {
print!("Enter save directory path: ");
io::stdout().flush().unwrap();
let path = read_line();
config.savedir = Some(path.trim().to_string());
}
"2" => {
config.play_sound = !config.play_sound;
println!("Play sound set to: {}", config.play_sound);
}
"3" => configure_file_cutting(config),
"4" => {
config.record_when_start = !config.record_when_start;
println!("Record on start set to: {}", config.record_when_start);
}
"5" => configure_network(config),
"6" => {
config.show_dsa = !config.show_dsa;
println!("Show DSA set to: {}", config.show_dsa);
}
"7" => break,
_ => println!("Invalid option!"),
}
}
}
fn configure_file_cutting(config: &mut AppConfig) {
println!("\n═══ File Cutting Configuration ═══");
println!("1. Cut by metric (HR/SpO2)");
println!("2. Cut hourly");
println!("3. No cutting");
print!("\nSelect option: ");
io::stdout().flush().unwrap();
let choice = read_line();
match choice.trim() {
"1" => {
config.cut_file = None;
config.cut_hourly = None;
print!("Cut by (hr/spo2/both - leave empty for both): ");
io::stdout().flush().unwrap();
let metric = read_line();
if !metric.trim().is_empty() {
config.cut_by = Some(metric.trim().to_string());
}
print!("Patient waiting time (1-30): ");
io::stdout().flush().unwrap();
let time = read_line();
if let Ok(t) = time.trim().parse::<u32>() {
if t >= 1 && t <= 30 {
config.pt_waiting_time = Some(t);
}
}
}
"2" => {
config.cut_file = Some(false);
config.cut_hourly = Some(true);
config.cut_by = None;
config.pt_waiting_time = None;
}
"3" => {
config.cut_file = None;
config.cut_hourly = None;
config.cut_by = None;
config.pt_waiting_time = None;
}
_ => println!("Invalid option!"),
}
}
fn configure_network(config: &mut AppConfig) {
println!("\n═══ Network Configuration ═══");
print!("Enable WebSocket sending? (y/n): ");
io::stdout().flush().unwrap();
let choice = read_line();
config.send_web = choice.trim().to_lowercase() == "y";
print!("VR Code (9 chars, default: {}): ", config.vrcode);
io::stdout().flush().unwrap();
let code = read_line();
if !code.trim().is_empty() {
config.vrcode = code.trim().to_string();
}
print!("Server IP (format: IPv4:PORT): ");
io::stdout().flush().unwrap();
let ip = read_line();
if !ip.trim().is_empty() {
config.server_ip = Some(ip.trim().to_string());
}
}
fn configure_beds(beds: &mut Vec<Bed>) {
loop {
println!("\n═══ Bed Configuration ═══");
println!("Current beds: {}", beds.len());
for (i, bed) in beds.iter().enumerate() {
println!(" {}. {} ({} devices)", i + 1, bed.name, bed.devices.len());
}
println!("\n1. Add New Bed");
println!("2. Configure Existing Bed");
println!("3. Remove Bed");
println!("4. Back to Main Menu");
print!("\nSelect option: ");
io::stdout().flush().unwrap();
let choice = read_line();
match choice.trim() {
"1" => add_bed(beds),
"2" => {
if !beds.is_empty() {
print!("Enter bed number to configure (1-{}): ", beds.len());
io::stdout().flush().unwrap();
let num = read_line();
if let Ok(n) = num.trim().parse::<usize>() {
if n > 0 && n <= beds.len() {
configure_bed_devices(&mut beds[n - 1]);
}
}
} else {
println!("No beds configured!");
}
}
"3" => {
if !beds.is_empty() {
print!("Enter bed number to remove (1-{}): ", beds.len());
io::stdout().flush().unwrap();
let num = read_line();
if let Ok(n) = num.trim().parse::<usize>() {
if n > 0 && n <= beds.len() {
beds.remove(n - 1);
println!("Bed removed!");
}
}
} else {
println!("No beds to remove!");
}
}
"4" => break,
_ => println!("Invalid option!"),
}
}
}
fn add_bed(beds: &mut Vec<Bed>) {
let bed_num = beds.len() + 1;
let bed_name = format!("BED_{:02}", bed_num);
beds.push(Bed {
name: bed_name.clone(),
devices: Vec::new(),
});
println!("Added bed: {}", bed_name);
}
fn configure_bed_devices(bed: &mut Bed) {
loop {
println!("\n═══ Devices for {} ═══", bed.name);
for (i, device) in bed.devices.iter().enumerate() {
println!(" {}. {} ({})", i + 1, device.name, device.device_type);
}
println!("\n1. Add Demo Device");
println!("2. Add Patient Monitor");
println!("3. Add Anesthesia Machine");
println!("4. Remove Device");
println!("5. Back");
print!("\nSelect option: ");
io::stdout().flush().unwrap();
let choice = read_line();
match choice.trim() {
"1" => add_demo_device(bed),
"2" => add_patient_monitor(bed),
"3" => add_anesthesia_machine(bed),
"4" => {
if !bed.devices.is_empty() {
print!("Enter device number to remove (1-{}): ", bed.devices.len());
io::stdout().flush().unwrap();
let num = read_line();
if let Ok(n) = num.trim().parse::<usize>() {
if n > 0 && n <= bed.devices.len() {
bed.devices.remove(n - 1);
println!("Device removed!");
}
}
}
}
"5" => break,
_ => println!("Invalid option!"),
}
}
}
fn add_demo_device(bed: &mut Bed) {
let device = Device {
name: "Demo".to_string(),
device_type: "Demo".to_string(),
port: None,
readonly: false,
wavs: None,
waveonly: None,
bedname: None,
event: Some(true),
skip: Some(false),
};
bed.devices.push(device);
println!("Demo device added!");
}
fn add_patient_monitor(bed: &mut Bed) {
println!("\n═══ Select Patient Monitor Type ═══");
let monitors = vec![
("1", "Intellivue", "Phillips Intellivue"),
("2", "VueLink", "Phillips VueLink"),
("3", "Solar8000", "GE Solar8000"),
("4", "Bx50", "GE Bx50"),
("5", "B1x5M", "GE B1x5M"),
("6", "Canvas", "GE Canvas"),
("7", "Dashx000", "GE Dashx000"),
("8", "Dash2500", "GE Dash2500"),
("9", "Infinity", "Draeger Infinity"),
("10", "BSM", "Nihon Kohden BSM"),
("11", "EGA", "Nihon Kohden EGA"),
("12", "ADT", "Nihon Kohden ADT"),
("13", "NealTime", "Nihon Kohden NealTime"),
("14", "ORF", "Nihon Kohden ORF"),
("15", "MEKICS", "MEKICS"),
];
for (num, _, desc) in &monitors {
println!("{}. {}", num, desc);
}
print!("\nSelect monitor type: ");
io::stdout().flush().unwrap();
let choice = read_line();
let monitor_type = monitors
.iter()
.find(|(num, _, _)| num == &choice.trim())
.map(|(_, type_name, _)| type_name.to_string());
if let Some(device_type) = monitor_type {
print!("Enter COM port (e.g., COM5 on Windows, /dev/ttyUSB0 on Unix): ");
io::stdout().flush().unwrap();
let port = read_line();
print!("Use readonly mode? (y/n): ");
io::stdout().flush().unwrap();
let readonly = read_line().trim().to_lowercase() == "y";
let mut device = Device {
name: device_type.clone(),
device_type: device_type.clone(),
port: Some(port.trim().to_string()),
readonly,
wavs: None,
waveonly: None,
bedname: None,
event: None,
skip: None,
};
if matches!(device_type.as_str(), "Intellivue" | "VueLink") {
print!("Configure waveforms? (y/n): ");
io::stdout().flush().unwrap();
if read_line().trim().to_lowercase() == "y" {
println!("Available waveforms:");
println!("ECG (500Hz): I,II,III,V,AVR,AVL,AVF,MCL,MCL1,V1-V6");
println!("125Hz: PLETH,ABP,ART,CVP,FAP,PAP,ICP,EEG");
println!("62.5Hz: CO2,RESP,AWF,AWP");
print!("Enter waveforms (comma-separated): ");
io::stdout().flush().unwrap();
let wavs = read_line();
device.wavs = Some(wavs.trim().to_string());
}
}
if matches!(device_type.as_str(), "Bx50" | "B1x5M" | "Canvas") {
print!("Use waveonly mode? (y/n): ");
io::stdout().flush().unwrap();
device.waveonly = Some(read_line().trim().to_lowercase() == "y");
print!("Configure waveforms? (y/n): ");
io::stdout().flush().unwrap();
if read_line().trim().to_lowercase() == "y" {
println!("Available: ECG1,ECG2 (300Hz), IABP1-6 (100Hz)");
print!("Enter waveforms: ");
io::stdout().flush().unwrap();
device.wavs = Some(read_line().trim().to_string());
}
}
if device_type == "ADT" {
print!("Enter bed name (e.g., BED-001): ");
io::stdout().flush().unwrap();
device.bedname = Some(read_line().trim().to_string());
}
bed.devices.push(device);
println!("Monitor added!");
} else {
println!("Invalid selection!");
}
}
fn add_anesthesia_machine(bed: &mut Bed) {
println!("\n═══ Select Anesthesia Machine Type ═══");
println!("1. Draeger Primus");
println!("2. Draeger Medibus X");
println!("3. GE Datex-Ohmeda");
print!("\nSelect machine type: ");
io::stdout().flush().unwrap();
let choice = read_line();
let machine_type = match choice.trim() {
"1" => Some("Primus"),
"2" => Some("MedibusX"),
"3" => Some("Datex-Ohmeda"),
_ => None,
};
if let Some(device_type) = machine_type {
print!("Enter COM port: ");
io::stdout().flush().unwrap();
let port = read_line();
print!("Use readonly mode? (y/n): ");
io::stdout().flush().unwrap();
let readonly = read_line().trim().to_lowercase() == "y";
let mut device = Device {
name: device_type.to_string(),
device_type: device_type.to_string(),
port: Some(port.trim().to_string()),
readonly,
wavs: None,
waveonly: None,
bedname: None,
event: None,
skip: None,
};
if device_type == "Datex-Ohmeda" {
device.wavs = Some("pfvc".to_string());
}
bed.devices.push(device);
println!("Anesthesia machine added!");
} else {
println!("Invalid selection!");
}
}
fn generate_config(config: &VRConfig) {
println!("\n═══ Output Options ═══");
println!("1. Save to current directory");
println!("2. Save to custom path");
print!("\nSelect option: ");
io::stdout().flush().unwrap();
let choice = read_line();
let output_path = match choice.trim() {
"1" => {
print!("Enter filename (default: vr.conf): ");
io::stdout().flush().unwrap();
let filename = read_line();
if filename.trim().is_empty() {
"vr.conf".to_string()
} else {
filename.trim().to_string()
}
}
"2" => {
print!("Enter full path with filename (e.g., /home/user/configs/vr.conf): ");
io::stdout().flush().unwrap();
let path = read_line();
let path = path.trim();
if path.is_empty() {
println!("Invalid path! Using default: vr.conf");
"vr.conf".to_string()
} else {
if path.ends_with('/') || path.ends_with('\\') {
format!("{}vr.conf", path)
} else if !path.contains('.') {
#[cfg(windows)]
let sep = '\\';
#[cfg(not(windows))]
let sep = '/';
format!("{}{}vr.conf", path, sep)
} else {
path.to_string()
}
}
}
_ => {
println!("Invalid option! Using default: vr.conf");
"vr.conf".to_string()
}
};
if let Some(parent) = Path::new(&output_path).parent() {
if !parent.exists() {
print!("Directory doesn't exist. Create it? (y/n): ");
io::stdout().flush().unwrap();
let create = read_line();
if create.trim().to_lowercase() == "y" {
if let Err(e) = std::fs::create_dir_all(parent) {
println!("✗ Error creating directory: {}", e);
return;
}
} else {
println!("✗ Operation cancelled");
return;
}
}
}
match write_config_file(&output_path, config) {
Ok(_) => {
let abs_path = std::fs::canonicalize(&output_path)
.unwrap_or_else(|_| Path::new(&output_path).to_path_buf());
println!("\n✓ Configuration file generated successfully!");
println!(" Location: {}", abs_path.display());
}
Err(e) => println!("\n✗ Error writing file: {}", e),
}
}
fn write_config_file(filename: &str, config: &VRConfig) -> io::Result<()> {
let file = File::create(filename)?;
let mut writer = BufWriter::new(file);
if let Some(ref dir) = config.app_config.savedir {
writeln!(writer, "SAVEDIR={}\r", dir)?;
}
writeln!(
writer,
"PLAY_SOUND={}\r",
if config.app_config.play_sound { 1 } else { 0 }
)?;
writeln!(writer, "\r")?;
if config.app_config.cut_hourly.is_some() {
writeln!(writer, "CUT_FILE=0\r")?;
writeln!(writer, "CUT_HOURLY=1\r")?;
} else {
if let Some(ref cut_by) = config.app_config.cut_by {
writeln!(writer, "CUT_BY={}\r", cut_by)?;
}
if let Some(time) = config.app_config.pt_waiting_time {
writeln!(writer, "PT_WAITING_TIME={}\r", time)?;
}
}
writeln!(writer, "\r")?;
writeln!(
writer,
"RECORD_WHEN_START={}\r",
if config.app_config.record_when_start {
1
} else {
0
}
)?;
writeln!(writer, "\r")?;
writeln!(
writer,
"SEND_WEB={}\r",
if config.app_config.send_web { 1 } else { 0 }
)?;
writeln!(writer, "VRCODE={}\r", config.app_config.vrcode)?;
if let Some(ref ip) = config.app_config.server_ip {
writeln!(writer, "SERVER_IP={}\r", ip)?;
}
writeln!(writer, "\r")?;
writeln!(
writer,
"SHOW_DSA={}\r",
if config.app_config.show_dsa { 1 } else { 0 }
)?;
writeln!(writer, "\r")?;
writeln!(writer, "----------\r")?;
writeln!(writer, "\r")?;
for bed in &config.beds {
writeln!(writer, "[BED/{}]\r", bed.name)?;
writeln!(writer, "\r")?;
for device in &bed.devices {
writeln!(writer, "[DEV/{}]\r", device.name)?;
if let Some(ref port) = device.port {
writeln!(writer, "port={}\r", port)?;
}
if device.readonly {
writeln!(writer, "readonly=1\r")?;
}
writeln!(writer, "type={}\r", device.device_type)?;
if let Some(ref wavs) = device.wavs {
writeln!(writer, "wavs={}\r", wavs)?;
}
if let Some(waveonly) = device.waveonly {
if waveonly {
writeln!(writer, "waveonly=1\r")?;
}
}
if let Some(ref bedname) = device.bedname {
writeln!(writer, "bedname={}\r", bedname)?;
}
if let Some(event) = device.event {
writeln!(writer, "event={}\r", if event { 1 } else { 0 })?;
}
if let Some(skip) = device.skip {
writeln!(writer, "skip={}\r", if skip { 1 } else { 0 })?;
}
writeln!(writer, "\r")?;
}
}
writer.flush()?;
Ok(())
}
fn read_line() -> String {
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
input
}