diff --git a/Cargo.lock b/Cargo.lock index 3a982bf8..93191612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "aimdb-embassy-adapter" +version = "0.1.0" +dependencies = [ + "aimdb-core", + "embassy-executor", + "embassy-time", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "heapless", +] + [[package]] name = "aimdb-examples" version = "0.1.0" @@ -40,6 +53,164 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + +[[package]] +name = "embassy-executor" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" +dependencies = [ + "critical-section", + "document-features", + "embassy-executor-macros", + "embassy-executor-timer-queue", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "embassy-executor-timer-queue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" + +[[package]] +name = "embassy-time" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" +dependencies = [ + "document-features", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + [[package]] name = "hash32" version = "0.3.1" @@ -59,18 +230,45 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "litrs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + [[package]] name = "proc-macro2" version = "1.0.101" @@ -143,6 +341,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.106" @@ -179,3 +383,9 @@ name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/Cargo.toml b/Cargo.toml index aa989784..379ac1dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ members = [ "aimdb-core", "aimdb-connectors", "aimdb-adapters", + "aimdb-embassy-adapter", "tools/aimdb-cli", "examples", ] @@ -45,6 +46,15 @@ clap = { version = "4.0", features = ["derive"] } # Development dependencies tokio-test = "0.4" +# Embassy ecosystem for embedded async +embassy-executor = { version = "0.9.1" } +embassy-time = "0.5" + +# Embedded HAL for peripheral abstractions +embedded-hal = "1.0" +embedded-hal-async = "1.0" +embedded-hal-nb = "1.0" + [workspace.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/Makefile b/Makefile index b3929796..7af7f3c6 100644 --- a/Makefile +++ b/Makefile @@ -1,55 +1,92 @@ # AimDB Makefile # Simple automation for common development tasks -.PHONY: help build test clean fmt clippy doc all +.PHONY: help build test clean fmt clippy doc all check test-embedded .DEFAULT_GOAL := help # Colors for output GREEN := \033[0;32m YELLOW := \033[0;33m +BLUE := \033[0;34m +RED := \033[0;31m NC := \033[0m # No Color ## Show available commands help: - @echo "$(GREEN)AimDB Development Commands$(NC)" - @echo "" - @echo " build Build the project" - @echo " test Run all tests" - @echo " fmt Format code" - @echo " clippy Run linter" - @echo " doc Generate docs" - @echo " clean Clean build artifacts" - @echo " check Quick development check (fmt + clippy + test)" - @echo " all Build everything" + @printf "$(GREEN)AimDB Development Commands$(NC)\n" + @printf "\n" + @printf " $(YELLOW)Core Commands:$(NC)\n" + @printf " build Build all components (std + embedded)\n" + @printf " test Run all tests (std + embedded)\n" + @printf " fmt Format code\n" + @printf " clippy Run linter\n" + @printf " doc Generate docs\n" + @printf " clean Clean build artifacts\n" + @printf "\n" + @printf " $(YELLOW)Testing Commands:$(NC)\n" + @printf " check Comprehensive development check (fmt + clippy + all tests)\n" + @printf " test-embedded Test embedded/MCU cross-compilation compatibility\n" + @printf "\n" + @printf " $(YELLOW)Convenience:$(NC)\n" + @printf " all Build everything\n" ## Core commands build: - @echo "$(GREEN)Building AimDB...$(NC)" - cargo build --all-features + @printf "$(GREEN)Building AimDB (all combinations)...$(NC)\n" + @printf "$(YELLOW) → Building default (no_std)$(NC)\n" + cargo build + @printf "$(YELLOW) → Building std components with all features$(NC)\n" + cargo build --workspace --exclude aimdb-embassy-adapter --all-features + @printf "$(YELLOW) → Building embassy adapter (no_std only)$(NC)\n" + cd aimdb-embassy-adapter && cargo build test: - @echo "$(GREEN)Running tests...$(NC)" - cargo test --all-features + @printf "$(GREEN)Running all tests (all combinations)...$(NC)\n" + @printf "$(YELLOW) → Testing default (no_std)$(NC)\n" + cargo test + @printf "$(YELLOW) → Testing std components with all features$(NC)\n" + cargo test --workspace --exclude aimdb-embassy-adapter --all-features + @printf "$(YELLOW) → Testing embassy adapter (no_std only)$(NC)\n" + cd aimdb-embassy-adapter && cargo test fmt: - @echo "$(GREEN)Formatting code...$(NC)" + @printf "$(GREEN)Formatting code...$(NC)\n" cargo fmt --all clippy: - @echo "$(GREEN)Running clippy...$(NC)" - cargo clippy --all-targets --all-features -- -D warnings + @printf "$(GREEN)Running clippy...$(NC)\n" + @printf "$(YELLOW) → Clippy on default (no_std)$(NC)\n" + cargo clippy --all-targets -- -D warnings + @printf "$(YELLOW) → Clippy on std components with all features$(NC)\n" + cargo clippy --workspace --exclude aimdb-embassy-adapter --all-targets --all-features -- -D warnings + @printf "$(YELLOW) → Clippy on embassy adapter$(NC)\n" + cd aimdb-embassy-adapter && cargo clippy --all-targets -- -D warnings doc: - @echo "$(GREEN)Generating documentation...$(NC)" - cargo doc --all-features --no-deps --open + @printf "$(GREEN)Generating documentation...$(NC)\n" + cargo doc --workspace --exclude aimdb-embassy-adapter --all-features --no-deps --open clean: - @echo "$(GREEN)Cleaning...$(NC)" + @printf "$(GREEN)Cleaning...$(NC)\n" cargo clean +## Testing commands +test-embedded: + @printf "$(BLUE)Testing embedded/MCU cross-compilation compatibility...$(NC)\n" + @printf "$(YELLOW) → Checking aimdb-core on thumbv7em-none-eabihf target$(NC)\n" + cd aimdb-core && cargo check --target thumbv7em-none-eabihf --no-default-features + @printf "$(YELLOW) → Checking aimdb-embassy-adapter on thumbv7em-none-eabihf target$(NC)\n" + cd aimdb-embassy-adapter && cargo check --target thumbv7em-none-eabihf + ## Convenience commands -check: fmt clippy test - @echo "$(GREEN)Development checks completed!$(NC)" +check: fmt clippy test test-embedded + @printf "$(GREEN)Comprehensive development checks completed!$(NC)\n" + @printf "$(BLUE)✓ Code formatted$(NC)\n" + @printf "$(BLUE)✓ Linter passed$(NC)\n" + @printf "$(BLUE)✓ All feature combinations tested$(NC)\n" + @printf "$(BLUE)✓ Embedded target compatibility verified$(NC)\n" + + all: build test - @echo "$(GREEN)Build and test completed!$(NC)" + @printf "$(GREEN)Build and test completed!$(NC)\n" diff --git a/aimdb-core/Cargo.toml b/aimdb-core/Cargo.toml index c88eb1de..b4b4bdc0 100644 --- a/aimdb-core/Cargo.toml +++ b/aimdb-core/Cargo.toml @@ -6,8 +6,9 @@ license.workspace = true description = "Core database engine for AimDB - async in-memory storage with real-time synchronization" [features] -default = ["std"] +default = [] std = ["thiserror", "anyhow", "serde_json"] +embedded = [] [dependencies] # Error handling - only for std environments diff --git a/aimdb-core/src/error.rs b/aimdb-core/src/error.rs index 7311c549..f0f0a174 100644 --- a/aimdb-core/src/error.rs +++ b/aimdb-core/src/error.rs @@ -505,6 +505,20 @@ impl core::fmt::Display for DbError { } impl DbError { + // Resource type constants for ResourceUnavailable and ResourceAllocationFailed errors + pub const RESOURCE_TYPE_MEMORY: u8 = 0; + pub const RESOURCE_TYPE_FILE_HANDLE: u8 = 1; + pub const RESOURCE_TYPE_SOCKET: u8 = 2; + pub const RESOURCE_TYPE_BUFFER: u8 = 3; + pub const RESOURCE_TYPE_THREAD: u8 = 4; + pub const RESOURCE_TYPE_MUTEX: u8 = 5; + pub const RESOURCE_TYPE_SEMAPHORE: u8 = 6; + pub const RESOURCE_TYPE_CHANNEL: u8 = 7; + // Reserved range for embedded-specific resources: 8-127 + // Reserved range for connector-specific resources: 128-254 + /// Special resource type code for non-blocking operations that would block + pub const RESOURCE_TYPE_WOULD_BLOCK: u8 = 255; + /// Creates a network timeout error with the specified timeout duration pub fn network_timeout(timeout_ms: u64) -> Self { DbError::NetworkTimeout { @@ -954,9 +968,21 @@ mod tests { "DbError size ({} bytes) exceeds 64-byte limit for embedded targets", size ); + } - // Print size for monitoring + #[test] + #[cfg(feature = "std")] + fn test_error_size_monitoring() { + // Monitor error size for performance tracking in std environments + let size = core::mem::size_of::(); println!("DbError size: {} bytes", size); + + // Also test that std version is within reasonable bounds (higher than no_std) + assert!( + size >= 24, + "DbError std size ({} bytes) unexpectedly small - check feature compilation", + size + ); } #[test] @@ -979,15 +1005,26 @@ mod tests { _resource_type: (), }; - // Test that errors can be formatted - let timeout_msg = format!("{:?}", timeout_error); - let capacity_msg = format!("{:?}", capacity_error); + // Test that errors can be formatted (std only) + #[cfg(feature = "std")] + { + let timeout_msg = format!("{:?}", timeout_error); + let capacity_msg = format!("{:?}", capacity_error); - assert!(timeout_msg.contains("NetworkTimeout")); - assert!(capacity_msg.contains("CapacityExceeded")); + assert!(timeout_msg.contains("NetworkTimeout")); + assert!(capacity_msg.contains("CapacityExceeded")); + } + + // Prevent unused variable warnings in no_std + #[cfg(not(feature = "std"))] + { + let _ = timeout_error; + let _ = capacity_error; + } } #[test] + #[cfg(feature = "std")] fn test_dbresult_usage() { // Test DbResult type alias usage fn example_operation() -> DbResult { @@ -1231,6 +1268,7 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn test_error_code_uniqueness() { // Ensure all error codes are unique use std::collections::HashSet; diff --git a/aimdb-core/src/lib.rs b/aimdb-core/src/lib.rs index 83f75b5b..c7a807fe 100644 --- a/aimdb-core/src/lib.rs +++ b/aimdb-core/src/lib.rs @@ -4,6 +4,8 @@ //! in-memory storage with real-time synchronization across MCU → edge → cloud //! environments. +#![cfg_attr(not(feature = "std"), no_std)] + mod error; // Public API exports diff --git a/aimdb-embassy-adapter/Cargo.toml b/aimdb-embassy-adapter/Cargo.toml new file mode 100644 index 00000000..1778a0b8 --- /dev/null +++ b/aimdb-embassy-adapter/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "aimdb-embassy-adapter" +version.workspace = true +edition.workspace = true +license.workspace = true +description = "Embassy async runtime adapter for AimDB embedded targets" +build = "build.rs" + +[features] +default = [] +# Note: std feature is intentionally kept but will cause build.rs to fail if enabled +# This provides clear error messages when accidentally enabled +std = [] + +[dependencies] +# Core AimDB types - always no_std for Embassy +aimdb-core = { path = "../aimdb-core", default-features = false, features = [ + "embedded", +] } + +# Embassy ecosystem for embedded async +embassy-executor = { workspace = true } +embassy-time = { workspace = true } + +# Embedded HAL for peripheral abstractions +embedded-hal = { workspace = true } +embedded-hal-async = { workspace = true } +embedded-hal-nb = { workspace = true } + +[dev-dependencies] +# For testing on embedded targets +heapless = "0.9.1" diff --git a/aimdb-embassy-adapter/build.rs b/aimdb-embassy-adapter/build.rs new file mode 100644 index 00000000..1b010a7b --- /dev/null +++ b/aimdb-embassy-adapter/build.rs @@ -0,0 +1,56 @@ +//! Build script for aimdb-embassy-adapter +//! +//! This build script enforces no_std compilation for the Embassy adapter. +//! Embassy is designed for embedded targets and should always be no_std. + +use std::env; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + // Check if std feature is enabled via feature unification + let features = env::var("CARGO_FEATURE_STD").is_ok(); + + if features { + println!("cargo:warning=aimdb-embassy-adapter: Skipping implementation due to std feature"); + println!( + "cargo:warning=This crate is designed exclusively for no_std embedded environments" + ); + println!("cargo:warning=To build embassy adapter: cd aimdb-embassy-adapter && cargo build"); + } + + // Always enforce no_std compilation + println!("cargo:rustc-cfg=no_std_enforced"); + + // Set target-specific configurations for embedded targets + let target = env::var("TARGET").unwrap_or_default(); + + if target.contains("thumbv") || target.contains("riscv") || target.contains("arm") { + // For embedded targets, ensure optimizations that help with code size + // Only add --gc-sections if using GNU ld as the linker + let linker_env = format!( + "CARGO_TARGET_{}_LINKER", + target.replace('-', "_").to_uppercase() + ); + let linker = env::var(&linker_env) + .or_else(|_| env::var("RUSTC_LINKER")) + .unwrap_or_default(); + + // Check for GNU ld specifically, avoiding false positives with lld or paths containing 'ld' + let linker_name = linker + .split('/') + .next_back() + .unwrap_or(&linker) + .split('\\') + .next_back() + .unwrap_or(&linker); + + if linker_name == "ld" || linker_name.starts_with("arm-") && linker_name.ends_with("-ld") { + println!("cargo:rustc-link-arg=-Wl,--gc-sections"); + } + println!("cargo:rustc-cfg=embedded_target"); + } + + // Always available in no_std environments + println!("cargo:rustc-cfg=core_available"); +} diff --git a/aimdb-embassy-adapter/src/error.rs b/aimdb-embassy-adapter/src/error.rs new file mode 100644 index 00000000..039846fc --- /dev/null +++ b/aimdb-embassy-adapter/src/error.rs @@ -0,0 +1,481 @@ +//! Embassy-specific error handling support +//! +//! This module provides traits and implementations that add Embassy +//! and embedded-hal specific functionality to AimDB's core error types. +//! +//! Embassy is a no_std async runtime, so this adapter always works in no_std mode +//! and uses the no_std field names from DbError. +//! +//! This crate is excluded from the main workspace to prevent feature unification issues +//! that would enable std mode. Build it separately with: cargo build -p aimdb-embassy-adapter + +use aimdb_core::DbError; +use embedded_hal::i2c; +use embedded_hal::spi; +use embedded_hal_nb::nb; + +// Embassy Error Code Base Values +const SPI_ERROR_BASE: u16 = 0x6100; +const UART_ERROR_BASE: u16 = 0x6200; +const I2C_ERROR_BASE: u16 = 0x6300; +const ADC_ERROR_BASE: u16 = 0x6400; +const GPIO_ERROR_BASE: u16 = 0x6500; +const TIMER_ERROR_BASE: u16 = 0x6600; + +// Component IDs for Embassy hardware +const TIMER_COMPONENT_ID: u8 = 0; +const GPIO_COMPONENT_ID: u8 = 1; +const SPI_COMPONENT_ID: u8 = 2; +const I2C_COMPONENT_ID: u8 = 3; +const UART_COMPONENT_ID: u8 = 4; +const ADC_COMPONENT_ID: u8 = 5; + +// SPI-specific error codes +const SPI_CHIP_SELECT_FAULT: u8 = 0x01; +const SPI_MODE_FAULT: u8 = 0x02; +const SPI_OVERRUN: u8 = 0x04; +const SPI_FRAME_FORMAT: u8 = 0x05; +const SPI_UNKNOWN_ERROR: u8 = 0xFF; + +// I2C-specific error codes +const I2C_BUS_ERROR: u8 = 0x01; +const I2C_ARBITRATION_LOSS: u8 = 0x02; +const I2C_NACK_ADDRESS: u8 = 0x03; +const I2C_NACK_DATA: u8 = 0x04; +const I2C_NACK_UNKNOWN: u8 = 0x05; +const I2C_OVERRUN: u8 = 0x06; +const I2C_UNKNOWN_ERROR: u8 = 0xFF; + +/// Trait that provides Embassy-specific error constructors for DbError +/// +/// This trait provides hardware-specific error creation methods without requiring +/// the core AimDB crate to depend on embassy or embedded-hal. +pub trait EmbassyErrorSupport { + /// Creates an SPI error for Embassy environments (error codes 0x6100-0x61FF) + fn from_spi_error(code: u8) -> Self; + + /// Creates a UART error for Embassy environments (error codes 0x6200-0x62FF) + fn from_uart_error(code: u8) -> Self; + + /// Creates an I2C error for Embassy environments (error codes 0x6300-0x63FF) + fn from_i2c_error(code: u8) -> Self; + + /// Creates an ADC error for Embassy environments (error codes 0x6400-0x64FF) + fn from_adc_error(code: u8) -> Self; + + /// Creates a GPIO error for Embassy environments (error codes 0x6500-0x65FF) + fn from_gpio_error(code: u8) -> Self; + + /// Creates a Timer error for Embassy environments (error codes 0x6600-0x66FF) + fn from_timer_error(code: u8) -> Self; + + /// Converts an SPI error to DbError + fn from_spi_hal_error(error: spi::ErrorKind) -> Self; + + /// Converts an I2C error to DbError + fn from_i2c_hal_error(error: i2c::ErrorKind) -> Self; + + /// Converts a non-blocking error to DbError + fn from_nb_error(error: nb::Error) -> Self + where + E: Into, + Self: Sized; +} + +impl EmbassyErrorSupport for DbError { + /// Creates an SPI error for Embassy environments (error codes 0x6100-0x61FF) + fn from_spi_error(code: u8) -> Self { + DbError::HardwareError { + component: SPI_COMPONENT_ID, + error_code: SPI_ERROR_BASE | (code as u16), + _description: (), + } + } + + /// Creates a UART error for Embassy environments (error codes 0x6200-0x62FF) + fn from_uart_error(code: u8) -> Self { + DbError::HardwareError { + component: UART_COMPONENT_ID, + error_code: UART_ERROR_BASE | (code as u16), + _description: (), + } + } + + /// Creates an I2C error for Embassy environments (error codes 0x6300-0x63FF) + fn from_i2c_error(code: u8) -> Self { + DbError::HardwareError { + component: I2C_COMPONENT_ID, + error_code: I2C_ERROR_BASE | (code as u16), + _description: (), + } + } + + /// Creates an ADC error for Embassy environments (error codes 0x6400-0x64FF) + fn from_adc_error(code: u8) -> Self { + DbError::HardwareError { + component: ADC_COMPONENT_ID, + error_code: ADC_ERROR_BASE | (code as u16), + _description: (), + } + } + + /// Creates a GPIO error for Embassy environments (error codes 0x6500-0x65FF) + fn from_gpio_error(code: u8) -> Self { + DbError::HardwareError { + component: GPIO_COMPONENT_ID, + error_code: GPIO_ERROR_BASE | (code as u16), + _description: (), + } + } + + /// Creates a Timer error for Embassy environments (error codes 0x6600-0x66FF) + fn from_timer_error(code: u8) -> Self { + DbError::HardwareError { + component: TIMER_COMPONENT_ID, + error_code: TIMER_ERROR_BASE | (code as u16), + _description: (), + } + } + + /// Converts an SPI error to DbError + fn from_spi_hal_error(error: spi::ErrorKind) -> Self { + let error_code = match error { + spi::ErrorKind::ChipSelectFault => SPI_CHIP_SELECT_FAULT, + spi::ErrorKind::ModeFault => SPI_MODE_FAULT, + spi::ErrorKind::Overrun => SPI_OVERRUN, + spi::ErrorKind::FrameFormat => SPI_FRAME_FORMAT, + _ => SPI_UNKNOWN_ERROR, // Generic/unknown error + }; + Self::from_spi_error(error_code) + } + + /// Converts an I2C error to DbError + fn from_i2c_hal_error(error: i2c::ErrorKind) -> Self { + let error_code = match error { + i2c::ErrorKind::Bus => I2C_BUS_ERROR, + i2c::ErrorKind::ArbitrationLoss => I2C_ARBITRATION_LOSS, + i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Address) => I2C_NACK_ADDRESS, + i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Data) => I2C_NACK_DATA, + i2c::ErrorKind::NoAcknowledge(i2c::NoAcknowledgeSource::Unknown) => I2C_NACK_UNKNOWN, + i2c::ErrorKind::Overrun => I2C_OVERRUN, + _ => I2C_UNKNOWN_ERROR, // Generic/unknown error + }; + Self::from_i2c_error(error_code) + } + + /// Converts a non-blocking error to DbError + fn from_nb_error(error: nb::Error) -> Self + where + E: Into, + Self: Sized, + { + match error { + nb::Error::Other(e) => e.into(), + nb::Error::WouldBlock => DbError::ResourceUnavailable { + resource_type: DbError::RESOURCE_TYPE_WOULD_BLOCK, + _resource_name: (), + }, + } + } +} + +/// Converter functions for embedded-hal errors to DbError +pub struct EmbassyErrorConverter; + +impl EmbassyErrorConverter { + /// Converts an SPI ErrorKind to DbError + pub fn from_spi(error: spi::ErrorKind) -> DbError { + DbError::from_spi_hal_error(error) + } + + /// Converts an I2C ErrorKind to DbError + pub fn from_i2c(error: i2c::ErrorKind) -> DbError { + DbError::from_i2c_hal_error(error) + } + + /// Converts an nb::Error to DbError + pub fn from_nb(error: nb::Error) -> DbError + where + E: Into, + { + DbError::from_nb_error(error) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_embassy_hardware_error_constructors() { + // Test Embassy-specific const constructors for hardware errors + + // Test SPI error constructor (0x6100-0x61FF range) + let spi_error = DbError::from_spi_error(0x42); + if let DbError::HardwareError { + component, + error_code, + .. + } = spi_error + { + assert_eq!(component, SPI_COMPONENT_ID); + assert_eq!(error_code, SPI_ERROR_BASE | 0x42); + } else { + panic!("Expected HardwareError variant"); + } + + // Test UART error constructor (0x6200-0x62FF range) + let uart_error = DbError::from_uart_error(0x10); + if let DbError::HardwareError { + component, + error_code, + .. + } = uart_error + { + assert_eq!(component, UART_COMPONENT_ID); + assert_eq!(error_code, UART_ERROR_BASE | 0x10); + } else { + panic!("Expected HardwareError variant"); + } + + // Test I2C error constructor (0x6300-0x63FF range) + let i2c_error = DbError::from_i2c_error(0x05); + if let DbError::HardwareError { + component, + error_code, + .. + } = i2c_error + { + assert_eq!(component, I2C_COMPONENT_ID); + assert_eq!(error_code, I2C_ERROR_BASE | 0x05); + } else { + panic!("Expected HardwareError variant"); + } + + // Test ADC error constructor (0x6400-0x64FF range) + let adc_error = DbError::from_adc_error(0x20); + if let DbError::HardwareError { + component, + error_code, + .. + } = adc_error + { + assert_eq!(component, ADC_COMPONENT_ID); + assert_eq!(error_code, ADC_ERROR_BASE | 0x20); + } else { + panic!("Expected HardwareError variant"); + } + + // Test GPIO error constructor (0x6500-0x65FF range) + let gpio_error = DbError::from_gpio_error(0x08); + if let DbError::HardwareError { + component, + error_code, + .. + } = gpio_error + { + assert_eq!(component, GPIO_COMPONENT_ID); + assert_eq!(error_code, GPIO_ERROR_BASE | 0x08); + } else { + panic!("Expected HardwareError variant"); + } + + // Test Timer error constructor (0x6600-0x66FF range) + let timer_error = DbError::from_timer_error(0xFF); + if let DbError::HardwareError { + component, + error_code, + .. + } = timer_error + { + assert_eq!(component, TIMER_COMPONENT_ID); + assert_eq!(error_code, TIMER_ERROR_BASE | 0xFF); + } else { + panic!("Expected HardwareError variant"); + } + } + + #[test] + fn test_embedded_hal_error_conversions() { + use crate::EmbassyErrorConverter; + + // Test SPI error conversions + let spi_overrun = EmbassyErrorConverter::from_spi(spi::ErrorKind::Overrun); + if let DbError::HardwareError { + component, + error_code, + .. + } = spi_overrun + { + assert_eq!(component, SPI_COMPONENT_ID); + assert_eq!(error_code, SPI_ERROR_BASE | SPI_OVERRUN as u16); + } else { + panic!("Expected HardwareError variant"); + } + + let spi_mode_fault = EmbassyErrorConverter::from_spi(spi::ErrorKind::ModeFault); + if let DbError::HardwareError { + component, + error_code, + .. + } = spi_mode_fault + { + assert_eq!(component, SPI_COMPONENT_ID); + assert_eq!(error_code, SPI_ERROR_BASE | SPI_MODE_FAULT as u16); + } else { + panic!("Expected HardwareError variant"); + } + + // Test I2C error conversions + let i2c_bus_error = EmbassyErrorConverter::from_i2c(i2c::ErrorKind::Bus); + if let DbError::HardwareError { + component, + error_code, + .. + } = i2c_bus_error + { + assert_eq!(component, I2C_COMPONENT_ID); + assert_eq!(error_code, I2C_ERROR_BASE | I2C_BUS_ERROR as u16); + } else { + panic!("Expected HardwareError variant"); + } + + let i2c_nack = EmbassyErrorConverter::from_i2c(i2c::ErrorKind::NoAcknowledge( + i2c::NoAcknowledgeSource::Address, + )); + if let DbError::HardwareError { + component, + error_code, + .. + } = i2c_nack + { + assert_eq!(component, I2C_COMPONENT_ID); + assert_eq!(error_code, I2C_ERROR_BASE | I2C_NACK_ADDRESS as u16); + } else { + panic!("Expected HardwareError variant"); + } + } + + #[test] + fn test_nb_error_conversion() { + // Test nb::Error::WouldBlock conversion (use DbError directly since it can convert to itself) + let would_block: nb::Error = nb::Error::WouldBlock; + let db_error = DbError::from_nb_error(would_block); + + if let DbError::ResourceUnavailable { resource_type, .. } = db_error { + assert_eq!(resource_type, DbError::RESOURCE_TYPE_WOULD_BLOCK); + } else { + panic!("Expected ResourceUnavailable variant"); + } + + // Test nb::Error::Other conversion with a known error + let known_error = DbError::from_spi_error(0x42); + let nb_other_error: nb::Error = nb::Error::Other(known_error); + let converted_error = DbError::from_nb_error(nb_other_error); + + // The converted error should have the same code as the original (0x6001 for all hardware errors) + assert_eq!(converted_error.error_code(), 0x6001); + } + + #[test] + fn test_embassy_error_code_ranges() { + // Test that Embassy hardware errors are properly categorized + // Note: All HardwareError variants return 0x6001 from error_code(), + // but have different component-specific error_code fields + + let spi_error = DbError::from_spi_error(0x00); + assert_eq!(spi_error.error_category(), 0x6000); // Hardware category + assert_eq!(spi_error.error_code(), 0x6001); // All hardware errors use this unified code + + let uart_error = DbError::from_uart_error(0x00); + assert_eq!(uart_error.error_category(), 0x6000); // Hardware category + assert_eq!(uart_error.error_code(), 0x6001); + + let i2c_error = DbError::from_i2c_error(0x00); + assert_eq!(i2c_error.error_category(), 0x6000); // Hardware category + assert_eq!(i2c_error.error_code(), 0x6001); + + let adc_error = DbError::from_adc_error(0x00); + assert_eq!(adc_error.error_category(), 0x6000); // Hardware category + assert_eq!(adc_error.error_code(), 0x6001); + + let gpio_error = DbError::from_gpio_error(0x00); + assert_eq!(gpio_error.error_category(), 0x6000); // Hardware category + assert_eq!(gpio_error.error_code(), 0x6001); + + let timer_error = DbError::from_timer_error(0x00); + assert_eq!(timer_error.error_category(), 0x6000); // Hardware category + assert_eq!(timer_error.error_code(), 0x6001); + } + + #[test] + fn test_converter_functions() { + // Test EmbassyErrorConverter functions + let spi_error = EmbassyErrorConverter::from_spi(spi::ErrorKind::Overrun); + if let DbError::HardwareError { + component, + error_code, + .. + } = spi_error + { + assert_eq!(component, SPI_COMPONENT_ID); + assert_eq!(error_code, SPI_ERROR_BASE | SPI_OVERRUN as u16); // component-specific code + } else { + panic!("Expected HardwareError variant"); + } + // But the unified error code is always 0x6001 + assert_eq!(spi_error.error_code(), 0x6001); + + let i2c_error = EmbassyErrorConverter::from_i2c(i2c::ErrorKind::Bus); + if let DbError::HardwareError { + component, + error_code, + .. + } = i2c_error + { + assert_eq!(component, I2C_COMPONENT_ID); + assert_eq!(error_code, I2C_ERROR_BASE | I2C_BUS_ERROR as u16); // component-specific code + } else { + panic!("Expected HardwareError variant"); + } + // But the unified error code is always 0x6001 + assert_eq!(i2c_error.error_code(), 0x6001); + + // Test nb error conversion + let would_block: nb::Error = nb::Error::WouldBlock; + let db_error = EmbassyErrorConverter::from_nb(would_block); + if let DbError::ResourceUnavailable { resource_type, .. } = db_error { + assert_eq!(resource_type, DbError::RESOURCE_TYPE_WOULD_BLOCK); + } else { + panic!("Expected ResourceUnavailable variant"); + } + } + + #[test] + fn test_error_construction() { + // Test that constructors work correctly at runtime + + let spi_error = DbError::from_spi_error(0x01); + let uart_error = DbError::from_uart_error(0x02); + let i2c_error = DbError::from_i2c_error(0x03); + let adc_error = DbError::from_adc_error(0x04); + let gpio_error = DbError::from_gpio_error(0x05); + let timer_error = DbError::from_timer_error(0x06); + + // Verify the construction worked correctly (all hardware errors use 0x6001) + assert_eq!(spi_error.error_code(), 0x6001); + assert_eq!(uart_error.error_code(), 0x6001); + assert_eq!(i2c_error.error_code(), 0x6001); + assert_eq!(adc_error.error_code(), 0x6001); + assert_eq!(gpio_error.error_code(), 0x6001); + assert_eq!(timer_error.error_code(), 0x6001); + + // Verify they're all hardware errors + assert!(spi_error.is_hardware_error()); + assert!(uart_error.is_hardware_error()); + assert!(i2c_error.is_hardware_error()); + assert!(adc_error.is_hardware_error()); + assert!(gpio_error.is_hardware_error()); + assert!(timer_error.is_hardware_error()); + } +} diff --git a/aimdb-embassy-adapter/src/lib.rs b/aimdb-embassy-adapter/src/lib.rs new file mode 100644 index 00000000..63d00188 --- /dev/null +++ b/aimdb-embassy-adapter/src/lib.rs @@ -0,0 +1,65 @@ +//! Embassy Adapter for AimDB +//! +//! This crate provides Embassy-specific extensions for AimDB, enabling the database +//! to run on embedded systems using the Embassy async runtime and embedded-hal +//! peripheral abstractions. +//! +//! # Features +//! +//! - **Embassy Integration**: Seamless integration with Embassy async executor +//! - **Hardware Abstraction**: Support for SPI, I2C, UART, ADC, GPIO, and Timer peripherals +//! - **Error Handling**: Embassy-specific error conversions and handling +//! - **No-std Compatible**: Designed for resource-constrained embedded systems +//! +//! # Architecture +//! +//! Embassy is a no_std async runtime, so this adapter is designed for embedded +//! environments and works with the no_std version of aimdb-core by default. +//! +//! The adapter extends AimDB's core functionality without requiring embassy +//! dependencies in the core crate. It provides: +//! +//! - Hardware error constructors for common MCU peripherals +//! - Automatic conversions from embedded-hal error types +//! - Embassy-specific const functions for zero-overhead error handling +//! +//! # Usage +//! +//! ```rust,no_run +//! // This example only works when std feature is not enabled +//! // Embassy adapter is designed exclusively for no_std environments +//! # #[cfg(not(feature = "std"))] +//! # { +//! use aimdb_core::DbError; +//! use aimdb_embassy_adapter::{EmbassyErrorSupport, EmbassyErrorConverter}; +//! +//! // Create hardware-specific errors +//! let spi_error = DbError::from_spi_error(0x42); +//! let i2c_error = DbError::from_i2c_error(0x10); +//! +//! // Convert from embedded-hal errors using the converter +//! let hal_error = embedded_hal::spi::ErrorKind::Overrun; +//! let db_error = EmbassyErrorConverter::from_spi(hal_error); +//! # } +//! ``` +//! +//! # Error Code Ranges +//! +//! The adapter uses specific error code ranges for different peripherals: +//! +//! - **SPI**: 0x6100-0x61FF +//! - **UART**: 0x6200-0x62FF +//! - **I2C**: 0x6300-0x63FF +//! - **ADC**: 0x6400-0x64FF +//! - **GPIO**: 0x6500-0x65FF +//! - **Timer**: 0x6600-0x66FF + +#![no_std] + +// Only include the implementation when std feature is not enabled +// Embassy adapter is designed exclusively for no_std environments +#[cfg(not(feature = "std"))] +mod error; + +#[cfg(not(feature = "std"))] +pub use error::{EmbassyErrorConverter, EmbassyErrorSupport};