Skip to content

Commit 73d4754

Browse files
committed
[zencan-cli] Add scan-pdo-config command to CLI
1 parent fd81f44 commit 73d4754

File tree

13 files changed

+327
-20
lines changed

13 files changed

+327
-20
lines changed

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ zencan-node = { path = "zencan-node" }
3131
# External
3232
crc16 = "0.4.0"
3333
critical-section = { version = "1.2.0", default-features = false }
34-
crossbeam = { version = "0.8.4", default-features = false }
3534
defmt = "1.0.1"
3635
defmt-or-log = { version = "0.2.1", default-features = false }
3736
embedded-io = { version = "0.6.1" }
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[tpdo.0]
2+
enabled = true
3+
cob = 0x800
4+
extended = true
5+
transmission_type = 254
6+
mappings = [
7+
{ index=0x2000, sub=0, size=16 },
8+
{ index=0x2001, sub=1, size=16 },
9+
]

integration_tests/tests/pdo_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ async fn test_pdo_configuration() {
264264

265265
let test_task = async move {
266266
let config = PdoConfig {
267-
cob: 0x301,
267+
cob: CanId::std(0x301),
268268
enabled: true,
269269
mappings: vec![
270270
PdoMapping {

zencan-cli/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ tokio = { version = "1.45.0", features = ["net", "macros", "rt-multi-thread"] }
2929
reedline = "0.40.0"
3030
shlex = "1.3.0"
3131
clap-num = "1.2.0"
32+
33+
[dev-dependencies]
34+
assertables = "9.8.2"

zencan-cli/src/bin/zencan-cli.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use reedline::{
1919
use shlex::Shlex;
2020
use zencan_cli::command::{Cli, Commands, LssCommands, NmtAction, SdoDataType};
2121
use zencan_client::{
22-
common::{lss::LssState, traits::AsyncCanSender, NodeId},
22+
common::{lss::LssState, node_id::ConfiguredNodeId, traits::AsyncCanSender, NodeId},
2323
open_socketcan, BusManager, NodeConfig,
2424
};
2525

@@ -374,7 +374,6 @@ async fn run_command<S: AsyncCanSender + Sync + Send>(cmd: Commands, manager: &m
374374
},
375375
Err(e) => {
376376
println!("Error reading object: {e}");
377-
return;
378377
}
379378
}
380379
}
@@ -417,11 +416,59 @@ async fn run_command<S: AsyncCanSender + Sync + Send>(cmd: Commands, manager: &m
417416
Err(e) => println!("Error: {e}"),
418417
}
419418
}
419+
Commands::ScanPdoConfig(args) => {
420+
let node_id = match ConfiguredNodeId::new(args.node_id) {
421+
Ok(id) => id,
422+
Err(_) => {
423+
println!("{} is not a valid node ID", args.node_id);
424+
return;
425+
}
426+
};
427+
428+
match manager.read_pdo_config(node_id).await {
429+
Ok(pdos) => {
430+
println!("Node {node_id}");
431+
for (i, pdo) in pdos.tpdos.iter().enumerate() {
432+
println!("TPDO{i}:");
433+
println!(
434+
" valid={}, COB={}, transmission_type={}",
435+
pdo.enabled, pdo.cob, pdo.transmission_type
436+
);
437+
if !pdo.mappings.is_empty() {
438+
println!(" mapping: ");
439+
for m in &pdo.mappings {
440+
println!(
441+
" - index=0x{:x}, sub={}, size={}",
442+
m.index, m.sub, m.size
443+
);
444+
}
445+
}
446+
}
447+
for (i, pdo) in pdos.rpdos.iter().enumerate() {
448+
println!("RPDO{i}:");
449+
println!(
450+
" valid={}, COB={}, transmission_type={}",
451+
pdo.enabled, pdo.cob, pdo.transmission_type
452+
);
453+
if !pdo.mappings.is_empty() {
454+
println!(" mapping: ");
455+
for m in &pdo.mappings {
456+
println!(
457+
" - index=0x{:x}, sub={}, size={}",
458+
m.index, m.sub, m.size
459+
);
460+
}
461+
}
462+
}
463+
}
464+
Err(e) => println!("Error reading PDO config: {e}"),
465+
}
466+
}
420467
}
421468
}
422469

423470
fn parse_command(line: &str) -> Result<Cli, clap::Error> {
424-
match shlex::split(&line) {
471+
match shlex::split(line) {
425472
Some(split) => {
426473
Cli::try_parse_from(std::iter::once("").chain(split.iter().map(String::as_str)))
427474
}

zencan-cli/src/command.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub enum Commands {
1717
Write(WriteArgs),
1818
/// Scan all node IDs to find configured devices
1919
Scan,
20+
/// Scan the PDO configuration from a node
21+
ScanPdoConfig(ScanPdoConfigArgs),
2022
/// Print info about nodes
2123
Info,
2224
/// Load a configuration from a file to a node
@@ -72,6 +74,11 @@ pub struct WriteArgs {
7274
pub value: String,
7375
}
7476

77+
#[derive(Debug, Args)]
78+
pub struct ScanPdoConfigArgs {
79+
pub node_id: u8,
80+
}
81+
7582
#[derive(Debug, Args)]
7683
pub struct LoadConfigArgs {
7784
/// The ID of the node to load the configuration into

zencan-client/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ tokio = { version = "1.45.0", features = [
3131
toml = "0.8.22"
3232
serde = { version = "1.0.219", features = ["derive"] }
3333

34+
[dev-dependencies]
35+
assertables = "9.8.2"
36+
3437
# docs.rs-specific configuration
3538
[package.metadata.docs.rs]
3639
# defines the configuration attribute `docsrs`

zencan-client/src/bus_manager/bus_manager.rs

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,22 @@ use std::{collections::HashMap, sync::Arc, time::Instant};
55

66
use futures::future::join_all;
77
use tokio::task::JoinHandle;
8+
use zencan_common::constants::object_ids::{
9+
RPDO_COMM_BASE, RPDO_MAP_BASE, TPDO_COMM_BASE, TPDO_MAP_BASE,
10+
};
811
use zencan_common::lss::{LssIdentity, LssState};
912
use zencan_common::messages::{NmtCommand, NmtCommandSpecifier, NmtState, ZencanMessage};
13+
use zencan_common::node_id::ConfiguredNodeId;
14+
use zencan_common::sdo::AbortCode;
15+
use zencan_common::CanId;
1016
use zencan_common::{
1117
traits::{AsyncCanReceiver, AsyncCanSender},
1218
NodeId,
1319
};
1420

1521
use super::shared_sender::SharedSender;
1622
use crate::sdo_client::{SdoClient, SdoClientError};
17-
use crate::{LssError, LssMaster};
23+
use crate::{LssError, LssMaster, PdoConfig, PdoMapping, RawAbortCode};
1824

1925
use super::shared_receiver::{SharedReceiver, SharedReceiverChannel};
2026

@@ -150,6 +156,79 @@ async fn scan_node<S: AsyncCanSender + Sync + Send>(
150156
})
151157
}
152158

159+
/// Result struct for reading PDO configuration from a single node
160+
#[derive(Clone, Debug)]
161+
pub struct PdoScanResult {
162+
/// List of TPDO configurations
163+
pub tpdos: Vec<PdoConfig>,
164+
/// List of RPDO configurations
165+
pub rpdos: Vec<PdoConfig>,
166+
}
167+
168+
async fn read_pdos<S: AsyncCanSender + Sync + Send, R: AsyncCanReceiver>(
169+
mut comm_base: u16,
170+
mut mapping_base: u16,
171+
client: &mut SdoClient<S, R>,
172+
) -> Result<Vec<PdoConfig>, SdoClientError> {
173+
let mut result = Vec::new();
174+
175+
loop {
176+
let _comm_max_sub = match client.read_u8(comm_base, 0).await {
177+
Ok(val) => val,
178+
// This error is expected; this means there are no more PDOs to read
179+
Err(SdoClientError::ServerAbort {
180+
index: _,
181+
sub: _,
182+
abort_code: RawAbortCode::Valid(AbortCode::NoSuchObject),
183+
}) => break,
184+
// Any other error is unexpected
185+
Err(e) => {
186+
return Err(e);
187+
}
188+
};
189+
190+
let cob_value = client.read_u32(comm_base, 1).await?;
191+
let transmission_type = client.read_u8(comm_base, 2).await?;
192+
193+
let frame = (cob_value & (1 << 29)) != 0;
194+
let enabled = (cob_value & (1 << 31)) == 0;
195+
let cob_id = cob_value & 0x1FFFFFFF;
196+
let cob = if frame {
197+
CanId::extended(cob_id)
198+
} else {
199+
CanId::std((cob_id & 0x7ff) as u16)
200+
};
201+
let num_mappings = client.read_u8(mapping_base, 0).await?;
202+
let mut mappings = Vec::new();
203+
for i in 0..num_mappings {
204+
let map_param = client.read_u32(mapping_base, i + 1).await?;
205+
mappings.push(PdoMapping::from_object_value(map_param));
206+
}
207+
208+
result.push(PdoConfig {
209+
cob,
210+
enabled,
211+
mappings,
212+
transmission_type,
213+
});
214+
comm_base += 1;
215+
mapping_base += 1;
216+
}
217+
Ok(result)
218+
}
219+
220+
async fn read_rpdo_config<S: AsyncCanSender + Sync + Send, R: AsyncCanReceiver>(
221+
client: &mut SdoClient<S, R>,
222+
) -> Result<Vec<PdoConfig>, SdoClientError> {
223+
read_pdos(RPDO_COMM_BASE, RPDO_MAP_BASE, client).await
224+
}
225+
226+
async fn read_tpdo_config<S: AsyncCanSender + Sync + Send, R: AsyncCanReceiver>(
227+
client: &mut SdoClient<S, R>,
228+
) -> Result<Vec<PdoConfig>, SdoClientError> {
229+
read_pdos(TPDO_COMM_BASE, TPDO_MAP_BASE, client).await
230+
}
231+
153232
#[derive(Debug)]
154233
pub struct SdoClientGuard<'a, S, R>
155234
where
@@ -470,4 +549,19 @@ impl<S: AsyncCanSender + Sync + Send> BusManager<S> {
470549
let message = NmtCommand { cs: cmd, node };
471550
self.sender.send(message.into()).await.ok();
472551
}
552+
553+
/// Read the RPDO and TPDO configuration for the specified node
554+
///
555+
/// node - The node ID to read from
556+
pub async fn read_pdo_config(
557+
&mut self,
558+
node: ConfiguredNodeId,
559+
) -> Result<PdoScanResult, SdoClientError> {
560+
let mut client = self.sdo_client(node.raw());
561+
562+
let tpdos = read_tpdo_config(&mut client).await?;
563+
let rpdos = read_rpdo_config(&mut client).await?;
564+
565+
Ok(PdoScanResult { tpdos, rpdos })
566+
}
473567
}

0 commit comments

Comments
 (0)