Skip to main content

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