snap7-cli 0.1.0

CLI tool for communicating with Siemens S7 PLCs — read/write data blocks, watch tags, upload blocks, query SZL
use rand::Rng;
use snap7_server::{DataStore, S7Server, ServerConfig};

#[tokio::main]
async fn main() {
    let port: u16 = std::env::args()
        .nth(1)
        .and_then(|s| s.parse().ok())
        .unwrap_or(10200);

    let update_interval_ms: u64 = std::env::args()
        .nth(2)
        .and_then(|s| s.parse().ok())
        .unwrap_or(1000);

    println!("snap7-sensor-server Starting...");
    println!("  Port: {}", port);
    println!("  Update interval: {}ms", update_interval_ms);
    println!("  Datablock:");
    println!("    DB1: Temperature (REAL, offset 0)");
    println!("    DB2: Humidity (REAL, offset 0)");
    println!("    DB3: Pressure (REAL, offset 0)");
    println!();

    let store = DataStore::new();
    store.write_bytes(1, 0, &25.0_f32.to_be_bytes());
    store.write_bytes(2, 0, &60.0_f32.to_be_bytes());
    store.write_bytes(3, 0, &101.325_f32.to_be_bytes());

    let store_for_update = store.clone();
    tokio::task::spawn_blocking(move || {
        let mut rng = rand::thread_rng();
        let mut temperature = 25.0_f32;
        let mut humidity = 60.0_f32;
        let mut pressure = 101.325_f32;

        loop {
            std::thread::sleep(std::time::Duration::from_millis(update_interval_ms));

            temperature += rng.gen_range(-0.5..0.5);
            temperature = temperature.clamp(20.0, 30.0);

            humidity += rng.gen_range(-1.0..1.0);
            humidity = humidity.clamp(40.0, 80.0);

            pressure += rng.gen_range(-0.2..0.2);
            pressure = pressure.clamp(100.0, 105.0);

            store_for_update.write_bytes(1, 0, &temperature.to_be_bytes());
            store_for_update.write_bytes(2, 0, &humidity.to_be_bytes());
            store_for_update.write_bytes(3, 0, &pressure.to_be_bytes());

            let now = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_secs_f64();
            let time_str = format!(
                "{:02}:{:02}:{:02}",
                ((now % 86400.0) / 3600.0) as u32,
                ((now % 3600.0) / 60.0) as u32,
                (now % 60.0) as u32
            );
            println!(
                "[{}] Update: Temperature={:.2}°C, Humidity={:.2}%, Pressure={:.2}kPa",
                time_str, temperature, humidity, pressure
            );
        }
    });

    let addr: std::net::SocketAddr = format!("0.0.0.0:{port}").parse().unwrap();
    let cfg = ServerConfig {
        bind_addr: addr,
        max_connections: 8,
    };

    let server = S7Server::bind(cfg).await.expect("failed to bind S7Server");

    println!("S7 Server listens: 0.0.0.0:{}", port);
    println!("Press Ctrl+C to stop server");
    println!();

    tokio::select! {
        result = server.serve(store) => {
            eprintln!("Server exit: {:?}", result);
        }
        _ = tokio::signal::ctrl_c() => {
            println!("\nStopping Server...");
        }
    }
}