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
205
src/tools/hardware_board_info.rs
Normal file
205
src/tools/hardware_board_info.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
//! Hardware board info tool — returns chip name, architecture, memory map for Telegram/agent.
|
||||
//!
|
||||
//! Use when user asks "what board do I have?", "board info", "connected hardware", etc.
|
||||
//! Uses probe-rs for Nucleo when available; otherwise static datasheet info.
|
||||
|
||||
use super::traits::{Tool, ToolResult};
|
||||
use async_trait::async_trait;
|
||||
use serde_json::json;
|
||||
|
||||
/// Static board info (datasheets). Used when probe-rs is unavailable.
|
||||
const BOARD_INFO: &[(&str, &str, &str)] = &[
|
||||
(
|
||||
"nucleo-f401re",
|
||||
"STM32F401RET6",
|
||||
"ARM Cortex-M4, 84 MHz. Flash: 512 KB, RAM: 128 KB. User LED on PA5 (pin 13).",
|
||||
),
|
||||
(
|
||||
"nucleo-f411re",
|
||||
"STM32F411RET6",
|
||||
"ARM Cortex-M4, 100 MHz. Flash: 512 KB, RAM: 128 KB. User LED on PA5 (pin 13).",
|
||||
),
|
||||
(
|
||||
"arduino-uno",
|
||||
"ATmega328P",
|
||||
"8-bit AVR, 16 MHz. Flash: 16 KB, SRAM: 2 KB. Built-in LED on pin 13.",
|
||||
),
|
||||
(
|
||||
"arduino-uno-q",
|
||||
"STM32U585 + Qualcomm",
|
||||
"Dual-core: STM32 (MCU) + Linux (aarch64). GPIO via Bridge app on port 9999.",
|
||||
),
|
||||
(
|
||||
"esp32",
|
||||
"ESP32",
|
||||
"Dual-core Xtensa LX6, 240 MHz. Flash: 4 MB typical. Built-in LED on GPIO 2.",
|
||||
),
|
||||
(
|
||||
"rpi-gpio",
|
||||
"Raspberry Pi",
|
||||
"ARM Linux. Native GPIO via sysfs/rppal. No fixed LED pin.",
|
||||
),
|
||||
];
|
||||
|
||||
/// Tool: return full board info (chip, architecture, memory map) for agent/Telegram.
|
||||
pub struct HardwareBoardInfoTool {
|
||||
boards: Vec<String>,
|
||||
}
|
||||
|
||||
impl HardwareBoardInfoTool {
|
||||
pub fn new(boards: Vec<String>) -> Self {
|
||||
Self { boards }
|
||||
}
|
||||
|
||||
fn static_info_for_board(&self, board: &str) -> Option<String> {
|
||||
BOARD_INFO
|
||||
.iter()
|
||||
.find(|(b, _, _)| *b == board)
|
||||
.map(|(_, chip, desc)| {
|
||||
format!(
|
||||
"**Board:** {}\n**Chip:** {}\n**Description:** {}",
|
||||
board, chip, desc
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Tool for HardwareBoardInfoTool {
|
||||
fn name(&self) -> &str {
|
||||
"hardware_board_info"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Return full board info (chip, architecture, memory map) for connected hardware. Use when: user asks for 'board info', 'what board do I have', 'connected hardware', 'chip info', 'what hardware', or 'memory map'."
|
||||
}
|
||||
|
||||
fn parameters_schema(&self) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"board": {
|
||||
"type": "string",
|
||||
"description": "Optional board name (e.g. nucleo-f401re). If omitted, returns info for first configured board."
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
|
||||
let board = args
|
||||
.get("board")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from)
|
||||
.or_else(|| self.boards.first().cloned());
|
||||
|
||||
let board = board.as_deref().unwrap_or("unknown");
|
||||
|
||||
if self.boards.is_empty() {
|
||||
return Ok(ToolResult {
|
||||
success: false,
|
||||
output: String::new(),
|
||||
error: Some(
|
||||
"No peripherals configured. Add boards to config.toml [peripherals.boards]."
|
||||
.into(),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
#[cfg(feature = "probe")]
|
||||
if board == "nucleo-f401re" || board == "nucleo-f411re" {
|
||||
let chip = if board == "nucleo-f411re" {
|
||||
"STM32F411RETx"
|
||||
} else {
|
||||
"STM32F401RETx"
|
||||
};
|
||||
match probe_board_info(chip) {
|
||||
Ok(info) => {
|
||||
return Ok(ToolResult {
|
||||
success: true,
|
||||
output: info,
|
||||
error: None,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
output.push_str(&format!(
|
||||
"probe-rs attach failed: {}. Using static info.\n\n",
|
||||
e
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(info) = self.static_info_for_board(board) {
|
||||
output.push_str(&info);
|
||||
if let Some(mem) = memory_map_static(board) {
|
||||
output.push_str(&format!("\n\n**Memory map:**\n{}", mem));
|
||||
}
|
||||
} else {
|
||||
output.push_str(&format!(
|
||||
"Board '{}' configured. No static info available.",
|
||||
board
|
||||
));
|
||||
}
|
||||
|
||||
Ok(ToolResult {
|
||||
success: true,
|
||||
output,
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "probe")]
|
||||
fn probe_board_info(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 arch = session.architecture();
|
||||
|
||||
let mut out = format!(
|
||||
"**Board:** {}\n**Chip:** {}\n**Architecture:** {:?}\n\n**Memory map:**\n",
|
||||
chip, target.name, arch
|
||||
);
|
||||
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
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
out.push_str("\n(Info read via USB/SWD — no firmware on target needed.)");
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn memory_map_static(board: &str) -> Option<&'static str> {
|
||||
match board {
|
||||
"nucleo-f401re" | "nucleo-f411re" => Some(
|
||||
"Flash: 0x0800_0000 - 0x0807_FFFF (512 KB)\nRAM: 0x2000_0000 - 0x2001_FFFF (128 KB)",
|
||||
),
|
||||
"arduino-uno" => Some("Flash: 16 KB, SRAM: 2 KB, EEPROM: 1 KB"),
|
||||
"esp32" => Some("Flash: 4 MB, IRAM/DRAM per ESP-IDF layout"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
205
src/tools/hardware_memory_map.rs
Normal file
205
src/tools/hardware_memory_map.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
//! Hardware memory map tool — returns flash/RAM address ranges for connected boards.
|
||||
//!
|
||||
//! Phase B: When user asks "what are the upper and lower memory addresses?", this tool
|
||||
//! returns the memory map. Uses probe-rs for Nucleo/STM32 when available; otherwise
|
||||
//! returns static maps from datasheets.
|
||||
|
||||
use super::traits::{Tool, ToolResult};
|
||||
use async_trait::async_trait;
|
||||
use serde_json::json;
|
||||
|
||||
/// Known memory maps (from datasheets). Used when probe-rs is unavailable.
|
||||
const MEMORY_MAPS: &[(&str, &str)] = &[
|
||||
(
|
||||
"nucleo-f401re",
|
||||
"Flash: 0x0800_0000 - 0x0807_FFFF (512 KB)\nRAM: 0x2000_0000 - 0x2001_FFFF (128 KB)\nSTM32F401RET6, ARM Cortex-M4",
|
||||
),
|
||||
(
|
||||
"nucleo-f411re",
|
||||
"Flash: 0x0800_0000 - 0x0807_FFFF (512 KB)\nRAM: 0x2000_0000 - 0x2001_FFFF (128 KB)\nSTM32F411RET6, ARM Cortex-M4",
|
||||
),
|
||||
(
|
||||
"arduino-uno",
|
||||
"Flash: 0x0000 - 0x3FFF (16 KB, ATmega328P)\nSRAM: 0x0100 - 0x08FF (2 KB)\nEEPROM: 0x0000 - 0x03FF (1 KB)",
|
||||
),
|
||||
(
|
||||
"arduino-mega",
|
||||
"Flash: 0x0000 - 0x3FFFF (256 KB, ATmega2560)\nSRAM: 0x0200 - 0x21FF (8 KB)\nEEPROM: 0x0000 - 0x0FFF (4 KB)",
|
||||
),
|
||||
(
|
||||
"esp32",
|
||||
"Flash: 0x3F40_0000 - 0x3F7F_FFFF (4 MB typical)\nIRAM: 0x4000_0000 - 0x4005_FFFF\nDRAM: 0x3FFB_0000 - 0x3FFF_FFFF",
|
||||
),
|
||||
];
|
||||
|
||||
/// Tool: report hardware memory map for connected boards.
|
||||
pub struct HardwareMemoryMapTool {
|
||||
boards: Vec<String>,
|
||||
}
|
||||
|
||||
impl HardwareMemoryMapTool {
|
||||
pub fn new(boards: Vec<String>) -> Self {
|
||||
Self { boards }
|
||||
}
|
||||
|
||||
fn static_map_for_board(&self, board: &str) -> Option<&'static str> {
|
||||
MEMORY_MAPS
|
||||
.iter()
|
||||
.find(|(b, _)| *b == board)
|
||||
.map(|(_, m)| *m)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Tool for HardwareMemoryMapTool {
|
||||
fn name(&self) -> &str {
|
||||
"hardware_memory_map"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Return the memory map (flash and RAM address ranges) for connected hardware. Use when: user asks for 'upper and lower memory addresses', 'memory map', 'address space', or 'readable addresses'. Returns flash/RAM ranges from datasheets."
|
||||
}
|
||||
|
||||
fn parameters_schema(&self) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"board": {
|
||||
"type": "string",
|
||||
"description": "Optional board name (e.g. nucleo-f401re, arduino-uno). If omitted, returns map for first configured board."
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
|
||||
let board = args
|
||||
.get("board")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from)
|
||||
.or_else(|| self.boards.first().cloned());
|
||||
|
||||
let board = board.as_deref().unwrap_or("unknown");
|
||||
|
||||
if self.boards.is_empty() {
|
||||
return Ok(ToolResult {
|
||||
success: false,
|
||||
output: String::new(),
|
||||
error: Some(
|
||||
"No peripherals configured. Add boards to config.toml [peripherals.boards]."
|
||||
.into(),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
#[cfg(feature = "probe")]
|
||||
let probe_ok = {
|
||||
if board == "nucleo-f401re" || board == "nucleo-f411re" {
|
||||
let chip = if board == "nucleo-f411re" {
|
||||
"STM32F411RETx"
|
||||
} else {
|
||||
"STM32F401RETx"
|
||||
};
|
||||
match probe_rs_memory_map(chip) {
|
||||
Ok(probe_msg) => {
|
||||
output.push_str(&format!("**{}** (via probe-rs):\n{}\n", board, probe_msg));
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
output.push_str(&format!("Probe-rs failed: {}. ", e));
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "probe"))]
|
||||
let probe_ok = false;
|
||||
|
||||
if !probe_ok {
|
||||
if let Some(map) = self.static_map_for_board(board) {
|
||||
output.push_str(&format!("**{}** (from datasheet):\n{}", board, map));
|
||||
} else {
|
||||
let known: Vec<&str> = MEMORY_MAPS.iter().map(|(b, _)| *b).collect();
|
||||
output.push_str(&format!(
|
||||
"No memory map for board '{}'. Known boards: {}",
|
||||
board,
|
||||
known.join(", ")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ToolResult {
|
||||
success: true,
|
||||
output,
|
||||
error: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "probe")]
|
||||
fn probe_rs_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!("probe-rs attach failed: {}", e))?;
|
||||
|
||||
let target = session.target();
|
||||
let mut out = String::new();
|
||||
|
||||
for region in target.memory_map.iter() {
|
||||
match region {
|
||||
MemoryRegion::Ram(ram) => {
|
||||
let start = ram.range.start;
|
||||
let end = ram.range.end;
|
||||
let size_kb = (end - start) / 1024;
|
||||
out.push_str(&format!(
|
||||
"RAM: 0x{:08X} - 0x{:08X} ({} KB)\n",
|
||||
start, end, size_kb
|
||||
));
|
||||
}
|
||||
MemoryRegion::Nvm(flash) => {
|
||||
let start = flash.range.start;
|
||||
let end = flash.range.end;
|
||||
let size_kb = (end - start) / 1024;
|
||||
out.push_str(&format!(
|
||||
"Flash: 0x{:08X} - 0x{:08X} ({} KB)\n",
|
||||
start, end, size_kb
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if out.is_empty() {
|
||||
out = "Could not read memory regions from probe.".to_string();
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn static_map_nucleo() {
|
||||
let tool = HardwareMemoryMapTool::new(vec!["nucleo-f401re".into()]);
|
||||
assert!(tool.static_map_for_board("nucleo-f401re").is_some());
|
||||
assert!(tool
|
||||
.static_map_for_board("nucleo-f401re")
|
||||
.unwrap()
|
||||
.contains("Flash"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_map_arduino() {
|
||||
let tool = HardwareMemoryMapTool::new(vec!["arduino-uno".into()]);
|
||||
assert!(tool.static_map_for_board("arduino-uno").is_some());
|
||||
}
|
||||
}
|
||||
181
src/tools/hardware_memory_read.rs
Normal file
181
src/tools/hardware_memory_read.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
//! Hardware memory read tool — read actual memory/register values from Nucleo via probe-rs.
|
||||
//!
|
||||
//! Use when user asks to "read register values", "read memory at address", "dump lower memory", etc.
|
||||
//! Requires probe feature and Nucleo connected via USB.
|
||||
|
||||
use super::traits::{Tool, ToolResult};
|
||||
use async_trait::async_trait;
|
||||
use serde_json::json;
|
||||
|
||||
/// RAM base for Nucleo-F401RE (STM32F401)
|
||||
const NUCLEO_RAM_BASE: u64 = 0x2000_0000;
|
||||
|
||||
/// Tool: read memory at address from connected Nucleo via probe-rs.
|
||||
pub struct HardwareMemoryReadTool {
|
||||
boards: Vec<String>,
|
||||
}
|
||||
|
||||
impl HardwareMemoryReadTool {
|
||||
pub fn new(boards: Vec<String>) -> Self {
|
||||
Self { boards }
|
||||
}
|
||||
|
||||
fn chip_for_board(board: &str) -> Option<&'static str> {
|
||||
match board {
|
||||
"nucleo-f401re" => Some("STM32F401RETx"),
|
||||
"nucleo-f411re" => Some("STM32F411RETx"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Tool for HardwareMemoryReadTool {
|
||||
fn name(&self) -> &str {
|
||||
"hardware_memory_read"
|
||||
}
|
||||
|
||||
fn description(&self) -> &str {
|
||||
"Read actual memory/register values from Nucleo via USB. Use when: user asks to 'read register values', 'read memory at address', 'dump memory', 'lower memory 0-126', or 'give address and value'. Returns hex dump. Requires Nucleo connected via USB and probe feature. Params: address (hex, e.g. 0x20000000 for RAM start), length (bytes, default 128)."
|
||||
}
|
||||
|
||||
fn parameters_schema(&self) -> serde_json::Value {
|
||||
json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {
|
||||
"type": "string",
|
||||
"description": "Memory address in hex (e.g. 0x20000000 for RAM start). Default: 0x20000000 (RAM base)."
|
||||
},
|
||||
"length": {
|
||||
"type": "integer",
|
||||
"description": "Number of bytes to read (default 128, max 256)."
|
||||
},
|
||||
"board": {
|
||||
"type": "string",
|
||||
"description": "Board name (nucleo-f401re). Optional if only one configured."
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn execute(&self, args: serde_json::Value) -> anyhow::Result<ToolResult> {
|
||||
if self.boards.is_empty() {
|
||||
return Ok(ToolResult {
|
||||
success: false,
|
||||
output: String::new(),
|
||||
error: Some(
|
||||
"No peripherals configured. Add nucleo-f401re to config.toml [peripherals.boards]."
|
||||
.into(),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
let board = args
|
||||
.get("board")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(String::from)
|
||||
.or_else(|| self.boards.first().cloned())
|
||||
.unwrap_or_else(|| "nucleo-f401re".into());
|
||||
|
||||
let chip = Self::chip_for_board(&board);
|
||||
if chip.is_none() {
|
||||
return Ok(ToolResult {
|
||||
success: false,
|
||||
output: String::new(),
|
||||
error: Some(format!(
|
||||
"Memory read only supports nucleo-f401re, nucleo-f411re. Got: {}",
|
||||
board
|
||||
)),
|
||||
});
|
||||
}
|
||||
|
||||
let address_str = args
|
||||
.get("address")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("0x20000000");
|
||||
let address = parse_hex_address(address_str).unwrap_or(NUCLEO_RAM_BASE);
|
||||
|
||||
let length = args.get("length").and_then(|v| v.as_u64()).unwrap_or(128) as usize;
|
||||
let length = length.min(256).max(1);
|
||||
|
||||
#[cfg(feature = "probe")]
|
||||
{
|
||||
match probe_read_memory(chip.unwrap(), address, length) {
|
||||
Ok(output) => {
|
||||
return Ok(ToolResult {
|
||||
success: true,
|
||||
output,
|
||||
error: None,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
return Ok(ToolResult {
|
||||
success: false,
|
||||
output: String::new(),
|
||||
error: Some(format!(
|
||||
"probe-rs read failed: {}. Ensure Nucleo is connected via USB and built with --features probe.",
|
||||
e
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "probe"))]
|
||||
{
|
||||
Ok(ToolResult {
|
||||
success: false,
|
||||
output: String::new(),
|
||||
error: Some(
|
||||
"Memory read requires probe feature. Build with: cargo build --features hardware,probe"
|
||||
.into(),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hex_address(s: &str) -> Option<u64> {
|
||||
let s = s.trim().trim_start_matches("0x").trim_start_matches("0X");
|
||||
u64::from_str_radix(s, 16).ok()
|
||||
}
|
||||
|
||||
#[cfg(feature = "probe")]
|
||||
fn probe_read_memory(chip: &str, address: u64, length: usize) -> anyhow::Result<String> {
|
||||
use probe_rs::MemoryInterface;
|
||||
use probe_rs::Session;
|
||||
use probe_rs::SessionConfig;
|
||||
|
||||
let mut session = Session::auto_attach(chip, SessionConfig::default())
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
|
||||
let mut core = session.core(0)?;
|
||||
let mut buf = vec![0u8; length];
|
||||
core.read_8(address, &mut buf)
|
||||
.map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
|
||||
// Format as hex dump: address | bytes (16 per line)
|
||||
let mut out = format!("Memory read from 0x{:08X} ({} bytes):\n\n", address, length);
|
||||
const COLS: usize = 16;
|
||||
for (i, chunk) in buf.chunks(COLS).enumerate() {
|
||||
let addr = address + (i * COLS) as u64;
|
||||
let hex: String = chunk
|
||||
.iter()
|
||||
.map(|b| format!("{:02X}", b))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let ascii: String = chunk
|
||||
.iter()
|
||||
.map(|&b| {
|
||||
if b.is_ascii_graphic() || b == b' ' {
|
||||
b as char
|
||||
} else {
|
||||
'.'
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
out.push_str(&format!("0x{:08X} {:48} {}\n", addr, hex, ascii));
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
|
@ -5,6 +5,9 @@ pub mod delegate;
|
|||
pub mod file_read;
|
||||
pub mod file_write;
|
||||
pub mod git_operations;
|
||||
pub mod hardware_board_info;
|
||||
pub mod hardware_memory_map;
|
||||
pub mod hardware_memory_read;
|
||||
pub mod http_request;
|
||||
pub mod image_info;
|
||||
pub mod memory_forget;
|
||||
|
|
@ -22,6 +25,9 @@ pub use delegate::DelegateTool;
|
|||
pub use file_read::FileReadTool;
|
||||
pub use file_write::FileWriteTool;
|
||||
pub use git_operations::GitOperationsTool;
|
||||
pub use hardware_board_info::HardwareBoardInfoTool;
|
||||
pub use hardware_memory_map::HardwareMemoryMapTool;
|
||||
pub use hardware_memory_read::HardwareMemoryReadTool;
|
||||
pub use http_request::HttpRequestTool;
|
||||
pub use image_info::ImageInfoTool;
|
||||
pub use memory_forget::MemoryForgetTool;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue