diff --git a/Cargo.toml b/Cargo.toml index 9a082db8..ac102f85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ libtock_proximity = { path = "apis/proximity" } libtock_runtime = { path = "runtime" } libtock_sound_pressure = {path = "apis/sound_pressure"} libtock_temperature = { path = "apis/temperature" } +libtock_humidity = { path = "apis/humidity" } [profile.dev] panic = "abort" @@ -46,6 +47,7 @@ members = [ "apis/buttons", "apis/buzzer", "apis/console", + "apis/humidity", "apis/leds", "apis/low_level_debug", "apis/ninedof", diff --git a/apis/humidity/Cargo.toml b/apis/humidity/Cargo.toml new file mode 100644 index 00000000..2144127b --- /dev/null +++ b/apis/humidity/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "libtock_humidity" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libtock_platform = { path = "../../platform" } + +[dev-dependencies] +libtock_unittest = { path = "../../unittest" } diff --git a/apis/humidity/src/lib.rs b/apis/humidity/src/lib.rs new file mode 100644 index 00000000..480b993a --- /dev/null +++ b/apis/humidity/src/lib.rs @@ -0,0 +1,88 @@ +#![no_std] + +use core::cell::Cell; +use libtock_platform::{ + share, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, +}; + +pub struct Humidity(S); + +impl Humidity { + /// 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 humidity measurement. + /// + /// This function is used both for synchronous and asynchronous readings + pub fn read() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, READ_HUM, 0, 0).to_result() + } + + /// Register an events listener + pub fn register_listener<'share, F: Fn(u32)>( + listener: &'share HumidityListener, + 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) + } + + /// Initiate a synchronous humidity measurement. + /// Returns Ok(humidity_value) if the operation was successful + /// humidity_value is returned in hundreds of percent + pub fn read_sync() -> Result { + let humidity_cell: Cell> = Cell::new(None); + let listener = HumidityListener(|humidity_val| { + humidity_cell.set(Some(humidity_val)); + }); + share::scope(|subscribe| { + Self::register_listener(&listener, subscribe)?; + Self::read()?; + while humidity_cell.get() == None { + S::yield_wait(); + } + + match humidity_cell.get() { + None => Err(ErrorCode::Busy), + Some(humidity_val) => Ok(humidity_val), + } + }) + } +} + +/// A wrapper around a closure to be registered and called when +/// a humidity reading is done. +/// +/// ```ignore +/// let listener = HumidityListener(|humidity_val| { +/// // make use of the humidity value +/// }); +/// ``` +pub struct HumidityListener(pub F); + +impl Upcall> for HumidityListener { + fn upcall(&self, humidity: u32, _arg1: u32, _arg2: u32) { + self.0(humidity) + } +} + +#[cfg(test)] +mod tests; + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x60001; + +// Command IDs + +const EXISTS: u32 = 0; +const READ_HUM: u32 = 1; diff --git a/apis/humidity/src/tests.rs b/apis/humidity/src/tests.rs new file mode 100644 index 00000000..466847d6 --- /dev/null +++ b/apis/humidity/src/tests.rs @@ -0,0 +1,73 @@ +use core::cell::Cell; +use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; +use libtock_unittest::fake; + +use crate::HumidityListener; + +type Humidity = super::Humidity; + +#[test] +fn no_driver() { + let _kernel = fake::Kernel::new(); + assert_eq!(Humidity::exists(), Err(ErrorCode::NoDevice)); +} + +#[test] +fn driver_check() { + let kernel = fake::Kernel::new(); + let driver = fake::Humidity::new(); + kernel.add_driver(&driver); + + assert_eq!(Humidity::exists(), Ok(())); +} + +#[test] +fn read_humidity() { + let kernel = fake::Kernel::new(); + let driver = fake::Humidity::new(); + kernel.add_driver(&driver); + + assert_eq!(Humidity::read(), Ok(())); + assert!(driver.is_busy()); + + assert_eq!(Humidity::read(), Err(ErrorCode::Busy)); + assert_eq!(Humidity::read_sync(), Err(ErrorCode::Busy)); +} + +#[test] +fn register_unregister_listener() { + let kernel = fake::Kernel::new(); + let driver = fake::Humidity::new(); + kernel.add_driver(&driver); + + let humidity_cell: Cell> = Cell::new(None); + let listener = HumidityListener(|val| { + humidity_cell.set(Some(val)); + }); + share::scope(|subscribe| { + assert_eq!(Humidity::read(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + assert_eq!(Humidity::register_listener(&listener, subscribe), Ok(())); + assert_eq!(Humidity::read(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(humidity_cell.get(), Some(100)); + + Humidity::unregister_listener(); + assert_eq!(Humidity::read(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + }); +} + +#[test] +fn read_humidity_sync() { + let kernel = fake::Kernel::new(); + let driver = fake::Humidity::new(); + kernel.add_driver(&driver); + + driver.set_value_sync(1000); + assert_eq!(Humidity::read_sync(), Ok(1000)); +} diff --git a/examples/humidity.rs b/examples/humidity.rs new file mode 100644 index 00000000..8aa90366 --- /dev/null +++ b/examples/humidity.rs @@ -0,0 +1,37 @@ +//! A simple libtock-rs example. Checks for humidity driver +//! and samples the sensor every 2 seconds. + +#![no_main] +#![no_std] + +use core::fmt::Write; +use libtock::console::Console; + +use libtock::alarm::{Alarm, Milliseconds}; +use libtock::humidity::Humidity; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x200} + +fn main() { + if Humidity::exists().is_err() { + writeln!(Console::writer(), "humidity driver unavailable").unwrap(); + return; + } + + loop { + match Humidity::read_sync() { + Ok(hum_val) => writeln!( + Console::writer(), + "Humidity: {}.{}%\n", + hum_val / 100, + hum_val % 100 + ) + .unwrap(), + Err(_) => writeln!(Console::writer(), "error while reading humidity",).unwrap(), + } + + Alarm::sleep_for(Milliseconds(2000)).unwrap(); + } +} diff --git a/src/lib.rs b/src/lib.rs index d0334838..5b3b8404 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,6 +44,13 @@ pub mod gpio { PullDown, PullNone, PullUp, }; } + +pub mod humidity { + use libtock_humidity as humidity; + pub type Humidity = humidity::Humidity; + pub use humidity::HumidityListener; +} + pub mod leds { use libtock_leds as leds; pub type Leds = leds::Leds; diff --git a/unittest/src/fake/humidity/mod.rs b/unittest/src/fake/humidity/mod.rs new file mode 100644 index 00000000..1412ccdb --- /dev/null +++ b/unittest/src/fake/humidity/mod.rs @@ -0,0 +1,85 @@ +//! Fake implementation of the Humidity API, documented here: +//! https://github.com/tock/tock/blob/master/doc/syscalls/60001_humidity.md +//! +//! Like the real API, `Humidity` controls a fake humidity sensor. It provides +//! a function `set_value` used to immediately call an upcall with a humidity 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 humidity read, +// because it was impossible to schedule an upcall during the `synchronous` read in other ways. +pub struct Humidity { + busy: Cell, + upcall_on_command: Cell>, + share_ref: DriverShareRef, +} + +impl Humidity { + pub fn new() -> std::rc::Rc { + std::rc::Rc::new(Humidity { + 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: i32) { + 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: i32) { + self.upcall_on_command.set(Some(value)); + } +} + +impl crate::fake::SyscallDriver for Humidity { + 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_HUM => { + 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); + } + crate::command_return::success() + } + _ => crate::command_return::failure(ErrorCode::NoSupport), + } + } +} + +#[cfg(test)] +mod tests; +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x60001; + +// Command IDs + +const EXISTS: u32 = 0; +const READ_HUM: u32 = 1; diff --git a/unittest/src/fake/humidity/tests.rs b/unittest/src/fake/humidity/tests.rs new file mode 100644 index 00000000..31501537 --- /dev/null +++ b/unittest/src/fake/humidity/tests.rs @@ -0,0 +1,67 @@ +use crate::fake::{self, SyscallDriver}; +use fake::humidity::*; +use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; + +//Test the command implementation +#[test] +fn command() { + let hum = Humidity::new(); + + assert!(hum.command(EXISTS, 1, 2).is_success()); + + assert!(hum.command(READ_HUM, 0, 0).is_success()); + + assert_eq!( + hum.command(READ_HUM, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + + hum.set_value(100); + assert!(hum.command(READ_HUM, 0, 1).is_success()); + hum.set_value(100); + + hum.set_value_sync(100); + assert!(hum.command(READ_HUM, 0, 1).is_success()); + assert!(hum.command(READ_HUM, 0, 1).is_success()); +} + +// Integration test that verifies Humidity works with fake::Kernel and +// libtock_platform::Syscalls. +#[test] +fn kernel_integration() { + use libtock_platform::Syscalls; + let kernel = fake::Kernel::new(); + let hum = Humidity::new(); + kernel.add_driver(&hum); + assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_HUM, 0, 0).is_success()); + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, READ_HUM, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + hum.set_value(100); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_HUM, 0, 1).is_success()); + + let listener = Cell::>::new(None); + share::scope(|subscribe| { + assert_eq!( + fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), + Ok(()) + ); + + hum.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(listener.get(), Some((100,))); + + hum.set_value(200); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + assert!(fake::Syscalls::command(DRIVER_NUM, READ_HUM, 0, 1).is_success()); + hum.set_value(200); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + + hum.set_value_sync(200); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_HUM, 0, 1).is_success()); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + }); +} diff --git a/unittest/src/fake/mod.rs b/unittest/src/fake/mod.rs index 2e71b9a3..dad61d09 100644 --- a/unittest/src/fake/mod.rs +++ b/unittest/src/fake/mod.rs @@ -16,6 +16,7 @@ mod buttons; mod buzzer; mod console; mod gpio; +mod humidity; mod kernel; mod leds; mod low_level_debug; @@ -33,6 +34,7 @@ pub use buttons::Buttons; pub use buzzer::Buzzer; pub use console::Console; pub use gpio::{Gpio, GpioMode, InterruptEdge, PullMode}; +pub use humidity::Humidity; pub use kernel::Kernel; pub use leds::Leds; pub use low_level_debug::{LowLevelDebug, Message};