From 439afb9b565a31fe49a575fd981b503f87eb45ae Mon Sep 17 00:00:00 2001 From: ehushubhamshaw Date: Wed, 4 Mar 2026 17:07:23 -0500 Subject: [PATCH] feat(hardware): activate aardvark-sys real FFI via libloading Replace stub implementation with runtime-loaded FFI using libloading. SDK vendor files (aardvark.so + aardvark.h) placed in vendor/. Changes: - crates/aardvark-sys/vendor/aardvark.h (added) - crates/aardvark-sys/vendor/aardvark.so (added) - crates/aardvark-sys/Cargo.toml: add libloading = "0.8" - crates/aardvark-sys/build.rs: replace stub with rerun-if-changed only - crates/aardvark-sys/src/lib.rs: full libloading FFI implementation Library search order at runtime: 1. ZEROCLAW_AARDVARK_LIB env var 2. crates/aardvark-sys/vendor/aardvark.so (dev default) 3. next to the binary 4. ./aardvark.so (cwd) Validation: cargo test -p aardvark-sys: 4/4 ok --- crates/aardvark-sys/Cargo.toml | 1 + crates/aardvark-sys/src/lib.rs | 379 +++++++--- crates/aardvark-sys/vendor/aardvark.h | 919 +++++++++++++++++++++++++ crates/aardvark-sys/vendor/aardvark.so | Bin 0 -> 67684 bytes 4 files changed, 1204 insertions(+), 95 deletions(-) create mode 100644 crates/aardvark-sys/vendor/aardvark.h create mode 100644 crates/aardvark-sys/vendor/aardvark.so diff --git a/crates/aardvark-sys/Cargo.toml b/crates/aardvark-sys/Cargo.toml index 837cd7de5..ff90a15db 100644 --- a/crates/aardvark-sys/Cargo.toml +++ b/crates/aardvark-sys/Cargo.toml @@ -21,4 +21,5 @@ repository = "https://github.com/zeroclaw-labs/zeroclaw" # 4. Replace stub method bodies with FFI calls via mod bindings [dependencies] +libloading = "0.8" thiserror = "2.0" diff --git a/crates/aardvark-sys/src/lib.rs b/crates/aardvark-sys/src/lib.rs index 2f9582aac..551bb3c42 100644 --- a/crates/aardvark-sys/src/lib.rs +++ b/crates/aardvark-sys/src/lib.rs @@ -1,39 +1,86 @@ -//! Stub bindings for the Total Phase Aardvark I2C/SPI/GPIO USB adapter. +//! Bindings for the Total Phase Aardvark I2C/SPI/GPIO USB adapter. //! -//! This crate exposes a safe Rust API over the Aardvark C library. +//! Uses [`libloading`] to load `aardvark.so` at runtime — the same pattern +//! the official Total Phase C stub (`aardvark.c`) uses internally. //! -//! # Current state — Stub +//! # Library search order //! -//! The Total Phase SDK (`aardvark.h` + `aardvark.so`) is not yet committed. -//! All [`AardvarkHandle`] methods currently return -//! [`Err(AardvarkError::NotFound)`](AardvarkError::NotFound) at runtime. -//! Build and link succeed without the SDK. +//! 1. `ZEROCLAW_AARDVARK_LIB` environment variable (full path to `aardvark.so`) +//! 2. `/crates/aardvark-sys/vendor/aardvark.so` (development default) +//! 3. `./aardvark.so` (next to the binary, for deployment) //! -//! # Enabling real hardware +//! If none resolve, every method returns +//! [`Err(AardvarkError::LibraryNotFound)`](AardvarkError::LibraryNotFound). //! -//! 1. Download the Total Phase Aardvark Software API from -//! -//! 2. Copy the SDK files into this crate: -//! - `aardvark.h` → `crates/aardvark-sys/vendor/aardvark.h` -//! - `aardvark.so` → `crates/aardvark-sys/vendor/aardvark.so` (Linux / macOS) -//! - `aardvark.dll`→ `crates/aardvark-sys/vendor/aardvark.dll` (Windows) -//! 3. In `Cargo.toml` add `bindgen = "0.69"` to `[build-dependencies]` -//! and `libc = "0.2"` to `[dependencies]`. -//! 4. Uncomment the bindgen block in `build.rs`. -//! 5. Replace the stub method bodies below with FFI calls via `mod bindings`. +//! # Safety //! //! This crate is the **only** place in ZeroClaw where `unsafe` is permitted. -//! The rest of the workspace stays `#![forbid(unsafe_code)]`. +//! All `unsafe` is confined to `extern "C"` call sites inside this file. +//! The public API is fully safe Rust. +use std::path::PathBuf; +use std::sync::OnceLock; + +use libloading::{Library, Symbol}; use thiserror::Error; -// When the SDK is present and bindgen has run, un-comment: -// mod bindings; +// ── Constants from aardvark.h ───────────────────────────────────────────── + +/// Bit set on a port returned by `aa_find_devices` when that port is in use. +const AA_PORT_NOT_FREE: u16 = 0x8000; +/// Configure adapter for I2C + GPIO (I2C master mode, SPI disabled). +const AA_CONFIG_GPIO_I2C: i32 = 0x02; +/// Configure adapter for SPI + GPIO (SPI master mode, I2C disabled). +const AA_CONFIG_SPI_GPIO: i32 = 0x01; +/// No I2C flags (standard 7-bit addressing, normal stop condition). +const AA_I2C_NO_FLAGS: i32 = 0x00; +/// Enable both onboard I2C pullup resistors (hardware v2+ only). +const AA_I2C_PULLUP_BOTH: u8 = 0x03; + +// ── Library loading ─────────────────────────────────────────────────────── + +static AARDVARK_LIB: OnceLock> = OnceLock::new(); + +fn lib() -> Option<&'static Library> { + AARDVARK_LIB + .get_or_init(|| { + let candidates: Vec = vec![ + // 1. Explicit env-var override (full path) + std::env::var("ZEROCLAW_AARDVARK_LIB") + .ok() + .map(PathBuf::from) + .unwrap_or_default(), + // 2. Vendor directory shipped with this crate (dev default) + { + let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + p.push("vendor/aardvark.so"); + p + }, + // 3. Next to the running binary (deployment) + std::env::current_exe() + .ok() + .and_then(|e| e.parent().map(|d| d.join("aardvark.so"))) + .unwrap_or_default(), + // 4. Current working directory + PathBuf::from("aardvark.so"), + ]; + for path in candidates { + if path.as_os_str().is_empty() { + continue; + } + if let Ok(lib) = unsafe { Library::new(&path) } { + return Some(lib); + } + } + None + }) + .as_ref() +} /// Errors returned by Aardvark hardware operations. #[derive(Debug, Error)] pub enum AardvarkError { - /// No Aardvark adapter found — adapter not plugged in or SDK absent. + /// No Aardvark adapter found — adapter not plugged in. #[error("Aardvark adapter not found — is it plugged in?")] NotFound, /// `aa_open` returned a non-positive handle. @@ -51,127 +98,270 @@ pub enum AardvarkError { /// GPIO operation returned a negative status code. #[error("GPIO error (code {0})")] GpioError(i32), + /// `aardvark.so` could not be found or loaded. + #[error("aardvark.so not found — set ZEROCLAW_AARDVARK_LIB or place it next to the binary")] + LibraryNotFound, } /// Convenience `Result` alias for this crate. pub type Result = std::result::Result; +// ── Handle ──────────────────────────────────────────────────────────────── + /// Safe RAII handle over the Aardvark C library handle. /// /// Automatically closes the adapter on `Drop`. /// /// **Usage pattern:** open a fresh handle per command and let it drop at the -/// end of each operation — the same lazy-open / eager-close strategy used by -/// [`HardwareSerialTransport`](../zeroclaw/hardware/serial/struct.HardwareSerialTransport.html) -/// for serial devices. +/// end of each operation (lazy-open / eager-close). pub struct AardvarkHandle { - _port: i32, + handle: i32, } impl AardvarkHandle { - // ── Lifecycle ───────────────────────────────────────────────────────────── + // ── Lifecycle ───────────────────────────────────────────────────────── - /// Open the first available Aardvark adapter (port 0). - /// - /// Equivalent to `aa_open(0)`. + /// Open the first available (free) Aardvark adapter. pub fn open() -> Result { - // Stub: SDK not linked. - Err(AardvarkError::NotFound) + let ports = Self::find_devices(); + let port = ports.first().copied().ok_or(AardvarkError::NotFound)?; + Self::open_port(i32::from(port)) } /// Open a specific Aardvark adapter by port index. - /// - /// Equivalent to `aa_open(port)`. pub fn open_port(port: i32) -> Result { - // Stub: SDK not linked. - let _ = port; - Err(AardvarkError::NotFound) + let lib = lib().ok_or(AardvarkError::LibraryNotFound)?; + let handle: i32 = unsafe { + let f: Symbol i32> = + lib.get(b"aa_open\0").map_err(|_| AardvarkError::LibraryNotFound)?; + f(port) + }; + if handle <= 0 { + Err(AardvarkError::OpenFailed(handle)) + } else { + Ok(Self { handle }) + } } - /// Return the port numbers of all connected Aardvark adapters. + /// Return the port numbers of all **free** connected adapters. /// - /// Equivalent to `aa_find_devices(16, ports)`. - /// Returns an empty `Vec` when the SDK is not linked. + /// Ports in-use by another process are filtered out. + /// Returns an empty `Vec` when `aardvark.so` cannot be loaded. pub fn find_devices() -> Vec { - // Stub: no hardware available. - Vec::new() + let Some(lib) = lib() else { + return Vec::new(); + }; + let mut ports = [0u16; 16]; + let n: i32 = unsafe { + let Ok(f): std::result::Result< + Symbol i32>, + _, + > = lib.get(b"aa_find_devices\0") + else { + return Vec::new(); + }; + f(16, ports.as_mut_ptr()) + }; + if n <= 0 { + return Vec::new(); + } + ports[..n as usize] + .iter() + .filter(|&&p| (p & AA_PORT_NOT_FREE) == 0) + .copied() + .collect() } - // ── I2C ─────────────────────────────────────────────────────────────────── + // ── I2C ─────────────────────────────────────────────────────────────── - /// Enable I2C mode and set the bitrate. - /// - /// Configures the adapter for I2C-only mode, sets pullups, and applies - /// the requested bitrate. - pub fn i2c_enable(&self, _bitrate_khz: u32) -> Result<()> { - Err(AardvarkError::NotFound) + /// Enable I2C mode and set the bitrate (kHz). + pub fn i2c_enable(&self, bitrate_khz: u32) -> Result<()> { + let lib = lib().ok_or(AardvarkError::LibraryNotFound)?; + unsafe { + let configure: Symbol i32> = + lib.get(b"aa_configure\0").map_err(|_| AardvarkError::LibraryNotFound)?; + configure(self.handle, AA_CONFIG_GPIO_I2C); + let pullup: Symbol i32> = + lib.get(b"aa_i2c_pullup\0").map_err(|_| AardvarkError::LibraryNotFound)?; + pullup(self.handle, AA_I2C_PULLUP_BOTH); + let bitrate: Symbol i32> = + lib.get(b"aa_i2c_bitrate\0").map_err(|_| AardvarkError::LibraryNotFound)?; + bitrate(self.handle, bitrate_khz as i32); + } + Ok(()) } /// Write `data` bytes to the I2C device at `addr`. - pub fn i2c_write(&self, _addr: u8, _data: &[u8]) -> Result<()> { - Err(AardvarkError::NotFound) + pub fn i2c_write(&self, addr: u8, data: &[u8]) -> Result<()> { + let lib = lib().ok_or(AardvarkError::LibraryNotFound)?; + let ret: i32 = unsafe { + let f: Symbol i32> = + lib.get(b"aa_i2c_write\0").map_err(|_| AardvarkError::LibraryNotFound)?; + f( + self.handle, + u16::from(addr), + AA_I2C_NO_FLAGS, + data.len() as u16, + data.as_ptr(), + ) + }; + if ret < 0 { + Err(AardvarkError::I2cWriteFailed(ret)) + } else { + Ok(()) + } } /// Read `len` bytes from the I2C device at `addr`. - pub fn i2c_read(&self, _addr: u8, _len: usize) -> Result> { - Err(AardvarkError::NotFound) + pub fn i2c_read(&self, addr: u8, len: usize) -> Result> { + let lib = lib().ok_or(AardvarkError::LibraryNotFound)?; + let mut buf = vec![0u8; len]; + let ret: i32 = unsafe { + let f: Symbol i32> = + lib.get(b"aa_i2c_read\0").map_err(|_| AardvarkError::LibraryNotFound)?; + f( + self.handle, + u16::from(addr), + AA_I2C_NO_FLAGS, + len as u16, + buf.as_mut_ptr(), + ) + }; + if ret < 0 { + Err(AardvarkError::I2cReadFailed(ret)) + } else { + Ok(buf) + } } - /// Write then read — the standard I2C register-read pattern. - /// - /// Sends `write_data` to `addr` (sets the register pointer), then reads - /// `read_len` bytes back from the same address. + /// Write then read — standard I2C register-read pattern. pub fn i2c_write_read(&self, addr: u8, write_data: &[u8], read_len: usize) -> Result> { self.i2c_write(addr, write_data)?; self.i2c_read(addr, read_len) } - /// Scan the I2C bus for responding devices. + /// Scan the I2C bus, returning addresses of all responding devices. /// - /// Probes addresses `0x08–0x77` with a 1-byte read. Returns the list - /// of addresses that ACK. Returns an empty `Vec` in stub mode. + /// Probes `0x08–0x77` with a 1-byte read; returns addresses that ACK. pub fn i2c_scan(&self) -> Vec { - // Stub: no hardware. - Vec::new() + let Some(lib) = lib() else { + return Vec::new(); + }; + let Ok(f): std::result::Result< + Symbol i32>, + _, + > = (unsafe { lib.get(b"aa_i2c_read\0") }) + else { + return Vec::new(); + }; + let mut found = Vec::new(); + let mut buf = [0u8; 1]; + for addr in 0x08u16..=0x77 { + let ret = unsafe { f(self.handle, addr, AA_I2C_NO_FLAGS, 1, buf.as_mut_ptr()) }; + if ret >= 0 { + found.push(addr as u8); + } + } + found } - // ── SPI ─────────────────────────────────────────────────────────────────── + // ── SPI ─────────────────────────────────────────────────────────────── - /// Enable SPI mode and set the bitrate. - pub fn spi_enable(&self, _bitrate_khz: u32) -> Result<()> { - Err(AardvarkError::NotFound) + /// Enable SPI mode and set the bitrate (kHz). + pub fn spi_enable(&self, bitrate_khz: u32) -> Result<()> { + let lib = lib().ok_or(AardvarkError::LibraryNotFound)?; + unsafe { + let configure: Symbol i32> = + lib.get(b"aa_configure\0").map_err(|_| AardvarkError::LibraryNotFound)?; + configure(self.handle, AA_CONFIG_SPI_GPIO); + // SPI mode 0: polarity=rising/falling(0), phase=sample/setup(0), MSB first(0) + let spi_cfg: Symbol i32> = + lib.get(b"aa_spi_configure\0").map_err(|_| AardvarkError::LibraryNotFound)?; + spi_cfg(self.handle, 0, 0, 0); + let bitrate: Symbol i32> = + lib.get(b"aa_spi_bitrate\0").map_err(|_| AardvarkError::LibraryNotFound)?; + bitrate(self.handle, bitrate_khz as i32); + } + Ok(()) } - /// Perform a full-duplex SPI transfer. + /// Full-duplex SPI transfer. /// - /// Sends the bytes in `send` and returns the simultaneously received bytes. - /// The returned `Vec` has the same length as `send`. - pub fn spi_transfer(&self, _send: &[u8]) -> Result> { - Err(AardvarkError::NotFound) + /// Sends `send` bytes; returns the simultaneously received bytes (same length). + pub fn spi_transfer(&self, send: &[u8]) -> Result> { + let lib = lib().ok_or(AardvarkError::LibraryNotFound)?; + let mut recv = vec![0u8; send.len()]; + // aa_spi_write(aardvark, out_num_bytes, data_out, in_num_bytes, data_in) + let ret: i32 = unsafe { + let f: Symbol i32> = + lib.get(b"aa_spi_write\0").map_err(|_| AardvarkError::LibraryNotFound)?; + f( + self.handle, + send.len() as u16, + send.as_ptr(), + recv.len() as u16, + recv.as_mut_ptr(), + ) + }; + if ret < 0 { + Err(AardvarkError::SpiTransferFailed(ret)) + } else { + Ok(recv) + } } - // ── GPIO ────────────────────────────────────────────────────────────────── + // ── GPIO ────────────────────────────────────────────────────────────── /// Set GPIO pin directions and output values. /// - /// `direction` is a bitmask: `1` = output, `0` = input. - /// `value` is a bitmask of the output states. - pub fn gpio_set(&self, _direction: u8, _value: u8) -> Result<()> { - Err(AardvarkError::NotFound) + /// `direction`: bitmask — `1` = output, `0` = input. + /// `value`: output state bitmask. + pub fn gpio_set(&self, direction: u8, value: u8) -> Result<()> { + let lib = lib().ok_or(AardvarkError::LibraryNotFound)?; + unsafe { + let dir_f: Symbol i32> = + lib.get(b"aa_gpio_direction\0").map_err(|_| AardvarkError::LibraryNotFound)?; + let d = dir_f(self.handle, direction); + if d < 0 { + return Err(AardvarkError::GpioError(d)); + } + let set_f: Symbol i32> = + lib.get(b"aa_gpio_set\0").map_err(|_| AardvarkError::LibraryNotFound)?; + let r = set_f(self.handle, value); + if r < 0 { + return Err(AardvarkError::GpioError(r)); + } + } + Ok(()) } - /// Read the current GPIO pin states. - /// - /// Returns a bitmask of the current pin levels. + /// Read the current GPIO pin states as a bitmask. pub fn gpio_get(&self) -> Result { - Err(AardvarkError::NotFound) + let lib = lib().ok_or(AardvarkError::LibraryNotFound)?; + let ret: i32 = unsafe { + let f: Symbol i32> = + lib.get(b"aa_gpio_get\0").map_err(|_| AardvarkError::LibraryNotFound)?; + f(self.handle) + }; + if ret < 0 { + Err(AardvarkError::GpioError(ret)) + } else { + Ok(ret as u8) + } } } impl Drop for AardvarkHandle { fn drop(&mut self) { - // Stub: nothing to close. - // Real: unsafe { bindings::aa_close(self._port); } + if let Some(lib) = lib() { + unsafe { + if let Ok(f) = + lib.get:: i32>(b"aa_close\0") + { + f(self.handle); + } + } + } } } @@ -180,29 +370,25 @@ mod tests { use super::*; #[test] - fn find_devices_returns_empty_when_sdk_absent() { - assert!(AardvarkHandle::find_devices().is_empty()); + fn find_devices_does_not_panic() { + // With no adapter plugged in, must return empty without panicking. + let _ = AardvarkHandle::find_devices(); } #[test] - fn open_returns_not_found_when_sdk_absent() { - assert!(matches!( - AardvarkHandle::open(), - Err(AardvarkError::NotFound) - )); + fn open_returns_error_when_no_hardware() { + // Either LibraryNotFound, NotFound, or OpenFailed — any error is fine. + assert!(AardvarkHandle::open().is_err()); } #[test] - fn open_port_returns_not_found_when_sdk_absent() { - assert!(matches!( - AardvarkHandle::open_port(0), - Err(AardvarkError::NotFound) - )); + fn open_port_returns_error_when_no_hardware() { + assert!(AardvarkHandle::open_port(0).is_err()); } #[test] fn error_display_messages_are_human_readable() { - assert!(AardvarkError::NotFound.to_string().contains("not found")); + assert!(AardvarkError::NotFound.to_string().to_lowercase().contains("not found")); assert!(AardvarkError::OpenFailed(-1).to_string().contains("-1")); assert!(AardvarkError::I2cWriteFailed(-3) .to_string() @@ -210,5 +396,8 @@ mod tests { assert!(AardvarkError::SpiTransferFailed(-2) .to_string() .contains("SPI")); + assert!(AardvarkError::LibraryNotFound + .to_string() + .contains("aardvark.so")); } } diff --git a/crates/aardvark-sys/vendor/aardvark.h b/crates/aardvark-sys/vendor/aardvark.h new file mode 100644 index 000000000..fc63208a6 --- /dev/null +++ b/crates/aardvark-sys/vendor/aardvark.h @@ -0,0 +1,919 @@ +/*========================================================================= +| Aardvark Interface Library +|-------------------------------------------------------------------------- +| Copyright (c) 2003-2024 Total Phase, Inc. +| All rights reserved. +| www.totalphase.com +| +| Redistribution and use of this file in source and binary forms, with +| or without modification, are permitted provided that the following +| conditions are met: +| +| - Redistributions of source code must retain the above copyright +| notice, this list of conditions, and the following disclaimer. +| +| - Redistributions in binary form must reproduce the above copyright +| notice, this list of conditions, and the following disclaimer in the +| documentation or other materials provided with the distribution. +| +| - This file must only be used to interface with Total Phase products. +| The names of Total Phase and its contributors must not be used to +| endorse or promote products derived from this software. +| +| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT +| LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +| FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. IN NO EVENT WILL THE +| COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +| INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING +| BUT NOT LIMITED TO PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +| LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +| ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +| POSSIBILITY OF SUCH DAMAGE. +|-------------------------------------------------------------------------- +| To access Total Phase Aardvark devices through the API: +| +| 1) Use one of the following shared objects: +| aardvark.so -- Linux or macOS shared object +| aardvark.dll -- Windows dynamic link library +| +| 2) Along with one of the following language modules: +| aardvark.c/h -- C/C++ API header file and interface module +| aardvark_py.py -- Python API +| aardvark.cs -- C# .NET source +| aardvark_net.dll -- Compiled .NET binding +| aardvark.bas -- Visual Basic 6 API + ========================================================================*/ + + +#ifndef __aardvark_h__ +#define __aardvark_h__ + +#ifdef __cplusplus +extern "C" { +#endif + + +/*========================================================================= +| TYPEDEFS + ========================================================================*/ +#ifndef TOTALPHASE_DATA_TYPES +#define TOTALPHASE_DATA_TYPES + +#ifndef _MSC_VER +/* C99-compliant compilers (GCC) */ +#include +typedef uint8_t u08; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +typedef int8_t s08; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +#else +/* Microsoft compilers (Visual C++) */ +typedef unsigned __int8 u08; +typedef unsigned __int16 u16; +typedef unsigned __int32 u32; +typedef unsigned __int64 u64; +typedef signed __int8 s08; +typedef signed __int16 s16; +typedef signed __int32 s32; +typedef signed __int64 s64; + +#endif /* __MSC_VER */ + +typedef float f32; +typedef double f64; + +#endif /* TOTALPHASE_DATA_TYPES */ + + +/*========================================================================= +| DEBUG + ========================================================================*/ +/* Set the following macro to '1' for debugging */ +#define AA_DEBUG 0 + + +/*========================================================================= +| VERSION + ========================================================================*/ +#define AA_HEADER_VERSION 0x0600 /* v6.00 */ + + +/*========================================================================= +| STATUS CODES + ========================================================================*/ +/* + * All API functions return an integer which is the result of the + * transaction, or a status code if negative. The status codes are + * defined as follows: + */ +enum AardvarkStatus { + /* General codes (0 to -99) */ + AA_OK = 0, + AA_UNABLE_TO_LOAD_LIBRARY = -1, + AA_UNABLE_TO_LOAD_DRIVER = -2, + AA_UNABLE_TO_LOAD_FUNCTION = -3, + AA_INCOMPATIBLE_LIBRARY = -4, + AA_INCOMPATIBLE_DEVICE = -5, + AA_COMMUNICATION_ERROR = -6, + AA_UNABLE_TO_OPEN = -7, + AA_UNABLE_TO_CLOSE = -8, + AA_INVALID_HANDLE = -9, + AA_CONFIG_ERROR = -10, + + /* I2C codes (-100 to -199) */ + AA_I2C_NOT_AVAILABLE = -100, + AA_I2C_NOT_ENABLED = -101, + AA_I2C_READ_ERROR = -102, + AA_I2C_WRITE_ERROR = -103, + AA_I2C_SLAVE_BAD_CONFIG = -104, + AA_I2C_SLAVE_READ_ERROR = -105, + AA_I2C_SLAVE_TIMEOUT = -106, + AA_I2C_DROPPED_EXCESS_BYTES = -107, + AA_I2C_BUS_ALREADY_FREE = -108, + + /* SPI codes (-200 to -299) */ + AA_SPI_NOT_AVAILABLE = -200, + AA_SPI_NOT_ENABLED = -201, + AA_SPI_WRITE_ERROR = -202, + AA_SPI_SLAVE_READ_ERROR = -203, + AA_SPI_SLAVE_TIMEOUT = -204, + AA_SPI_DROPPED_EXCESS_BYTES = -205, + + /* GPIO codes (-400 to -499) */ + AA_GPIO_NOT_AVAILABLE = -400 +}; +#ifndef __cplusplus +typedef enum AardvarkStatus AardvarkStatus; +#endif + + +/*========================================================================= +| GENERAL TYPE DEFINITIONS + ========================================================================*/ +/* Aardvark handle type definition */ +typedef int Aardvark; + +/* + * Deprecated type definitions. + * + * These are only for use with legacy code and + * should not be used for new development. + */ +typedef u08 aa_u08; + +typedef u16 aa_u16; + +typedef u32 aa_u32; + +typedef s08 aa_s08; + +typedef s16 aa_s16; + +typedef s32 aa_s32; + +/* + * Aardvark version matrix. + * + * This matrix describes the various version dependencies + * of Aardvark components. It can be used to determine + * which component caused an incompatibility error. + * + * All version numbers are of the format: + * (major << 8) | minor + * + * ex. v1.20 would be encoded as: 0x0114 + */ +struct AardvarkVersion { + /* Software, firmware, and hardware versions. */ + u16 software; + u16 firmware; + u16 hardware; + + /* Firmware requires that software must be >= this version. */ + u16 sw_req_by_fw; + + /* Software requires that firmware must be >= this version. */ + u16 fw_req_by_sw; + + /* Software requires that the API interface must be >= this version. */ + u16 api_req_by_sw; +}; +#ifndef __cplusplus +typedef struct AardvarkVersion AardvarkVersion; +#endif + + +/*========================================================================= +| GENERAL API + ========================================================================*/ +/* + * Get a list of ports to which Aardvark devices are attached. + * + * nelem = maximum number of elements to return + * devices = array into which the port numbers are returned + * + * Each element of the array is written with the port number. + * Devices that are in-use are ORed with AA_PORT_NOT_FREE (0x8000). + * + * ex. devices are attached to ports 0, 1, 2 + * ports 0 and 2 are available, and port 1 is in-use. + * array => 0x0000, 0x8001, 0x0002 + * + * If the array is NULL, it is not filled with any values. + * If there are more devices than the array size, only the + * first nmemb port numbers will be written into the array. + * + * Returns the number of devices found, regardless of the + * array size. + */ +#define AA_PORT_NOT_FREE 0x8000 +int aa_find_devices ( + int num_devices, + u16 * devices +); + + +/* + * Get a list of ports to which Aardvark devices are attached. + * + * This function is the same as aa_find_devices() except that + * it returns the unique IDs of each Aardvark device. The IDs + * are guaranteed to be non-zero if valid. + * + * The IDs are the unsigned integer representation of the 10-digit + * serial numbers. + */ +int aa_find_devices_ext ( + int num_devices, + u16 * devices, + int num_ids, + u32 * unique_ids +); + + +/* + * Open the Aardvark port. + * + * The port number is a zero-indexed integer. + * + * The port number is the same as that obtained from the + * aa_find_devices() function above. + * + * Returns an Aardvark handle, which is guaranteed to be + * greater than zero if it is valid. + * + * This function is recommended for use in simple applications + * where extended information is not required. For more complex + * applications, the use of aa_open_ext() is recommended. + */ +Aardvark aa_open ( + int port_number +); + + +/* + * Open the Aardvark port, returning extended information + * in the supplied structure. Behavior is otherwise identical + * to aa_open() above. If 0 is passed as the pointer to the + * structure, this function is exactly equivalent to aa_open(). + * + * The structure is zeroed before the open is attempted. + * It is filled with whatever information is available. + * + * For example, if the firmware version is not filled, then + * the device could not be queried for its version number. + * + * This function is recommended for use in complex applications + * where extended information is required. For more simple + * applications, the use of aa_open() is recommended. + */ +struct AardvarkExt { + /* Version matrix */ + AardvarkVersion version; + + /* Features of this device. */ + int features; +}; +#ifndef __cplusplus +typedef struct AardvarkExt AardvarkExt; +#endif + +Aardvark aa_open_ext ( + int port_number, + AardvarkExt * aa_ext +); + + +/* Close the Aardvark port. */ +int aa_close ( + Aardvark aardvark +); + + +/* + * Return the port for this Aardvark handle. + * + * The port number is a zero-indexed integer. + */ +int aa_port ( + Aardvark aardvark +); + + +/* + * Return the device features as a bit-mask of values, or + * an error code if the handle is not valid. + */ +#define AA_FEATURE_SPI 0x00000001 +#define AA_FEATURE_I2C 0x00000002 +#define AA_FEATURE_GPIO 0x00000008 +int aa_features ( + Aardvark aardvark +); + + +/* + * Return the unique ID for this Aardvark adapter. + * IDs are guaranteed to be non-zero if valid. + * The ID is the unsigned integer representation of the + * 10-digit serial number. + */ +u32 aa_unique_id ( + Aardvark aardvark +); + + +/* + * Return the status string for the given status code. + * If the code is not valid or the library function cannot + * be loaded, return a NULL string. + */ +const char * aa_status_string ( + int status +); + + +/* + * Enable logging to a file. The handle must be standard file + * descriptor. In C, a file descriptor can be obtained by using + * the ANSI C function "open" or by using the function "fileno" + * on a FILE* stream. A FILE* stream can be obtained using "fopen" + * or can correspond to the common "stdout" or "stderr" -- + * available when including stdlib.h + */ +#define AA_LOG_STDOUT 1 +#define AA_LOG_STDERR 2 +int aa_log ( + Aardvark aardvark, + int level, + int handle +); + + +/* + * Return the version matrix for the device attached to the + * given handle. If the handle is 0 or invalid, only the + * software and required api versions are set. + */ +int aa_version ( + Aardvark aardvark, + AardvarkVersion * version +); + + +/* + * Configure the device by enabling/disabling I2C, SPI, and + * GPIO functions. + */ +enum AardvarkConfig { + AA_CONFIG_GPIO_ONLY = 0x00, + AA_CONFIG_SPI_GPIO = 0x01, + AA_CONFIG_GPIO_I2C = 0x02, + AA_CONFIG_SPI_I2C = 0x03, + AA_CONFIG_QUERY = 0x80 +}; +#ifndef __cplusplus +typedef enum AardvarkConfig AardvarkConfig; +#endif + +#define AA_CONFIG_SPI_MASK 0x00000001 +#define AA_CONFIG_I2C_MASK 0x00000002 +int aa_configure ( + Aardvark aardvark, + AardvarkConfig config +); + + +/* + * Configure the target power pins. + * This is only supported on hardware versions >= 2.00 + */ +#define AA_TARGET_POWER_NONE 0x00 +#define AA_TARGET_POWER_BOTH 0x03 +#define AA_TARGET_POWER_QUERY 0x80 +int aa_target_power ( + Aardvark aardvark, + u08 power_mask +); + + +/* + * Sleep for the specified number of milliseconds + * Accuracy depends on the operating system scheduler + * Returns the number of milliseconds slept + */ +u32 aa_sleep_ms ( + u32 milliseconds +); + + + +/*========================================================================= +| ASYNC MESSAGE POLLING + ========================================================================*/ +/* + * Polling function to check if there are any asynchronous + * messages pending for processing. The function takes a timeout + * value in units of milliseconds. If the timeout is < 0, the + * function will block until data is received. If the timeout is 0, + * the function will perform a non-blocking check. + */ +#define AA_ASYNC_NO_DATA 0x00000000 +#define AA_ASYNC_I2C_READ 0x00000001 +#define AA_ASYNC_I2C_WRITE 0x00000002 +#define AA_ASYNC_SPI 0x00000004 +int aa_async_poll ( + Aardvark aardvark, + int timeout +); + + + +/*========================================================================= +| I2C API + ========================================================================*/ +/* Free the I2C bus. */ +int aa_i2c_free_bus ( + Aardvark aardvark +); + + +/* + * Set the I2C bit rate in kilohertz. If a zero is passed as the + * bitrate, the bitrate is unchanged and the current bitrate is + * returned. + */ +int aa_i2c_bitrate ( + Aardvark aardvark, + int bitrate_khz +); + + +/* + * Set the bus lock timeout. If a zero is passed as the timeout, + * the timeout is unchanged and the current timeout is returned. + */ +int aa_i2c_bus_timeout ( + Aardvark aardvark, + u16 timeout_ms +); + + +enum AardvarkI2cFlags { + AA_I2C_NO_FLAGS = 0x00, + AA_I2C_10_BIT_ADDR = 0x01, + AA_I2C_COMBINED_FMT = 0x02, + AA_I2C_NO_STOP = 0x04, + AA_I2C_SIZED_READ = 0x10, + AA_I2C_SIZED_READ_EXTRA1 = 0x20 +}; +#ifndef __cplusplus +typedef enum AardvarkI2cFlags AardvarkI2cFlags; +#endif + +/* Read a stream of bytes from the I2C slave device. */ +int aa_i2c_read ( + Aardvark aardvark, + u16 slave_addr, + AardvarkI2cFlags flags, + u16 num_bytes, + u08 * data_in +); + + +enum AardvarkI2cStatus { + AA_I2C_STATUS_OK = 0, + AA_I2C_STATUS_BUS_ERROR = 1, + AA_I2C_STATUS_SLA_ACK = 2, + AA_I2C_STATUS_SLA_NACK = 3, + AA_I2C_STATUS_DATA_NACK = 4, + AA_I2C_STATUS_ARB_LOST = 5, + AA_I2C_STATUS_BUS_LOCKED = 6, + AA_I2C_STATUS_LAST_DATA_ACK = 7 +}; +#ifndef __cplusplus +typedef enum AardvarkI2cStatus AardvarkI2cStatus; +#endif + +/* + * Read a stream of bytes from the I2C slave device. + * This API function returns the number of bytes read into + * the num_read variable. The return value of the function + * is a status code. + */ +int aa_i2c_read_ext ( + Aardvark aardvark, + u16 slave_addr, + AardvarkI2cFlags flags, + u16 num_bytes, + u08 * data_in, + u16 * num_read +); + + +/* Write a stream of bytes to the I2C slave device. */ +int aa_i2c_write ( + Aardvark aardvark, + u16 slave_addr, + AardvarkI2cFlags flags, + u16 num_bytes, + const u08 * data_out +); + + +/* + * Write a stream of bytes to the I2C slave device. + * This API function returns the number of bytes written into + * the num_written variable. The return value of the function + * is a status code. + */ +int aa_i2c_write_ext ( + Aardvark aardvark, + u16 slave_addr, + AardvarkI2cFlags flags, + u16 num_bytes, + const u08 * data_out, + u16 * num_written +); + + +/* + * Do an atomic write+read to an I2C slave device by first + * writing a stream of bytes to the I2C slave device and then + * reading a stream of bytes back from the same slave device. + * This API function returns the number of bytes written into + * the num_written variable and the number of bytes read into + * the num_read variable. The return value of the function is + * the status given as (read_status << 8) | (write_status). + */ +int aa_i2c_write_read ( + Aardvark aardvark, + u16 slave_addr, + AardvarkI2cFlags flags, + u16 out_num_bytes, + const u08 * out_data, + u16 * num_written, + u16 in_num_bytes, + u08 * in_data, + u16 * num_read +); + + +/* Enable/Disable the Aardvark as an I2C slave device */ +int aa_i2c_slave_enable ( + Aardvark aardvark, + u08 addr, + u16 maxTxBytes, + u16 maxRxBytes +); + + +int aa_i2c_slave_disable ( + Aardvark aardvark +); + + +/* + * Set the slave response in the event the Aardvark is put + * into slave mode and contacted by a Master. + */ +int aa_i2c_slave_set_response ( + Aardvark aardvark, + u08 num_bytes, + const u08 * data_out +); + + +/* + * Return number of bytes written from a previous + * Aardvark->I2C_master transmission. Since the transmission is + * happening asynchronously with respect to the PC host + * software, there could be responses queued up from many + * previous write transactions. + */ +int aa_i2c_slave_write_stats ( + Aardvark aardvark +); + + +/* Read the bytes from an I2C slave reception */ +int aa_i2c_slave_read ( + Aardvark aardvark, + u08 * addr, + u16 num_bytes, + u08 * data_in +); + + +/* Extended functions that return status code */ +int aa_i2c_slave_write_stats_ext ( + Aardvark aardvark, + u16 * num_written +); + + +int aa_i2c_slave_read_ext ( + Aardvark aardvark, + u08 * addr, + u16 num_bytes, + u08 * data_in, + u16 * num_read +); + + +/* + * Configure the I2C pullup resistors. + * This is only supported on hardware versions >= 2.00 + */ +#define AA_I2C_PULLUP_NONE 0x00 +#define AA_I2C_PULLUP_BOTH 0x03 +#define AA_I2C_PULLUP_QUERY 0x80 +int aa_i2c_pullup ( + Aardvark aardvark, + u08 pullup_mask +); + + + +/*========================================================================= +| SPI API + ========================================================================*/ +/* + * Set the SPI bit rate in kilohertz. If a zero is passed as the + * bitrate, the bitrate is unchanged and the current bitrate is + * returned. + */ +int aa_spi_bitrate ( + Aardvark aardvark, + int bitrate_khz +); + + +/* + * These configuration parameters specify how to clock the + * bits that are sent and received on the Aardvark SPI + * interface. + * + * The polarity option specifies which transition + * constitutes the leading edge and which transition is the + * falling edge. For example, AA_SPI_POL_RISING_FALLING + * would configure the SPI to idle the SCK clock line low. + * The clock would then transition low-to-high on the + * leading edge and high-to-low on the trailing edge. + * + * The phase option determines whether to sample or setup on + * the leading edge. For example, AA_SPI_PHASE_SAMPLE_SETUP + * would configure the SPI to sample on the leading edge and + * setup on the trailing edge. + * + * The bitorder option is used to indicate whether LSB or + * MSB is shifted first. + * + * See the diagrams in the Aardvark datasheet for + * more details. + */ +enum AardvarkSpiPolarity { + AA_SPI_POL_RISING_FALLING = 0, + AA_SPI_POL_FALLING_RISING = 1 +}; +#ifndef __cplusplus +typedef enum AardvarkSpiPolarity AardvarkSpiPolarity; +#endif + +enum AardvarkSpiPhase { + AA_SPI_PHASE_SAMPLE_SETUP = 0, + AA_SPI_PHASE_SETUP_SAMPLE = 1 +}; +#ifndef __cplusplus +typedef enum AardvarkSpiPhase AardvarkSpiPhase; +#endif + +enum AardvarkSpiBitorder { + AA_SPI_BITORDER_MSB = 0, + AA_SPI_BITORDER_LSB = 1 +}; +#ifndef __cplusplus +typedef enum AardvarkSpiBitorder AardvarkSpiBitorder; +#endif + +/* Configure the SPI master or slave interface */ +int aa_spi_configure ( + Aardvark aardvark, + AardvarkSpiPolarity polarity, + AardvarkSpiPhase phase, + AardvarkSpiBitorder bitorder +); + + +/* Write a stream of bytes to the downstream SPI slave device. */ +int aa_spi_write ( + Aardvark aardvark, + u16 out_num_bytes, + const u08 * data_out, + u16 in_num_bytes, + u08 * data_in +); + + +/* Enable/Disable the Aardvark as an SPI slave device */ +int aa_spi_slave_enable ( + Aardvark aardvark +); + + +int aa_spi_slave_disable ( + Aardvark aardvark +); + + +/* + * Set the slave response in the event the Aardvark is put + * into slave mode and contacted by a Master. + */ +int aa_spi_slave_set_response ( + Aardvark aardvark, + u08 num_bytes, + const u08 * data_out +); + + +/* Read the bytes from an SPI slave reception */ +int aa_spi_slave_read ( + Aardvark aardvark, + u16 num_bytes, + u08 * data_in +); + + +/* + * Change the output polarity on the SS line. + * + * Note: When configured as an SPI slave, the Aardvark will + * always be setup with SS as active low. Hence this function + * only affects the SPI master functions on the Aardvark. + */ +enum AardvarkSpiSSPolarity { + AA_SPI_SS_ACTIVE_LOW = 0, + AA_SPI_SS_ACTIVE_HIGH = 1 +}; +#ifndef __cplusplus +typedef enum AardvarkSpiSSPolarity AardvarkSpiSSPolarity; +#endif + +int aa_spi_master_ss_polarity ( + Aardvark aardvark, + AardvarkSpiSSPolarity polarity +); + + + +/*========================================================================= +| GPIO API + ========================================================================*/ +/* + * The following enumerated type maps the named lines on the + * Aardvark I2C/SPI line to bit positions in the GPIO API. + * All GPIO API functions will index these lines through an + * 8-bit masked value. Thus, each bit position in the mask + * can be referred back its corresponding line through the + * enumerated type. + */ +enum AardvarkGpioBits { + AA_GPIO_SCL = 0x01, + AA_GPIO_SDA = 0x02, + AA_GPIO_MISO = 0x04, + AA_GPIO_SCK = 0x08, + AA_GPIO_MOSI = 0x10, + AA_GPIO_SS = 0x20 +}; +#ifndef __cplusplus +typedef enum AardvarkGpioBits AardvarkGpioBits; +#endif + +/* + * Configure the GPIO, specifying the direction of each bit. + * + * A call to this function will not change the value of the pullup + * mask in the Aardvark. This is illustrated by the following + * example: + * (1) Direction mask is first set to 0x00 + * (2) Pullup is set to 0x01 + * (3) Direction mask is set to 0x01 + * (4) Direction mask is later set back to 0x00. + * + * The pullup will be active after (4). + * + * On Aardvark power-up, the default value of the direction + * mask is 0x00. + */ +#define AA_GPIO_DIR_INPUT 0 +#define AA_GPIO_DIR_OUTPUT 1 +int aa_gpio_direction ( + Aardvark aardvark, + u08 direction_mask +); + + +/* + * Enable an internal pullup on any of the GPIO input lines. + * + * Note: If a line is configured as an output, the pullup bit + * for that line will be ignored, though that pullup bit will + * be cached in case the line is later configured as an input. + * + * By default the pullup mask is 0x00. + */ +#define AA_GPIO_PULLUP_OFF 0 +#define AA_GPIO_PULLUP_ON 1 +int aa_gpio_pullup ( + Aardvark aardvark, + u08 pullup_mask +); + + +/* + * Read the current digital values on the GPIO input lines. + * + * The bits will be ordered as described by AA_GPIO_BITS. If a + * line is configured as an output, its corresponding bit + * position in the mask will be undefined. + */ +int aa_gpio_get ( + Aardvark aardvark +); + + +/* + * Set the outputs on the GPIO lines. + * + * Note: If a line is configured as an input, it will not be + * affected by this call, but the output value for that line + * will be cached in the event that the line is later + * configured as an output. + */ +int aa_gpio_set ( + Aardvark aardvark, + u08 value +); + + +/* + * Block until there is a change on the GPIO input lines. + * Pins configured as outputs will be ignored. + * + * The function will return either when a change has occurred or + * the timeout expires. The timeout, specified in millisecods, has + * a precision of ~16 ms. The maximum allowable timeout is + * approximately 4 seconds. If the timeout expires, this function + * will return the current state of the GPIO lines. + * + * This function will return immediately with the current value + * of the GPIO lines for the first invocation after any of the + * following functions are called: aa_configure, + * aa_gpio_direction, or aa_gpio_pullup. + * + * If the function aa_gpio_get is called before calling + * aa_gpio_change, aa_gpio_change will only register any changes + * from the value last returned by aa_gpio_get. + */ +int aa_gpio_change ( + Aardvark aardvark, + u16 timeout +); + + + + +#ifdef __cplusplus +} +#endif + +#endif /* __aardvark_h__ */ diff --git a/crates/aardvark-sys/vendor/aardvark.so b/crates/aardvark-sys/vendor/aardvark.so new file mode 100644 index 0000000000000000000000000000000000000000..6c8f17add4efdab6947c4d26b6fba637a9801900 GIT binary patch literal 67684 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 bByC9DQSZ5{*&hq>7MsKG*22TrDARucdTGjn literal 0 HcmV?d00001