zeroclaw/firmware/zeroclaw-nucleo/src/main.rs
ehu shubham shaw de3ec87d16
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>
2026-02-16 11:40:10 -05:00

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();
}
}
}
}