diff --git a/Cargo.toml b/Cargo.toml index 76791d69..2d6cf6d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ libtock_low_level_debug = { path = "apis/low_level_debug" } libtock_platform = { path = "platform" } libtock_proximity = { path = "apis/proximity" } libtock_runtime = { path = "runtime" } +libtock_sound_pressure = {path = "apis/sound_pressure"} libtock_temperature = { path = "apis/temperature" } [profile.dev] diff --git a/apis/sound_pressure/Cargo.toml b/apis/sound_pressure/Cargo.toml new file mode 100644 index 00000000..b714f5d3 --- /dev/null +++ b/apis/sound_pressure/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "libtock_sound_pressure" +version = "0.1.0" +authors = ["Tock Project Developers "] +license = "MIT/Apache-2.0" +edition = "2018" +repository = "https://www.github.com/tock/libtock-rs" +description = "libtock sound pressure driver" + +[dependencies] +libtock_platform = { path = "../../platform" } + +[dev-dependencies] +libtock_unittest = { path = "../../unittest" } diff --git a/apis/sound_pressure/src/lib.rs b/apis/sound_pressure/src/lib.rs new file mode 100644 index 00000000..4d3934a2 --- /dev/null +++ b/apis/sound_pressure/src/lib.rs @@ -0,0 +1,93 @@ +#![no_std] + +use core::cell::Cell; +use libtock_platform::{ + share, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, +}; + +pub struct SoundPressure(S); + +impl SoundPressure { + /// Returns Ok() if the driver was present.This does not necessarily mean + /// that the driver is working. + pub fn exists() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() + } + + /// Initiate a pressure measurement. + /// This function is used both for synchronous and asynchronous readings + pub fn read() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, READ_PRESSURE, 0, 0).to_result() + } + + /// Register an events listener + pub fn register_listener<'share, F: Fn(u32)>( + listener: &'share SoundPressureListener, + subscribe: share::Handle>, + ) -> Result<(), ErrorCode> { + S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) + } + + /// Unregister the events listener + pub fn unregister_listener() { + S::unsubscribe(DRIVER_NUM, 0) + } + + /// Enable sound pressure measurement + pub fn enable() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, 2, 0, 0).to_result() + } + + /// Disable sound pressure measurement + pub fn disable() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, 3, 0, 0).to_result() + } + + /// Initiate a synchronous pressure measurement. + /// Returns Ok(pressure_value) if the operation was successful + /// pressure_value is between 0 and 255 + pub fn read_sync() -> Result { + let pressure_cell: Cell> = Cell::new(None); + let listener = SoundPressureListener(|pressure_val| { + pressure_cell.set(Some(pressure_val)); + }); + share::scope(|subscribe| { + Self::register_listener(&listener, subscribe)?; + Self::read()?; + while pressure_cell.get() == None { + S::yield_wait(); + } + match pressure_cell.get() { + None => Err(ErrorCode::Fail), + Some(pressure_val) => { + if !(0..=256).contains(&pressure_val) { + Err(ErrorCode::Invalid) + } else { + Ok(pressure_val as u8) + } + } + } + }) + } +} + +pub struct SoundPressureListener(pub F); +impl Upcall> for SoundPressureListener { + fn upcall(&self, pressure_val: u32, _arg1: u32, _arg2: u32) { + (self.0)(pressure_val); + } +} + +#[cfg(test)] +mod tests; + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x60006; + +// Command IDs + +const EXISTS: u32 = 0; +const READ_PRESSURE: u32 = 1; diff --git a/apis/sound_pressure/src/tests.rs b/apis/sound_pressure/src/tests.rs new file mode 100644 index 00000000..f44e6fbb --- /dev/null +++ b/apis/sound_pressure/src/tests.rs @@ -0,0 +1,75 @@ +use core::cell::Cell; +use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; +use libtock_unittest::fake; + +type SoundPressure = super::SoundPressure; + +#[test] +fn no_driver() { + let _kernel = fake::Kernel::new(); + assert_eq!(SoundPressure::exists(), Err(ErrorCode::NoDevice)); +} + +#[test] +fn driver_check() { + let kernel = fake::Kernel::new(); + let driver = fake::SoundPressure::new(); + kernel.add_driver(&driver); + + assert_eq!(SoundPressure::exists(), Ok(())); +} + +#[test] +fn driver_busy() { + let kernel = fake::Kernel::new(); + let driver = fake::SoundPressure::new(); + kernel.add_driver(&driver); + + assert_eq!(SoundPressure::read(), Ok(())); + assert!(driver.is_busy()); + + assert_eq!(SoundPressure::read(), Err(ErrorCode::Busy)); + assert_eq!(SoundPressure::read_sync(), Err(ErrorCode::Busy)); +} + +#[test] +fn read_pressure() { + let kernel = fake::Kernel::new(); + let driver = fake::SoundPressure::new(); + kernel.add_driver(&driver); + + let pressure_cell: Cell> = Cell::new(None); + let listener = crate::SoundPressureListener(|pressure_val| { + pressure_cell.set(Some(pressure_val)); + }); + + share::scope(|subscribe| { + assert_eq!(SoundPressure::read(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + assert_eq!( + SoundPressure::register_listener(&listener, subscribe), + Ok(()) + ); + assert_eq!(SoundPressure::read(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(pressure_cell.get(), Some(100)); + + SoundPressure::unregister_listener(); + assert_eq!(SoundPressure::read(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + }); +} + +#[test] +fn read_pressure_sync() { + let kernel = fake::Kernel::new(); + let driver = fake::SoundPressure::new(); + kernel.add_driver(&driver); + + driver.set_value_sync(100); + assert_eq!(SoundPressure::read_sync(), Ok(100)); +} diff --git a/examples/sound_pressure.rs b/examples/sound_pressure.rs new file mode 100644 index 00000000..ba49e660 --- /dev/null +++ b/examples/sound_pressure.rs @@ -0,0 +1,45 @@ +//! This example shows how to use the sound pressure driver. +//! It checks for the sound pressure driver and samples the sensor every second. + +#![no_main] +#![no_std] + +use core::fmt::Write; +use libtock::console::Console; + +use libtock::alarm::{Alarm, Milliseconds}; +use libtock::runtime::{set_main, stack_size}; +use libtock::sound_pressure::SoundPressure; + +set_main! {main} +stack_size! {0x200} + +fn main() { + if SoundPressure::exists().is_err() { + writeln!(Console::writer(), "Sound pressure driver not found").unwrap(); + return; + } + + writeln!(Console::writer(), "Sound pressure driver found").unwrap(); + let enable = SoundPressure::enable(); + match enable { + Ok(()) => { + writeln!(Console::writer(), "Sound pressure driver enabled").unwrap(); + loop { + match SoundPressure::read_sync() { + Ok(sound_pressure_val) => writeln!( + Console::writer(), + "Sound Pressure: {}\n", + sound_pressure_val + ) + .unwrap(), + Err(_) => { + writeln!(Console::writer(), "error while reading sound pressure",).unwrap() + } + } + Alarm::sleep_for(Milliseconds(1000)).unwrap(); + } + } + Err(_e) => writeln!(Console::writer(), "Sound pressure driver enable failed",).unwrap(), + } +} diff --git a/src/lib.rs b/src/lib.rs index fda15c86..1a0b646b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,10 @@ pub mod proximity { use libtock_proximity as proximity; pub type Proximity = proximity::Proximity; } +pub mod sound_pressure { + use libtock_sound_pressure as sound_pressure; + pub type SoundPressure = sound_pressure::SoundPressure; +} pub mod temperature { use libtock_temperature as temperature; pub type Temperature = temperature::Temperature; diff --git a/unittest/src/fake/mod.rs b/unittest/src/fake/mod.rs index 61e0141a..8f958b68 100644 --- a/unittest/src/fake/mod.rs +++ b/unittest/src/fake/mod.rs @@ -17,6 +17,7 @@ mod kernel; mod leds; mod low_level_debug; mod proximity; +mod sound_pressure; mod syscall_driver; mod syscalls; mod temperature; @@ -29,6 +30,7 @@ pub use kernel::Kernel; pub use leds::Leds; pub use low_level_debug::{LowLevelDebug, Message}; pub use proximity::Proximity; +pub use sound_pressure::SoundPressure; pub use syscall_driver::SyscallDriver; pub use syscalls::Syscalls; pub use temperature::Temperature; diff --git a/unittest/src/fake/sound_pressure/mod.rs b/unittest/src/fake/sound_pressure/mod.rs new file mode 100644 index 00000000..84140ca6 --- /dev/null +++ b/unittest/src/fake/sound_pressure/mod.rs @@ -0,0 +1,84 @@ +//! Fake implementation of the SoundPressure API, documented here: +//! +//! Like the real API, `SoundPressure` controls a fake sound pressure sensor. It provides +//! a function `set_value` used to immediately call an upcall with a sound pressure value read by the sensor +//! and a function 'set_value_sync' used to call the upcall when the read command is received. +use crate::{DriverInfo, DriverShareRef}; +use libtock_platform::{CommandReturn, ErrorCode}; +use std::cell::Cell; + +// The `upcall_on_command` field is set to Some(value) if an upcall(with value as its argument) should be called when read command is received, +// or None otherwise. It was needed for testing `read_sync` library function which simulates a synchronous sound pressure read, +// because it was impossible to schedule an upcall during the `synchronous` read in other ways. +pub struct SoundPressure { + busy: Cell, + upcall_on_command: Cell>, + share_ref: DriverShareRef, +} + +impl SoundPressure { + pub fn new() -> std::rc::Rc { + std::rc::Rc::new(SoundPressure { + busy: Cell::new(false), + upcall_on_command: Cell::new(None), + share_ref: Default::default(), + }) + } + + pub fn is_busy(&self) -> bool { + self.busy.get() + } + + pub fn set_value(&self, value: u8) { + if self.busy.get() { + self.share_ref + .schedule_upcall(0, (value as u32, 0, 0)) + .expect("Unable to schedule upcall"); + self.busy.set(false); + } + } + + pub fn set_value_sync(&self, value: u8) { + self.upcall_on_command.set(Some(value)); + } +} + +impl crate::fake::SyscallDriver for SoundPressure { + fn info(&self) -> DriverInfo { + DriverInfo::new(DRIVER_NUM).upcall_count(1) + } + + fn register(&self, share_ref: DriverShareRef) { + self.share_ref.replace(share_ref); + } + + fn command(&self, command_id: u32, _argument0: u32, _argument1: u32) -> CommandReturn { + match command_id { + EXISTS => crate::command_return::success(), + + READ_PRESSURE => { + if self.busy.get() { + return crate::command_return::failure(ErrorCode::Busy); + } + self.busy.set(true); + if let Some(val) = self.upcall_on_command.take() { + self.set_value(val as u8); + } + crate::command_return::success() + } + _ => crate::command_return::failure(ErrorCode::NoSupport), + } + } +} + +#[cfg(test)] +mod tests; +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x60006; + +// Command IDs +const EXISTS: u32 = 0; +const READ_PRESSURE: u32 = 1; diff --git a/unittest/src/fake/sound_pressure/tests.rs b/unittest/src/fake/sound_pressure/tests.rs new file mode 100644 index 00000000..82bed032 --- /dev/null +++ b/unittest/src/fake/sound_pressure/tests.rs @@ -0,0 +1,64 @@ +use crate::fake::{self, SyscallDriver}; +use fake::sound_pressure::*; +use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; + +//Test the command implementation +#[test] +fn command() { + let sound_pressure = SoundPressure::new(); + + assert!(sound_pressure.command(EXISTS, 1, 2).is_success()); + + assert!(sound_pressure.command(READ_PRESSURE, 0, 0).is_success()); + + assert_eq!( + sound_pressure.command(READ_PRESSURE, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + + sound_pressure.set_value(100); + assert!(sound_pressure.command(READ_PRESSURE, 0, 1).is_success()); + sound_pressure.set_value(100); + + sound_pressure.set_value_sync(100); + assert!(sound_pressure.command(READ_PRESSURE, 0, 1).is_success()); + assert!(sound_pressure.command(READ_PRESSURE, 0, 1).is_success()); +} + +// Integration test that verifies SoundPressure works with fake::Kernel and +// libtock_platform::Syscalls. +#[test] +fn kernel_integration() { + use libtock_platform::Syscalls; + let kernel = fake::Kernel::new(); + let sound_pressure = SoundPressure::new(); + kernel.add_driver(&sound_pressure); + assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_PRESSURE, 0, 0).is_success()); + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, READ_PRESSURE, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + sound_pressure.set_value(100); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_PRESSURE, 0, 1).is_success()); + + let listener = Cell::>::new(None); + share::scope(|subscribe| { + assert_eq!( + fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), + Ok(()) + ); + + sound_pressure.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(listener.get(), Some((100,))); + + sound_pressure.set_value(200); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + assert!(fake::Syscalls::command(DRIVER_NUM, READ_PRESSURE, 0, 1).is_success()); + sound_pressure.set_value(200); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(listener.get(), Some((200,))); + }); +}