* 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>
187 lines
6.7 KiB
Rust
187 lines
6.7 KiB
Rust
//! ZeroClaw Nucleo-F401RE firmware — JSON-over-serial peripheral.
|
|
//!
|
|
//! Listens for newline-delimited JSON on USART2 (PA2=TX, PA3=RX).
|
|
//! USART2 is connected to ST-Link VCP — host sees /dev/ttyACM0 (Linux) or /dev/cu.usbmodem* (macOS).
|
|
//!
|
|
//! Protocol: same as Arduino/ESP32 — see docs/hardware-peripherals-design.md
|
|
|
|
#![no_std]
|
|
#![no_main]
|
|
|
|
use core::fmt::Write;
|
|
use core::str;
|
|
use defmt::info;
|
|
use embassy_executor::Spawner;
|
|
use embassy_stm32::gpio::{Level, Output, Speed};
|
|
use embassy_stm32::usart::{Config, Uart};
|
|
use heapless::String;
|
|
use {defmt_rtt as _, panic_probe as _};
|
|
|
|
/// Arduino-style pin 13 = PA5 (User LED LD2 on Nucleo-F401RE)
|
|
const LED_PIN: u8 = 13;
|
|
|
|
/// Parse integer from JSON: "pin":13 or "value":1
|
|
fn parse_arg(line: &[u8], key: &[u8]) -> Option<i32> {
|
|
// key like b"pin" -> search for b"\"pin\":"
|
|
let mut suffix: [u8; 32] = [0; 32];
|
|
suffix[0] = b'"';
|
|
let mut len = 1;
|
|
for (i, &k) in key.iter().enumerate() {
|
|
if i >= 30 {
|
|
break;
|
|
}
|
|
suffix[len] = k;
|
|
len += 1;
|
|
}
|
|
suffix[len] = b'"';
|
|
suffix[len + 1] = b':';
|
|
len += 2;
|
|
let suffix = &suffix[..len];
|
|
|
|
let line_len = line.len();
|
|
if line_len < len {
|
|
return None;
|
|
}
|
|
for i in 0..=line_len - len {
|
|
if line[i..].starts_with(suffix) {
|
|
let rest = &line[i + len..];
|
|
let mut num: i32 = 0;
|
|
let mut neg = false;
|
|
let mut j = 0;
|
|
if j < rest.len() && rest[j] == b'-' {
|
|
neg = true;
|
|
j += 1;
|
|
}
|
|
while j < rest.len() && rest[j].is_ascii_digit() {
|
|
num = num * 10 + (rest[j] - b'0') as i32;
|
|
j += 1;
|
|
}
|
|
return Some(if neg { -num } else { num });
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn has_cmd(line: &[u8], cmd: &[u8]) -> bool {
|
|
let mut pat: [u8; 64] = [0; 64];
|
|
pat[0..7].copy_from_slice(b"\"cmd\":\"");
|
|
let clen = cmd.len().min(50);
|
|
pat[7..7 + clen].copy_from_slice(&cmd[..clen]);
|
|
pat[7 + clen] = b'"';
|
|
let pat = &pat[..8 + clen];
|
|
|
|
let line_len = line.len();
|
|
if line_len < pat.len() {
|
|
return false;
|
|
}
|
|
for i in 0..=line_len - pat.len() {
|
|
if line[i..].starts_with(pat) {
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Extract "id" for response
|
|
fn copy_id(line: &[u8], out: &mut [u8]) -> usize {
|
|
let prefix = b"\"id\":\"";
|
|
if line.len() < prefix.len() + 1 {
|
|
out[0] = b'0';
|
|
return 1;
|
|
}
|
|
for i in 0..=line.len() - prefix.len() {
|
|
if line[i..].starts_with(prefix) {
|
|
let start = i + prefix.len();
|
|
let mut j = 0;
|
|
while start + j < line.len() && j < out.len() - 1 && line[start + j] != b'"' {
|
|
out[j] = line[start + j];
|
|
j += 1;
|
|
}
|
|
return j;
|
|
}
|
|
}
|
|
out[0] = b'0';
|
|
1
|
|
}
|
|
|
|
#[embassy_executor::main]
|
|
async fn main(_spawner: Spawner) {
|
|
let p = embassy_stm32::init(Default::default());
|
|
|
|
let mut config = Config::default();
|
|
config.baudrate = 115_200;
|
|
|
|
let mut usart = Uart::new_blocking(p.USART2, p.PA3, p.PA2, config).unwrap();
|
|
let mut led = Output::new(p.PA5, Level::Low, Speed::Low);
|
|
|
|
info!("ZeroClaw Nucleo firmware ready on USART2 (115200)");
|
|
|
|
let mut line_buf: heapless::Vec<u8, 256> = heapless::Vec::new();
|
|
let mut id_buf = [0u8; 16];
|
|
let mut resp_buf: String<128> = String::new();
|
|
|
|
loop {
|
|
let mut byte = [0u8; 1];
|
|
if usart.blocking_read(&mut byte).is_ok() {
|
|
let b = byte[0];
|
|
if b == b'\n' || b == b'\r' {
|
|
if !line_buf.is_empty() {
|
|
let id_len = copy_id(&line_buf, &mut id_buf);
|
|
let id_str = str::from_utf8(&id_buf[..id_len]).unwrap_or("0");
|
|
|
|
resp_buf.clear();
|
|
if has_cmd(&line_buf, b"ping") {
|
|
let _ = write!(resp_buf, "{{\"id\":\"{}\",\"ok\":true,\"result\":\"pong\"}}", id_str);
|
|
} else if has_cmd(&line_buf, b"capabilities") {
|
|
let _ = write!(
|
|
resp_buf,
|
|
"{{\"id\":\"{}\",\"ok\":true,\"result\":\"{{\\\"gpio\\\":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],\\\"led_pin\\\":13}}\"}}",
|
|
id_str
|
|
);
|
|
} else if has_cmd(&line_buf, b"gpio_read") {
|
|
let pin = parse_arg(&line_buf, b"pin").unwrap_or(-1);
|
|
if pin == LED_PIN as i32 {
|
|
// Output doesn't support read; return 0 (LED state not readable)
|
|
let _ = write!(resp_buf, "{{\"id\":\"{}\",\"ok\":true,\"result\":\"0\"}}", id_str);
|
|
} else if pin >= 0 && pin <= 13 {
|
|
let _ = write!(resp_buf, "{{\"id\":\"{}\",\"ok\":true,\"result\":\"0\"}}", id_str);
|
|
} else {
|
|
let _ = write!(
|
|
resp_buf,
|
|
"{{\"id\":\"{}\",\"ok\":false,\"result\":\"\",\"error\":\"Invalid pin {}\"}}",
|
|
id_str, pin
|
|
);
|
|
}
|
|
} else if has_cmd(&line_buf, b"gpio_write") {
|
|
let pin = parse_arg(&line_buf, b"pin").unwrap_or(-1);
|
|
let value = parse_arg(&line_buf, b"value").unwrap_or(0);
|
|
if pin == LED_PIN as i32 {
|
|
led.set_level(if value != 0 { Level::High } else { Level::Low });
|
|
let _ = write!(resp_buf, "{{\"id\":\"{}\",\"ok\":true,\"result\":\"done\"}}", id_str);
|
|
} else if pin >= 0 && pin <= 13 {
|
|
let _ = write!(resp_buf, "{{\"id\":\"{}\",\"ok\":true,\"result\":\"done\"}}", id_str);
|
|
} else {
|
|
let _ = write!(
|
|
resp_buf,
|
|
"{{\"id\":\"{}\",\"ok\":false,\"result\":\"\",\"error\":\"Invalid pin {}\"}}",
|
|
id_str, pin
|
|
);
|
|
}
|
|
} else {
|
|
let _ = write!(
|
|
resp_buf,
|
|
"{{\"id\":\"{}\",\"ok\":false,\"result\":\"\",\"error\":\"Unknown command\"}}",
|
|
id_str
|
|
);
|
|
}
|
|
|
|
let _ = usart.blocking_write(resp_buf.as_bytes());
|
|
let _ = usart.blocking_write(b"\n");
|
|
line_buf.clear();
|
|
}
|
|
} else if line_buf.push(b).is_err() {
|
|
line_buf.clear();
|
|
}
|
|
}
|
|
}
|
|
}
|