embedded_debugger_mcp/debugger/
discovery.rs1use probe_rs::probe::list::Lister;
4use crate::error::{DebugError, Result};
5use crate::utils::ProbeType;
6use tracing::{debug, info, warn};
7
8#[derive(Debug, Clone)]
10pub struct ProbeInfo {
11 pub identifier: String,
12 pub vendor_id: u16,
13 pub product_id: u16,
14 pub serial_number: Option<String>,
15 pub probe_type: String,
16 pub speed_khz: u32,
17 pub version: Option<String>,
18}
19
20pub struct ProbeDiscovery;
22
23impl ProbeDiscovery {
24 pub fn list_probes() -> Result<Vec<ProbeInfo>> {
26 debug!("Discovering debug probes");
27
28 let lister = Lister::new();
29
30 let probes = lister
31 .list_all()
32 .into_iter()
33 .map(|probe_info| {
34 let probe_type = ProbeType::from_vid_pid(probe_info.vendor_id, probe_info.product_id);
35
36 ProbeInfo {
37 identifier: probe_info.identifier.clone(),
38 vendor_id: probe_info.vendor_id,
39 product_id: probe_info.product_id,
40 serial_number: probe_info.serial_number.clone(),
41 probe_type: probe_type.to_string(),
42 speed_khz: 4000, version: Some("USB".to_string()),
44 }
45 })
46 .collect::<Vec<_>>();
47
48 info!("Found {} debug probes", probes.len());
49 for probe in &probes {
50 debug!(" {} - {} ({})",
51 probe.identifier,
52 probe.probe_type,
53 probe.serial_number.as_deref().unwrap_or("no serial"));
54 }
55
56 Ok(probes)
57 }
58
59 pub fn find_probe(
61 serial_number: Option<&str>,
62 vendor_id: Option<u16>,
63 product_id: Option<u16>,
64 probe_type: Option<&str>,
65 ) -> Result<ProbeInfo> {
66 debug!("Finding probe with criteria: serial={:?}, vid={:?}, pid={:?}, type={:?}",
67 serial_number, vendor_id, product_id, probe_type);
68
69 let all_probes = Self::list_probes()?;
70
71 if all_probes.is_empty() {
72 return Err(DebugError::ProbeNotFound("No debug probes found".to_string()));
73 }
74
75 if serial_number.is_none() && vendor_id.is_none() && product_id.is_none() && probe_type.is_none() {
77 return Ok(all_probes[0].clone());
78 }
79
80 let matching_probes: Vec<_> = all_probes
82 .into_iter()
83 .filter(|probe| {
84 if let Some(serial) = serial_number {
86 if probe.serial_number.as_deref() != Some(serial) {
87 return false;
88 }
89 }
90
91 if let Some(vid) = vendor_id {
93 if probe.vendor_id != vid {
94 return false;
95 }
96 }
97
98 if let Some(pid) = product_id {
100 if probe.product_id != pid {
101 return false;
102 }
103 }
104
105 if let Some(ptype) = probe_type {
107 if probe.probe_type.to_lowercase() != ptype.to_lowercase() {
108 return false;
109 }
110 }
111
112 true
113 })
114 .collect();
115
116 if matching_probes.is_empty() {
117 return Err(DebugError::ProbeNotFound(
118 "No probe found matching the specified criteria".to_string()
119 ));
120 }
121
122 if matching_probes.len() > 1 {
123 warn!("Multiple probes match criteria, using the first one");
124 for (i, probe) in matching_probes.iter().enumerate() {
125 debug!(" {}: {} ({})", i, probe.identifier,
126 probe.serial_number.as_deref().unwrap_or("no serial"));
127 }
128 }
129
130 Ok(matching_probes[0].clone())
131 }
132
133 pub fn auto_select_probe() -> Result<ProbeInfo> {
135 debug!("Auto-selecting debug probe");
136
137 let all_probes = Self::list_probes()?;
138
139 if all_probes.is_empty() {
140 return Err(DebugError::ProbeNotFound("No debug probes found".to_string()));
141 }
142
143 let preferred_order = ["j-link", "st-link", "daplink"];
145
146 for preferred_type in &preferred_order {
147 for probe in &all_probes {
148 if probe.probe_type.to_lowercase().contains(preferred_type) {
149 info!("Auto-selected probe: {} ({})", probe.identifier, probe.probe_type);
150 return Ok(probe.clone());
151 }
152 }
153 }
154
155 let selected = &all_probes[0];
157 info!("Auto-selected probe: {} ({})", selected.identifier, selected.probe_type);
158 Ok(selected.clone())
159 }
160
161 pub fn get_probe_details(identifier: &str) -> Result<ProbeInfo> {
163 debug!("Getting details for probe: {}", identifier);
164
165 let all_probes = Self::list_probes()?;
166
167 all_probes
168 .into_iter()
169 .find(|probe| probe.identifier == identifier)
170 .ok_or_else(|| DebugError::ProbeNotFound(format!("Probe not found: {}", identifier)))
171 }
172
173 pub fn check_target_support(probe_type: &ProbeType, target_chip: &str) -> bool {
175 match probe_type {
176 ProbeType::JLink => {
177 target_chip.to_lowercase().contains("stm32") ||
179 target_chip.to_lowercase().contains("nrf") ||
180 target_chip.to_lowercase().contains("cortex") ||
181 target_chip.to_lowercase().contains("risc")
182 }
183 ProbeType::StLink => {
184 target_chip.to_lowercase().contains("stm32")
186 }
187 ProbeType::DapLink => {
188 target_chip.to_lowercase().contains("cortex") ||
190 target_chip.to_lowercase().contains("stm32") ||
191 target_chip.to_lowercase().contains("nrf")
192 }
193 ProbeType::Blackmagic => {
194 target_chip.to_lowercase().contains("cortex") ||
196 target_chip.to_lowercase().contains("stm32")
197 }
198 ProbeType::Ftdi => {
199 true
201 }
202 ProbeType::Unknown => {
203 true
205 }
206 }
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_probe_type_support() {
216 assert!(ProbeDiscovery::check_target_support(&ProbeType::JLink, "STM32F407VG"));
217 assert!(ProbeDiscovery::check_target_support(&ProbeType::StLink, "STM32F407VG"));
218 assert!(ProbeDiscovery::check_target_support(&ProbeType::DapLink, "nRF52832"));
219 assert!(!ProbeDiscovery::check_target_support(&ProbeType::StLink, "ESP32"));
220 }
221
222 #[tokio::test]
223 async fn test_list_probes() {
224 let result = ProbeDiscovery::list_probes();
227 assert!(result.is_ok());
228
229 let probes = result.unwrap();
230 for probe in probes {
232 assert!(!probe.identifier.is_empty());
233 assert!(!probe.probe_type.is_empty());
234 }
235 }
236}