mod config;
mod simulate;
use std::path::PathBuf;
use std::process::ExitCode;
use std::sync::Arc;
use std::time::Duration;
use clap::Parser;
use igs_iec_tase2::TaseServer;
use crate::config::{build, parse, InformationSource, Simulation};
use crate::simulate::Stepper;
#[derive(Debug, Parser)]
#[command(
name = "igstase2-server",
version,
about = "A demonstration TASE.2 server: points from a TOML file, with simulated changes. The bench's device under test."
)]
struct Cli {
#[arg(long, default_value = "127.0.0.1:102")]
listen: String,
#[arg(long)]
config: PathBuf,
#[arg(long, default_value_t = 64, value_parser = clap::value_parser!(u32).range(1..=10_000))]
max_associations: u32,
}
async fn run(simulation: Simulation, server: Arc<TaseServer>) {
let mut stepper = Stepper::new(simulation.clone());
let mut ticker = tokio::time::interval(simulation.period.max(Duration::from_millis(1)));
loop {
ticker.tick().await;
let value = stepper.next();
if server
.set_value(simulation.name.clone(), value)
.await
.is_err()
{
return;
}
}
}
async fn announce(source: InformationSource, server: Arc<TaseServer>) {
let mut ticker = tokio::time::interval(source.period.max(Duration::from_millis(1)));
let mut message_id: i64 = 0;
loop {
ticker.tick().await;
message_id = message_id.saturating_add(1);
let Ok(message) = igs_iec_tase2_link::information::InformationMessage::new(
source.info_reference,
source.local_reference,
message_id,
source.stream.clone(),
) else {
return;
};
if server.send_information(message).await.is_err() {
return;
}
}
}
#[tokio::main]
async fn main() -> ExitCode {
let cli = Cli::parse();
let text = match std::fs::read_to_string(&cli.config) {
Ok(text) => text,
Err(error) => {
eprintln!(
"igstase2-server: cannot read {}: {error}",
cli.config.display()
);
return ExitCode::from(2);
}
};
let config = match parse(&text) {
Ok(config) => config,
Err(error) => {
eprintln!("igstase2-server: {}: {error}", cli.config.display());
return ExitCode::from(2);
}
};
let built = match build(&config, cli.max_associations as usize) {
Ok(built) => built,
Err(error) => {
eprintln!("igstase2-server: {}: {error}", cli.config.display());
return ExitCode::from(2);
}
};
let server = match TaseServer::bind(&cli.listen, built.server, built.vmd).await {
Ok(server) => Arc::new(server),
Err(error) => {
eprintln!(
"igstase2-server: cannot listen on {}: {error:?}",
cli.listen
);
return ExitCode::from(1);
}
};
println!("igstase2-server: listening on {}", server.local_address());
let simulations: Vec<_> = built
.simulations
.into_iter()
.map(|simulation| tokio::spawn(run(simulation, Arc::clone(&server))))
.chain(
built
.information
.into_iter()
.map(|source| tokio::spawn(announce(source, Arc::clone(&server)))),
)
.collect();
if let Err(error) = tokio::signal::ctrl_c().await {
eprintln!("igstase2-server: the Ctrl-C signal is not available: {error}");
}
for task in simulations {
task.abort();
let _ = task.await;
}
match Arc::try_unwrap(server) {
Ok(server) => server.shutdown().await,
Err(_) => eprintln!("igstase2-server: the server is still in use at its stop"),
}
ExitCode::SUCCESS
}