Ehu shubham shaw contribution --> Hardware support (#306)
* feat: add ZeroClaw firmware for ESP32 and Nucleo * Introduced new firmware for ZeroClaw on ESP32 and Nucleo-F401RE, enabling JSON-over-serial communication for GPIO control. * Added `zeroclaw-esp32` with support for commands like `gpio_read` and `gpio_write`, along with capabilities reporting. * Implemented `zeroclaw-nucleo` firmware with similar functionality for STM32, ensuring compatibility with existing ZeroClaw protocols. * Updated `.gitignore` to include new firmware targets and added necessary dependencies in `Cargo.toml` for both platforms. * Created README files for both firmware projects detailing setup, build, and usage instructions. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: enhance hardware peripheral support and documentation - Added `Peripheral` trait implementation in `src/peripherals/` to manage hardware boards (STM32, RPi GPIO). - Updated `AGENTS.md` to include new extension points for peripherals and their configuration. - Introduced comprehensive documentation for adding boards and tools, including a quick start guide and supported boards. - Enhanced `Cargo.toml` to include optional dependencies for PDF extraction and peripheral support. - Created new datasheets for Arduino Uno, ESP32, and Nucleo-F401RE, detailing pin aliases and GPIO usage. - Implemented new tools for hardware memory reading and board information retrieval in the agent loop. This update significantly improves the integration and usability of hardware peripherals within the ZeroClaw framework. * feat: add ZeroClaw firmware for ESP32 and Nucleo * Introduced new firmware for ZeroClaw on ESP32 and Nucleo-F401RE, enabling JSON-over-serial communication for GPIO control. * Added `zeroclaw-esp32` with support for commands like `gpio_read` and `gpio_write`, along with capabilities reporting. * Implemented `zeroclaw-nucleo` firmware with similar functionality for STM32, ensuring compatibility with existing ZeroClaw protocols. * Updated `.gitignore` to include new firmware targets and added necessary dependencies in `Cargo.toml` for both platforms. * Created README files for both firmware projects detailing setup, build, and usage instructions. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: enhance hardware peripheral support and documentation - Added `Peripheral` trait implementation in `src/peripherals/` to manage hardware boards (STM32, RPi GPIO). - Updated `AGENTS.md` to include new extension points for peripherals and their configuration. - Introduced comprehensive documentation for adding boards and tools, including a quick start guide and supported boards. - Enhanced `Cargo.toml` to include optional dependencies for PDF extraction and peripheral support. - Created new datasheets for Arduino Uno, ESP32, and Nucleo-F401RE, detailing pin aliases and GPIO usage. - Implemented new tools for hardware memory reading and board information retrieval in the agent loop. This update significantly improves the integration and usability of hardware peripherals within the ZeroClaw framework. * feat: Introduce hardware auto-discovery and expanded configuration options for agents, hardware, and security. * chore: update dependencies and improve probe-rs integration - Updated `Cargo.lock` to remove specific version constraints for several dependencies, including `zerocopy`, `syn`, and `strsim`, allowing for more flexibility in version resolution. - Upgraded `bincode` and `bitfield` to their latest versions, enhancing serialization and memory management capabilities. - Updated `Cargo.toml` to reflect the new version of `probe-rs` from `0.24` to `0.30`, improving hardware probing functionality. - Refactored code in `src/hardware` and `src/tools` to utilize the new `SessionConfig` for session management in `probe-rs`, ensuring better compatibility and performance. - Cleaned up documentation in `docs/datasheets/nucleo-f401re.md` by removing unnecessary lines. * fix: apply cargo fmt * docs: add hardware architecture diagram. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b36f23784a
commit
de3ec87d16
59 changed files with 9607 additions and 1885 deletions
45
src/hardware/discover.rs
Normal file
45
src/hardware/discover.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
//! USB device discovery — enumerate devices and enrich with board registry.
|
||||
|
||||
use super::registry;
|
||||
use anyhow::Result;
|
||||
use nusb::MaybeFuture;
|
||||
|
||||
/// Information about a discovered USB device.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UsbDeviceInfo {
|
||||
pub bus_id: String,
|
||||
pub device_address: u8,
|
||||
pub vid: u16,
|
||||
pub pid: u16,
|
||||
pub product_string: Option<String>,
|
||||
pub board_name: Option<String>,
|
||||
pub architecture: Option<String>,
|
||||
}
|
||||
|
||||
/// Enumerate all connected USB devices and enrich with board registry lookup.
|
||||
#[cfg(feature = "hardware")]
|
||||
pub fn list_usb_devices() -> Result<Vec<UsbDeviceInfo>> {
|
||||
let mut devices = Vec::new();
|
||||
|
||||
let iter = nusb::list_devices()
|
||||
.wait()
|
||||
.map_err(|e| anyhow::anyhow!("USB enumeration failed: {e}"))?;
|
||||
|
||||
for dev in iter {
|
||||
let vid = dev.vendor_id();
|
||||
let pid = dev.product_id();
|
||||
let board = registry::lookup_board(vid, pid);
|
||||
|
||||
devices.push(UsbDeviceInfo {
|
||||
bus_id: dev.bus_id().to_string(),
|
||||
device_address: dev.device_address(),
|
||||
vid,
|
||||
pid,
|
||||
product_string: dev.product_string().map(String::from),
|
||||
board_name: board.map(|b| b.name.to_string()),
|
||||
architecture: board.and_then(|b| b.architecture.map(String::from)),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(devices)
|
||||
}
|
||||
121
src/hardware/introspect.rs
Normal file
121
src/hardware/introspect.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
//! Device introspection — correlate serial path with USB device info.
|
||||
|
||||
use super::discover;
|
||||
use super::registry;
|
||||
use anyhow::Result;
|
||||
|
||||
/// Result of introspecting a device by path.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IntrospectResult {
|
||||
pub path: String,
|
||||
pub vid: Option<u16>,
|
||||
pub pid: Option<u16>,
|
||||
pub board_name: Option<String>,
|
||||
pub architecture: Option<String>,
|
||||
pub memory_map_note: String,
|
||||
}
|
||||
|
||||
/// Introspect a device by its serial path (e.g. /dev/ttyACM0, /dev/tty.usbmodem*).
|
||||
/// Attempts to correlate with USB devices from discovery.
|
||||
#[cfg(feature = "hardware")]
|
||||
pub fn introspect_device(path: &str) -> Result<IntrospectResult> {
|
||||
let devices = discover::list_usb_devices()?;
|
||||
|
||||
// Try to correlate path with a discovered device.
|
||||
// On Linux, /dev/ttyACM0 corresponds to a CDC-ACM device; we may have multiple.
|
||||
// Best-effort: if we have exactly one CDC-like device, use it. Otherwise unknown.
|
||||
let matched = if devices.len() == 1 {
|
||||
devices.first().cloned()
|
||||
} else if devices.is_empty() {
|
||||
None
|
||||
} else {
|
||||
// Multiple devices: try to match by path. On Linux we could use sysfs;
|
||||
// for stub, pick first known board or first device.
|
||||
devices
|
||||
.iter()
|
||||
.find(|d| d.board_name.is_some())
|
||||
.cloned()
|
||||
.or_else(|| devices.first().cloned())
|
||||
};
|
||||
|
||||
let (vid, pid, board_name, architecture) = match matched {
|
||||
Some(d) => (Some(d.vid), Some(d.pid), d.board_name, d.architecture),
|
||||
None => (None, None, None, None),
|
||||
};
|
||||
|
||||
let board_info = vid.and_then(|v| pid.and_then(|p| registry::lookup_board(v, p)));
|
||||
let architecture =
|
||||
architecture.or_else(|| board_info.and_then(|b| b.architecture.map(String::from)));
|
||||
let board_name = board_name.or_else(|| board_info.map(|b| b.name.to_string()));
|
||||
|
||||
let memory_map_note = memory_map_for_board(board_name.as_deref());
|
||||
|
||||
Ok(IntrospectResult {
|
||||
path: path.to_string(),
|
||||
vid,
|
||||
pid,
|
||||
board_name,
|
||||
architecture,
|
||||
memory_map_note,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get memory map: via probe-rs when probe feature on and Nucleo, else static or stub.
|
||||
#[cfg(feature = "hardware")]
|
||||
fn memory_map_for_board(board_name: Option<&str>) -> String {
|
||||
#[cfg(feature = "probe")]
|
||||
if let Some(board) = board_name {
|
||||
let chip = match board {
|
||||
"nucleo-f401re" => "STM32F401RETx",
|
||||
"nucleo-f411re" => "STM32F411RETx",
|
||||
_ => return "Build with --features probe for live memory map (Nucleo)".to_string(),
|
||||
};
|
||||
match probe_memory_map(chip) {
|
||||
Ok(s) => return s,
|
||||
Err(_) => return format!("probe-rs attach failed (chip {}). Connect via USB.", chip),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "probe"))]
|
||||
let _ = board_name;
|
||||
|
||||
"Build with --features probe for live memory map via USB".to_string()
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "hardware", feature = "probe"))]
|
||||
fn probe_memory_map(chip: &str) -> anyhow::Result<String> {
|
||||
use probe_rs::config::MemoryRegion;
|
||||
use probe_rs::{Session, SessionConfig};
|
||||
|
||||
let session = Session::auto_attach(chip, SessionConfig::default())
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
let target = session.target();
|
||||
let mut out = String::new();
|
||||
for region in target.memory_map.iter() {
|
||||
match region {
|
||||
MemoryRegion::Ram(ram) => {
|
||||
let (start, end) = (ram.range.start, ram.range.end);
|
||||
out.push_str(&format!(
|
||||
"RAM: 0x{:08X} - 0x{:08X} ({} KB)\n",
|
||||
start,
|
||||
end,
|
||||
(end - start) / 1024
|
||||
));
|
||||
}
|
||||
MemoryRegion::Nvm(flash) => {
|
||||
let (start, end) = (flash.range.start, flash.range.end);
|
||||
out.push_str(&format!(
|
||||
"Flash: 0x{:08X} - 0x{:08X} ({} KB)\n",
|
||||
start,
|
||||
end,
|
||||
(end - start) / 1024
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if out.is_empty() {
|
||||
out = "Could not read memory regions".to_string();
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
1511
src/hardware/mod.rs
1511
src/hardware/mod.rs
File diff suppressed because it is too large
Load diff
102
src/hardware/registry.rs
Normal file
102
src/hardware/registry.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
//! Board registry — maps USB VID/PID to known board names and architectures.
|
||||
|
||||
/// Information about a known board.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BoardInfo {
|
||||
pub vid: u16,
|
||||
pub pid: u16,
|
||||
pub name: &'static str,
|
||||
pub architecture: Option<&'static str>,
|
||||
}
|
||||
|
||||
/// Known USB VID/PID to board mappings.
|
||||
/// VID 0x0483 = STMicroelectronics, 0x2341 = Arduino, 0x10c4 = Silicon Labs.
|
||||
const KNOWN_BOARDS: &[BoardInfo] = &[
|
||||
BoardInfo {
|
||||
vid: 0x0483,
|
||||
pid: 0x374b,
|
||||
name: "nucleo-f401re",
|
||||
architecture: Some("ARM Cortex-M4"),
|
||||
},
|
||||
BoardInfo {
|
||||
vid: 0x0483,
|
||||
pid: 0x3748,
|
||||
name: "nucleo-f411re",
|
||||
architecture: Some("ARM Cortex-M4"),
|
||||
},
|
||||
BoardInfo {
|
||||
vid: 0x2341,
|
||||
pid: 0x0043,
|
||||
name: "arduino-uno",
|
||||
architecture: Some("AVR ATmega328P"),
|
||||
},
|
||||
BoardInfo {
|
||||
vid: 0x2341,
|
||||
pid: 0x0078,
|
||||
name: "arduino-uno",
|
||||
architecture: Some("Arduino Uno Q / ATmega328P"),
|
||||
},
|
||||
BoardInfo {
|
||||
vid: 0x2341,
|
||||
pid: 0x0042,
|
||||
name: "arduino-mega",
|
||||
architecture: Some("AVR ATmega2560"),
|
||||
},
|
||||
BoardInfo {
|
||||
vid: 0x10c4,
|
||||
pid: 0xea60,
|
||||
name: "cp2102",
|
||||
architecture: Some("USB-UART bridge"),
|
||||
},
|
||||
BoardInfo {
|
||||
vid: 0x10c4,
|
||||
pid: 0xea70,
|
||||
name: "cp2102n",
|
||||
architecture: Some("USB-UART bridge"),
|
||||
},
|
||||
// ESP32 dev boards often use CH340 USB-UART
|
||||
BoardInfo {
|
||||
vid: 0x1a86,
|
||||
pid: 0x7523,
|
||||
name: "esp32",
|
||||
architecture: Some("ESP32 (CH340)"),
|
||||
},
|
||||
BoardInfo {
|
||||
vid: 0x1a86,
|
||||
pid: 0x55d4,
|
||||
name: "esp32",
|
||||
architecture: Some("ESP32 (CH340)"),
|
||||
},
|
||||
];
|
||||
|
||||
/// Look up a board by VID and PID.
|
||||
pub fn lookup_board(vid: u16, pid: u16) -> Option<&'static BoardInfo> {
|
||||
KNOWN_BOARDS.iter().find(|b| b.vid == vid && b.pid == pid)
|
||||
}
|
||||
|
||||
/// Return all known board entries.
|
||||
pub fn known_boards() -> &'static [BoardInfo] {
|
||||
KNOWN_BOARDS
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn lookup_nucleo_f401re() {
|
||||
let b = lookup_board(0x0483, 0x374b).unwrap();
|
||||
assert_eq!(b.name, "nucleo-f401re");
|
||||
assert_eq!(b.architecture, Some("ARM Cortex-M4"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lookup_unknown_returns_none() {
|
||||
assert!(lookup_board(0x0000, 0x0000).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_boards_not_empty() {
|
||||
assert!(!known_boards().is_empty());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue