snap7-cli 0.1.6

CLI tool for communicating with Siemens S7 PLCs — read/write data blocks, watch tags, upload blocks, query SZL
use std::io::Write;
use anyhow::Result;
use snap7_client::{transport::TcpTransport, types::BlockData, S7Client};

use crate::args::{BlockAction, BlockArgs};

pub async fn run(client: &S7Client<TcpTransport>, args: BlockArgs) -> Result<()> {
    match args.action {
        BlockAction::List => {
            let list = client.list_blocks().await?;
            println!("Total blocks: {}", list.total_count);
            if list.entries.is_empty() {
                println!("(no block type entries)");
            }
            for e in &list.entries {
                let label = block_type_label(e.block_type);
                println!("  {} (0x{:04X}): {} blocks", label, e.block_type, e.count);
            }
        }
        BlockAction::Numbers { r#type } => {
            let bt = parse_block_type(&r#type)?;
            let nums = client.list_blocks_of_type(bt).await
                .map_err(|e| anyhow::anyhow!("{}", e))?;
            println!("{} blocks of type {}:", nums.len(), r#type.to_uppercase());
            for n in &nums {
                println!("  {}{}", r#type.to_uppercase(), n);
            }
        }
        BlockAction::Info { r#type, number } => {
            let bt = parse_block_type(&r#type)?;
            let info = client.get_ag_block_info(bt, number).await?;
            println!("Block type:   0x{:04X} ({})", info.block_type, r#type);
            println!("Block number: {}", info.block_number);
            println!("Language:     {}", info.language);
            println!("Flags:        0x{:04X}", info.flags);
            println!("Size:         {} bytes", info.size);
            println!("RAM size:     {} bytes", info.size_ram);
            println!("MC7 size:     {} bytes", info.mc7_size);
            println!("Local data:   {} bytes", info.local_data);
            println!("Checksum:     0x{:04X}", info.checksum);
            println!("Version:      0x{:04X}", info.version);
            println!("Author:       {}", info.author);
            println!("Family:       {}", info.family);
            println!("Header:       {}", info.header);
            println!("Date:         {}", info.date);
        }
        BlockAction::Upload { r#type, number, out } => {
            let bt = parse_block_type(&r#type)?;
            let data = client.upload(bt, number).await?;
            let mut file = std::fs::File::create(&out)?;
            file.write_all(&data)?;
            if let Some(bd) = BlockData::from_bytes(&data) {
                println!(
                    "Uploaded {} {} ({} bytes + {} payload) → {}",
                    r#type, number, bd.total_length, bd.payload.len(), out
                );
            } else {
                println!("Uploaded {} {} ({} raw bytes) → {}", r#type, number, data.len(), out);
            }
        }
    }
    Ok(())
}

fn block_type_label(t: u16) -> &'static str {
    match t {
        0x38 => "OB",
        0x41 => "DB",
        0x42 => "SDB",
        0x43 => "FC",
        0x44 => "SFC",
        0x45 => "FB",
        0x46 => "SFB",
        0x47 => "DI",
        _ => "?",
    }
}

fn parse_block_type(s: &str) -> Result<u8> {
    Ok(match s.to_uppercase().as_str() {
        "OB" => 0x38,
        "DB" => 0x41,
        "SDB" => 0x42,
        "FC" => 0x43,
        "SFC" => 0x44,
        "FB" => 0x45,
        "SFB" => 0x46,
        _ => return Err(anyhow::anyhow!("unknown block type: {}. Use OB, DB, FC, FB, SFC, SFB, SDB", s)),
    })
}