Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
326
vendor/ruvector/examples/ruvLLM/esp32-flash/src/diagnostics.rs
vendored
Normal file
326
vendor/ruvector/examples/ruvLLM/esp32-flash/src/diagnostics.rs
vendored
Normal file
@@ -0,0 +1,326 @@
|
||||
//! Error Diagnostics with Fix Suggestions
|
||||
//!
|
||||
//! Provides helpful error messages and automated fix suggestions
|
||||
//! for common issues encountered during build, flash, and runtime.
|
||||
|
||||
use core::fmt;
|
||||
use heapless::String;
|
||||
|
||||
/// Diagnostic severity
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Severity {
|
||||
/// Informational message
|
||||
Info,
|
||||
/// Warning - may cause issues
|
||||
Warning,
|
||||
/// Error - operation failed
|
||||
Error,
|
||||
/// Fatal - cannot continue
|
||||
Fatal,
|
||||
}
|
||||
|
||||
impl fmt::Display for Severity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Severity::Info => write!(f, "INFO"),
|
||||
Severity::Warning => write!(f, "WARN"),
|
||||
Severity::Error => write!(f, "ERROR"),
|
||||
Severity::Fatal => write!(f, "FATAL"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error category
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ErrorCategory {
|
||||
/// Build/compilation errors
|
||||
Build,
|
||||
/// Toolchain issues
|
||||
Toolchain,
|
||||
/// Flash/upload errors
|
||||
Flash,
|
||||
/// Runtime errors
|
||||
Runtime,
|
||||
/// Memory issues
|
||||
Memory,
|
||||
/// Network/WiFi errors
|
||||
Network,
|
||||
/// Hardware issues
|
||||
Hardware,
|
||||
}
|
||||
|
||||
/// Diagnostic result with fix suggestions
|
||||
#[derive(Clone)]
|
||||
pub struct Diagnostic {
|
||||
/// Error code (e.g., "E0001")
|
||||
pub code: String<8>,
|
||||
/// Severity level
|
||||
pub severity: Severity,
|
||||
/// Error category
|
||||
pub category: ErrorCategory,
|
||||
/// Short description
|
||||
pub message: String<128>,
|
||||
/// Detailed explanation
|
||||
pub explanation: String<256>,
|
||||
/// Suggested fixes
|
||||
pub fixes: heapless::Vec<String<128>, 4>,
|
||||
/// Related documentation link
|
||||
pub docs_url: Option<String<128>>,
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
/// Create new diagnostic
|
||||
pub fn new(code: &str, severity: Severity, category: ErrorCategory, message: &str) -> Self {
|
||||
Self {
|
||||
code: String::try_from(code).unwrap_or_default(),
|
||||
severity,
|
||||
category,
|
||||
message: String::try_from(message).unwrap_or_default(),
|
||||
explanation: String::new(),
|
||||
fixes: heapless::Vec::new(),
|
||||
docs_url: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add explanation
|
||||
pub fn with_explanation(mut self, explanation: &str) -> Self {
|
||||
self.explanation = String::try_from(explanation).unwrap_or_default();
|
||||
self
|
||||
}
|
||||
|
||||
/// Add fix suggestion
|
||||
pub fn with_fix(mut self, fix: &str) -> Self {
|
||||
let _ = self.fixes.push(String::try_from(fix).unwrap_or_default());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add documentation URL
|
||||
pub fn with_docs(mut self, url: &str) -> Self {
|
||||
self.docs_url = Some(String::try_from(url).unwrap_or_default());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Diagnostic {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "\n[{}] {}: {}", self.code, self.severity, self.message)?;
|
||||
|
||||
if !self.explanation.is_empty() {
|
||||
writeln!(f, "\n {}", self.explanation)?;
|
||||
}
|
||||
|
||||
if !self.fixes.is_empty() {
|
||||
writeln!(f, "\n Suggested fixes:")?;
|
||||
for (i, fix) in self.fixes.iter().enumerate() {
|
||||
writeln!(f, " {}. {}", i + 1, fix)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(url) = &self.docs_url {
|
||||
writeln!(f, "\n Documentation: {}", url)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Known error patterns and their diagnostics
|
||||
pub fn diagnose_error(error_text: &str) -> Option<Diagnostic> {
|
||||
// Toolchain errors
|
||||
if error_text.contains("espup") && error_text.contains("not found") {
|
||||
return Some(
|
||||
Diagnostic::new("T0001", Severity::Error, ErrorCategory::Toolchain, "ESP toolchain not installed")
|
||||
.with_explanation("The ESP32 Rust toolchain (espup) is not installed or not in PATH.")
|
||||
.with_fix("Run: npx ruvllm-esp32 install")
|
||||
.with_fix("Or manually: cargo install espup && espup install")
|
||||
.with_fix("Then restart your terminal or run: source ~/export-esp.sh")
|
||||
.with_docs("https://esp-rs.github.io/book/installation/")
|
||||
);
|
||||
}
|
||||
|
||||
if error_text.contains("LIBCLANG_PATH") {
|
||||
return Some(
|
||||
Diagnostic::new("T0002", Severity::Error, ErrorCategory::Toolchain, "LIBCLANG_PATH not set")
|
||||
.with_explanation("The LIBCLANG_PATH environment variable is not set or points to an invalid location.")
|
||||
.with_fix("Windows: Run .\\scripts\\windows\\env.ps1")
|
||||
.with_fix("Linux/Mac: source ~/export-esp.sh")
|
||||
.with_fix("Or set manually: export LIBCLANG_PATH=/path/to/libclang")
|
||||
);
|
||||
}
|
||||
|
||||
if error_text.contains("ldproxy") && error_text.contains("not found") {
|
||||
return Some(
|
||||
Diagnostic::new("T0003", Severity::Error, ErrorCategory::Toolchain, "ldproxy not installed")
|
||||
.with_explanation("The ldproxy linker wrapper is required for ESP32 builds.")
|
||||
.with_fix("Run: cargo install ldproxy")
|
||||
);
|
||||
}
|
||||
|
||||
// Flash errors
|
||||
if error_text.contains("Permission denied") && error_text.contains("/dev/tty") {
|
||||
return Some(
|
||||
Diagnostic::new("F0001", Severity::Error, ErrorCategory::Flash, "Serial port permission denied")
|
||||
.with_explanation("Your user does not have permission to access the serial port.")
|
||||
.with_fix("Add user to dialout group: sudo usermod -a -G dialout $USER")
|
||||
.with_fix("Then log out and log back in")
|
||||
.with_fix("Or use sudo (not recommended): sudo espflash flash ...")
|
||||
);
|
||||
}
|
||||
|
||||
if error_text.contains("No such file or directory") && error_text.contains("/dev/tty") {
|
||||
return Some(
|
||||
Diagnostic::new("F0002", Severity::Error, ErrorCategory::Flash, "Serial port not found")
|
||||
.with_explanation("The specified serial port does not exist. The ESP32 may not be connected.")
|
||||
.with_fix("Check USB connection")
|
||||
.with_fix("Try a different USB cable (data cable, not charge-only)")
|
||||
.with_fix("Install USB-to-serial drivers if needed")
|
||||
.with_fix("Run 'ls /dev/tty*' to find available ports")
|
||||
);
|
||||
}
|
||||
|
||||
if error_text.contains("A]fatal error occurred: Failed to connect") {
|
||||
return Some(
|
||||
Diagnostic::new("F0003", Severity::Error, ErrorCategory::Flash, "Failed to connect to ESP32")
|
||||
.with_explanation("Could not establish connection with the ESP32 bootloader.")
|
||||
.with_fix("Hold BOOT button while connecting")
|
||||
.with_fix("Try pressing RESET while holding BOOT")
|
||||
.with_fix("Check that the correct port is selected")
|
||||
.with_fix("Try a lower baud rate: --baud 115200")
|
||||
);
|
||||
}
|
||||
|
||||
// Memory errors
|
||||
if error_text.contains("out of memory") || error_text.contains("alloc") {
|
||||
return Some(
|
||||
Diagnostic::new("M0001", Severity::Error, ErrorCategory::Memory, "Out of memory")
|
||||
.with_explanation("The device ran out of RAM during operation.")
|
||||
.with_fix("Use a smaller model (e.g., nanoembed-500k)")
|
||||
.with_fix("Reduce max_seq_len in config")
|
||||
.with_fix("Enable binary quantization for 32x compression")
|
||||
.with_fix("Use ESP32-S3 for more SRAM (512KB)")
|
||||
);
|
||||
}
|
||||
|
||||
if error_text.contains("stack overflow") {
|
||||
return Some(
|
||||
Diagnostic::new("M0002", Severity::Fatal, ErrorCategory::Memory, "Stack overflow")
|
||||
.with_explanation("The call stack exceeded its allocated size.")
|
||||
.with_fix("Increase stack size in sdkconfig")
|
||||
.with_fix("Reduce recursion depth in your code")
|
||||
.with_fix("Move large arrays to heap allocation")
|
||||
);
|
||||
}
|
||||
|
||||
// Build errors
|
||||
if error_text.contains("error[E0433]") && error_text.contains("esp_idf") {
|
||||
return Some(
|
||||
Diagnostic::new("B0001", Severity::Error, ErrorCategory::Build, "ESP-IDF crate not found")
|
||||
.with_explanation("The esp-idf-* crates are not available for your target.")
|
||||
.with_fix("Ensure you're using the ESP toolchain: rustup default esp")
|
||||
.with_fix("Check that esp feature is enabled in Cargo.toml")
|
||||
.with_fix("Run: source ~/export-esp.sh")
|
||||
);
|
||||
}
|
||||
|
||||
if error_text.contains("target may not be installed") {
|
||||
return Some(
|
||||
Diagnostic::new("B0002", Severity::Error, ErrorCategory::Build, "Target not installed")
|
||||
.with_explanation("The Rust target for your ESP32 variant is not installed.")
|
||||
.with_fix("Run: espup install")
|
||||
.with_fix("Or: rustup target add <target>")
|
||||
);
|
||||
}
|
||||
|
||||
// Network errors
|
||||
if error_text.contains("WiFi") && error_text.contains("connect") {
|
||||
return Some(
|
||||
Diagnostic::new("N0001", Severity::Error, ErrorCategory::Network, "WiFi connection failed")
|
||||
.with_explanation("Could not connect to the WiFi network.")
|
||||
.with_fix("Check SSID and password")
|
||||
.with_fix("Ensure the network is 2.4GHz (ESP32 doesn't support 5GHz)")
|
||||
.with_fix("Move closer to the access point")
|
||||
.with_fix("Check that the network is not hidden")
|
||||
);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Check system for common issues
|
||||
pub fn run_diagnostics() -> heapless::Vec<Diagnostic, 8> {
|
||||
let mut issues = heapless::Vec::new();
|
||||
|
||||
// These would be actual checks in a real implementation
|
||||
// Here we just show the structure
|
||||
|
||||
// Check available memory
|
||||
// In real impl: check heap_caps_get_free_size()
|
||||
|
||||
// Check flash size
|
||||
// In real impl: check partition table
|
||||
|
||||
// Check WiFi status
|
||||
// In real impl: check esp_wifi_get_mode()
|
||||
|
||||
issues
|
||||
}
|
||||
|
||||
/// Print diagnostic in colored format (for terminals)
|
||||
pub fn format_diagnostic_colored(diag: &Diagnostic) -> String<512> {
|
||||
let mut output = String::new();
|
||||
|
||||
let color = match diag.severity {
|
||||
Severity::Info => "\x1b[36m", // Cyan
|
||||
Severity::Warning => "\x1b[33m", // Yellow
|
||||
Severity::Error => "\x1b[31m", // Red
|
||||
Severity::Fatal => "\x1b[35m", // Magenta
|
||||
};
|
||||
let reset = "\x1b[0m";
|
||||
|
||||
let _ = core::fmt::write(
|
||||
&mut output,
|
||||
format_args!("\n{}[{}]{} {}: {}\n", color, diag.code, reset, diag.severity, diag.message)
|
||||
);
|
||||
|
||||
if !diag.explanation.is_empty() {
|
||||
let _ = core::fmt::write(&mut output, format_args!("\n {}\n", diag.explanation));
|
||||
}
|
||||
|
||||
if !diag.fixes.is_empty() {
|
||||
let _ = output.push_str("\n \x1b[32mSuggested fixes:\x1b[0m\n");
|
||||
for (i, fix) in diag.fixes.iter().enumerate() {
|
||||
let _ = core::fmt::write(&mut output, format_args!(" {}. {}\n", i + 1, fix));
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_diagnose_toolchain_error() {
|
||||
let error = "error: espup: command not found";
|
||||
let diag = diagnose_error(error);
|
||||
assert!(diag.is_some());
|
||||
assert_eq!(diag.unwrap().code.as_str(), "T0001");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_diagnose_flash_error() {
|
||||
let error = "Permission denied: /dev/ttyUSB0";
|
||||
let diag = diagnose_error(error);
|
||||
assert!(diag.is_some());
|
||||
assert_eq!(diag.unwrap().code.as_str(), "F0001");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_diagnose_memory_error() {
|
||||
let error = "panicked at 'alloc error'";
|
||||
let diag = diagnose_error(error);
|
||||
assert!(diag.is_some());
|
||||
assert_eq!(diag.unwrap().code.as_str(), "M0001");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user