agent_can/cli/
commands.rs1use crate::cli::args::{
2 AdaptersCommand, CliArgs, Command, ConnectArgs, MessageArgs, MessageCommand, MessageSendArgs,
3 TraceCommand,
4};
5use crate::cli::error::CliError;
6use crate::protocol::{
7 ConnectRequest, DbcSpec, MessageListRequest, MessagePayload, MessageReadRequest,
8 MessageSendRequest, MessageStopRequest, Request, RequestAction, SchemaRequest, Selector,
9 TraceStartRequest,
10};
11use uuid::Uuid;
12
13pub fn to_request(args: &CliArgs) -> Result<Request, CliError> {
14 let command = args.command.as_ref().ok_or(CliError::MissingCommand)?;
15 let action = match command {
16 Command::Adapters(adapters) => match adapters.command {
17 AdaptersCommand::List => RequestAction::AdaptersList,
18 },
19 Command::Connect(connect) => RequestAction::Connect(parse_connect_request(connect)?),
20 Command::Disconnect => RequestAction::Disconnect,
21 Command::Status => RequestAction::Status,
22 Command::Schema(schema) => RequestAction::Schema(SchemaRequest {
23 filter: schema.filter.clone(),
24 }),
25 Command::Message(MessageArgs { command }) => match command {
26 MessageCommand::List(list) => RequestAction::MessageList(MessageListRequest {
27 filter: list.filter.clone(),
28 allow_raw: list.allow_raw,
29 include_tx: list.include_tx,
30 }),
31 MessageCommand::Read(read) => RequestAction::MessageRead(MessageReadRequest {
32 select: read.select.clone(),
33 count: read.count,
34 include_tx: read.include_tx,
35 }),
36 MessageCommand::Send(send) => RequestAction::MessageSend(parse_message_send(send)?),
37 MessageCommand::Stop(stop) => RequestAction::MessageStop(MessageStopRequest {
38 target: stop.target.clone(),
39 }),
40 },
41 Command::Trace(trace) => match &trace.command {
42 TraceCommand::Start(start) => RequestAction::TraceStart(TraceStartRequest {
43 path: start.path.clone(),
44 }),
45 TraceCommand::Stop => RequestAction::TraceStop,
46 },
47 };
48 Ok(Request {
49 id: Uuid::new_v4(),
50 action,
51 })
52}
53
54fn parse_connect_request(args: &ConnectArgs) -> Result<ConnectRequest, CliError> {
55 let mut dbcs = Vec::with_capacity(args.dbcs.len());
56 for entry in &args.dbcs {
57 let Some((alias, path)) = entry.split_once('=') else {
58 return Err(CliError::InvalidDbcMapping(entry.clone()));
59 };
60 dbcs.push(DbcSpec {
61 alias: alias.trim().to_string(),
62 path: path.trim().to_string(),
63 });
64 }
65 Ok(ConnectRequest {
66 adapter: args.adapter.clone(),
67 bitrate: args.bitrate,
68 bitrate_data: args.bitrate_data,
69 fd: args.fd,
70 dbcs,
71 })
72}
73
74fn parse_message_send(args: &MessageSendArgs) -> Result<MessageSendRequest, CliError> {
75 let data = match Selector::parse(&args.target).map_err(CliError::CommandFailed)? {
76 Selector::ArbId(_) => MessagePayload::RawHex(args.data.clone()),
77 Selector::SemanticPattern(_) => MessagePayload::Signals(parse_signal_map(args)?),
78 };
79 Ok(MessageSendRequest {
80 target: args.target.clone(),
81 data,
82 periodicity_ms: args.periodicity_ms,
83 })
84}
85
86fn parse_signal_map(
87 args: &MessageSendArgs,
88) -> Result<std::collections::BTreeMap<String, f64>, CliError> {
89 serde_json::from_str(&args.data).map_err(|err| {
90 CliError::CommandFailed(format!(
91 "semantic `message send` data must be a JSON object mapping signal names to numeric values: {err}"
92 ))
93 })
94}
95
96#[cfg(test)]
97mod tests {
98 use super::{parse_message_send, parse_signal_map};
99 use crate::cli::args::MessageSendArgs;
100 use crate::protocol::MessagePayload;
101
102 #[test]
103 fn semantic_message_send_data_uses_json_signal_maps() {
104 let args = MessageSendArgs {
105 target: "powertrain.VehicleControl".to_string(),
106 data: r#"{"enable":1,"torque":12.5}"#.to_string(),
107 periodicity_ms: None,
108 };
109 let parsed = parse_signal_map(&args).expect("signal map should parse");
110 assert_eq!(parsed.get("enable"), Some(&1.0));
111 assert_eq!(parsed.get("torque"), Some(&12.5));
112 }
113
114 #[test]
115 fn raw_message_send_data_uses_hex_strings() {
116 let args = MessageSendArgs {
117 target: "0x123".to_string(),
118 data: "DEADBEEF".to_string(),
119 periodicity_ms: Some(100),
120 };
121 let parsed = parse_message_send(&args).expect("raw send should parse");
122 assert_eq!(parsed.data, MessagePayload::RawHex("DEADBEEF".to_string()));
123 assert_eq!(parsed.periodicity_ms, Some(100));
124 }
125}