zencan_cli/
command.rs

1use clap::{Args, Parser, Subcommand, ValueEnum};
2use clap_num::maybe_hex;
3use std::{path::PathBuf, str::FromStr};
4use zencan_client::common::lss::LssIdentity;
5
6#[derive(Debug, Parser)]
7pub struct Cli {
8    #[command(subcommand)]
9    pub command: Commands,
10}
11
12#[derive(Debug, Subcommand)]
13pub enum Commands {
14    /// Read an object via SDO
15    Read(ReadArgs),
16    /// Write an object via SDO
17    Write(WriteArgs),
18    /// Scan all node IDs to find configured devices
19    Scan,
20    /// Scan the PDO configuration from a node
21    ScanPdoConfig(ScanPdoConfigArgs),
22    /// Print info about nodes
23    Info,
24    /// Load a configuration from a file to a node
25    LoadConfig(LoadConfigArgs),
26    /// Send command to save persistable objects
27    SaveObjects(SaveObjectsArgs),
28    /// NMT commands
29    Nmt(NmtArgs),
30    /// LSS commands
31    #[command(subcommand)]
32    Lss(LssCommands),
33}
34
35#[derive(Debug, Args)]
36pub struct ReadArgs {
37    /// The ID of the node to read from
38    pub node_id: u8,
39    /// The object index to read
40    #[clap(value_parser=maybe_hex::<u16>)]
41    pub index: u16,
42    /// The sub object to read
43    #[clap(value_parser=maybe_hex::<u8>)]
44    pub sub: u8,
45    /// How to interpret the response (optional)
46    pub data_type: Option<SdoDataType>,
47}
48
49#[derive(Clone, Copy, Debug, ValueEnum)]
50pub enum SdoDataType {
51    U32,
52    U16,
53    U8,
54    I32,
55    I16,
56    I8,
57    F32,
58    Utf8,
59}
60
61#[derive(Debug, Args)]
62pub struct WriteArgs {
63    /// The ID of the node to read from
64    pub node_id: u8,
65    /// The object index to read
66    #[clap(value_parser=maybe_hex::<u16>)]
67    pub index: u16,
68    /// The sub object to read
69    #[clap(value_parser=maybe_hex::<u8>)]
70    pub sub: u8,
71    /// How to interpret the value
72    pub data_type: SdoDataType,
73    /// The value to write
74    pub value: String,
75}
76
77#[derive(Debug, Args)]
78pub struct ScanPdoConfigArgs {
79    pub node_id: u8,
80}
81
82#[derive(Debug, Args)]
83pub struct LoadConfigArgs {
84    /// The ID of the node to load the configuration into
85    pub node_id: u8,
86    /// Path to a node config TOML file
87    #[arg(value_hint=clap::ValueHint::FilePath)]
88    pub path: PathBuf,
89}
90
91#[derive(Debug, Args)]
92pub struct SaveObjectsArgs {
93    /// The ID of the node to command
94    pub node_id: u8,
95}
96
97/// Specifies a node to apply an NMT command
98#[derive(Debug, Clone, Copy, PartialEq)]
99pub enum NmtNodeArg {
100    All,
101    Specific(u8),
102}
103
104impl NmtNodeArg {
105    pub fn raw(&self) -> u8 {
106        match self {
107            Self::All => 0,
108            Self::Specific(id) => *id,
109        }
110    }
111}
112
113impl FromStr for NmtNodeArg {
114    type Err = &'static str;
115    fn from_str(s: &str) -> Result<Self, Self::Err> {
116        match s.parse::<u8>() {
117            Ok(num) => {
118                if num == 0 {
119                    Ok(Self::All)
120                } else if num < 128 {
121                    Ok(Self::Specific(num))
122                } else {
123                    Err("Node ID must be between 0 and 127")
124                }
125            }
126            Err(_) => {
127                if s == "all" {
128                    Ok(Self::All)
129                } else {
130                    Err("Must specify a node ID, or 'all' to broadcast")
131                }
132            }
133        }
134    }
135}
136
137#[derive(Debug, Args)]
138pub struct NmtArgs {
139    pub action: NmtAction,
140    /// Specify the node ID to command. Use '0' or 'all' to broadcast to all nodes.
141    pub node: NmtNodeArg,
142}
143
144#[derive(Clone, Copy, Debug, PartialEq, ValueEnum)]
145pub enum NmtAction {
146    ResetApp,
147    ResetComms,
148    Start,
149    Stop,
150}
151
152#[derive(Args, Clone, Copy, Debug)]
153#[group(multiple=true, requires_all=["vendor_id", "product_code", "revision", "serial"])]
154pub struct IdentityArgs {
155    #[clap(value_parser=maybe_hex::<u32>)]
156    #[arg(required = false)]
157    pub vendor_id: u32,
158    /// The product to configure
159    #[clap(value_parser=maybe_hex::<u32>)]
160    #[arg(required = false)]
161    pub product_code: u32,
162    /// The revision to configure
163    #[clap(value_parser=maybe_hex::<u32>)]
164    #[arg(required = false)]
165    pub revision: u32,
166    /// The serial number
167    #[clap(value_parser=maybe_hex::<u32>)]
168    #[arg(required = false)]
169    pub serial: u32,
170}
171
172impl From<IdentityArgs> for LssIdentity {
173    fn from(value: IdentityArgs) -> Self {
174        LssIdentity {
175            vendor_id: value.vendor_id,
176            product_code: value.product_code,
177            revision: value.revision,
178            serial: value.serial,
179        }
180    }
181}
182
183#[derive(Debug, Subcommand)]
184pub enum LssCommands {
185    /// Put the specified device into configuration mode, and put all others into waiting mode
186    Activate {
187        #[clap(flatten)]
188        identity: IdentityArgs,
189    },
190    /// Perform a fastscan to find unconfigured nodes
191    Fastscan {
192        /// Timeout for waiting for fastscan response in milliseconds
193        #[arg(default_value = "5")]
194        timeout: u64,
195    },
196    SetNodeId {
197        /// The node ID to assign
198        node_id: u8,
199        #[clap(flatten)]
200        identity: Option<IdentityArgs>,
201    },
202    StoreConfig {
203        #[clap(flatten)]
204        identity: Option<IdentityArgs>,
205    },
206    /// Globally enable or disable configuration mode
207    Global {
208        /// 0 to put in waiting, 1 to put into configuration
209        #[clap(action=clap::ArgAction::Set)]
210        enable: u8,
211    },
212}