diff --git a/Cargo.toml b/Cargo.toml index 76791d69..93cc34c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ version = "0.1.0" [dependencies] libtock_alarm = { path = "apis/alarm" } +libtock_ambient_light = { path = "apis/ambient_light" } libtock_buttons = { path = "apis/buttons" } libtock_console = { path = "apis/console" } libtock_debug_panic = { path = "panic_handlers/debug_panic" } @@ -43,6 +44,7 @@ members = [ "apis/low_level_debug", "apis/proximity", "apis/temperature", + "apis/ambient_light", "panic_handlers/debug_panic", "panic_handlers/small_panic", "platform", diff --git a/apis/ambient_light/Cargo.toml b/apis/ambient_light/Cargo.toml new file mode 100644 index 00000000..b38d750c --- /dev/null +++ b/apis/ambient_light/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "libtock_ambient_light" +version = "0.1.0" +authors = ["Tock Project Developers "] +license = "MIT/Apache-2.0" +edition = "2021" +repository = "https://www.github.com/tock/libtock-rs" +description = "libtock ambient light driver" + +[dependencies] +libtock_platform = { path = "../../platform" } + +[dev-dependencies] +libtock_unittest = { path = "../../unittest" } diff --git a/apis/ambient_light/src/lib.rs b/apis/ambient_light/src/lib.rs new file mode 100644 index 00000000..0f8de578 --- /dev/null +++ b/apis/ambient_light/src/lib.rs @@ -0,0 +1,87 @@ +#![no_std] + +use core::cell::Cell; +use libtock_platform::{ + share, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, +}; + +pub struct AmbientLight(S); + +impl AmbientLight { + /// 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 light intensity reading. + pub fn read_intensity() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, READ_INTENSITY, 0, 0).to_result() + } + + /// Register an events listener + pub fn register_listener<'share, F: Fn(u32)>( + listener: &'share IntensityListener, + 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 light intensity measurement. + /// Returns Ok(intensity_value) if the operation was successful + /// intensity_value is returned in lux + pub fn read_intensity_sync() -> Result { + let intensity_cell: Cell> = Cell::new(None); + let listener = IntensityListener(|intensity_val| { + intensity_cell.set(Some(intensity_val)); + }); + + share::scope(|subscribe| { + Self::register_listener(&listener, subscribe)?; + Self::read_intensity()?; + while intensity_cell.get() == None { + S::yield_wait(); + } + + match intensity_cell.get() { + None => Err(ErrorCode::Busy), + Some(intensity_val) => Ok(intensity_val), + } + }) + } +} + +/// A wrapper around a closure to be registered and called when +/// a luminance reading is done. +/// +/// ```ignore +/// let listener = IntensityListener(|intensity_val| { +/// // make use of the intensity value +/// }); +/// ``` +pub struct IntensityListener(pub F); + +impl Upcall> for IntensityListener { + fn upcall(&self, intensity: u32, _arg1: u32, _arg2: u32) { + self.0(intensity) + } +} + +#[cfg(test)] +mod tests; + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x60002; + +// Command IDs + +const EXISTS: u32 = 0; +const READ_INTENSITY: u32 = 1; diff --git a/apis/ambient_light/src/tests.rs b/apis/ambient_light/src/tests.rs new file mode 100644 index 00000000..9d5e267e --- /dev/null +++ b/apis/ambient_light/src/tests.rs @@ -0,0 +1,76 @@ +use core::cell::Cell; +use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; +use libtock_unittest::fake; + +use crate::IntensityListener; + +type AmbientLight = super::AmbientLight; + +#[test] +fn no_driver() { + let _kernel = fake::Kernel::new(); + assert_eq!(AmbientLight::exists(), Err(ErrorCode::NoDevice)); +} + +#[test] +fn driver_check() { + let kernel = fake::Kernel::new(); + let driver = fake::AmbientLight::new(); + kernel.add_driver(&driver); + + assert_eq!(AmbientLight::exists(), Ok(())); +} + +#[test] +fn read_temperature() { + let kernel = fake::Kernel::new(); + let driver = fake::AmbientLight::new(); + kernel.add_driver(&driver); + + assert_eq!(AmbientLight::read_intensity(), Ok(())); + assert!(driver.is_busy()); + + assert_eq!(AmbientLight::read_intensity(), Err(ErrorCode::Busy)); + assert_eq!(AmbientLight::read_intensity_sync(), Err(ErrorCode::Busy)); +} + +#[test] +fn register_unregister_listener() { + let kernel = fake::Kernel::new(); + let driver = fake::AmbientLight::new(); + kernel.add_driver(&driver); + + let intensity_cell: Cell> = Cell::new(None); + let listener = IntensityListener(|val| { + intensity_cell.set(Some(val)); + }); + share::scope(|subscribe| { + assert_eq!(AmbientLight::read_intensity(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + assert_eq!( + AmbientLight::register_listener(&listener, subscribe), + Ok(()) + ); + assert_eq!(AmbientLight::read_intensity(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(intensity_cell.get(), Some(100)); + + AmbientLight::unregister_listener(); + assert_eq!(AmbientLight::read_intensity(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + }); +} + +#[test] +fn read_temperature_sync() { + let kernel = fake::Kernel::new(); + let driver = fake::AmbientLight::new(); + kernel.add_driver(&driver); + + driver.set_value_sync(1000); + assert_eq!(AmbientLight::read_intensity_sync(), Ok(1000)); +} diff --git a/examples/ambient_light.rs b/examples/ambient_light.rs new file mode 100644 index 00000000..14a7e8b3 --- /dev/null +++ b/examples/ambient_light.rs @@ -0,0 +1,36 @@ +//! A simple libtock-rs example. Checks for ambient light 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::ambient_light::AmbientLight; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x200} + +fn main() { + if AmbientLight::exists().is_err() { + writeln!(Console::writer(), "ambient light driver unavailable").unwrap(); + return; + } + + loop { + match AmbientLight::read_intensity_sync() { + Ok(intensity_val) => writeln!( + Console::writer(), + "Light intensity: {} lux\n", + intensity_val + ) + .unwrap(), + Err(_) => writeln!(Console::writer(), "error while reading light intensity",).unwrap(), + } + + Alarm::sleep_for(Milliseconds(2000)).unwrap(); + } +} diff --git a/src/lib.rs b/src/lib.rs index fda15c86..2a9c12c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,11 @@ pub mod alarm { pub type Alarm = alarm::Alarm; pub use alarm::{Convert, Hz, Milliseconds, Ticks}; } +pub mod ambient_light { + use libtock_ambient_light as ambient_light; + pub type AmbientLight = ambient_light::AmbientLight; + pub use ambient_light::IntensityListener; +} pub mod buttons { use libtock_buttons as buttons; pub type Buttons = buttons::Buttons; diff --git a/unittest/src/fake/ambient_light/mod.rs b/unittest/src/fake/ambient_light/mod.rs new file mode 100644 index 00000000..f412a0b4 --- /dev/null +++ b/unittest/src/fake/ambient_light/mod.rs @@ -0,0 +1,85 @@ +//! Fake implementation of the Ambient Light API, documented here: +//! https://github.com/tock/tock/blob/master/doc/syscalls/60002_luminance.md +//! +//! Like the real API, `AmbientLight` controls a fake ambient light sensor. It provides +//! a function `set_value` used to immediately call an upcall with a intensity 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 temperature read, +// because it was impossible to schedule an upcall during the `synchronous` read in other ways. +pub struct AmbientLight { + busy: Cell, + upcall_on_command: Cell>, + share_ref: DriverShareRef, +} + +impl AmbientLight { + pub fn new() -> std::rc::Rc { + std::rc::Rc::new(AmbientLight { + 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: u32) { + 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: u32) { + self.upcall_on_command.set(Some(value)); + } +} + +impl crate::fake::SyscallDriver for AmbientLight { + 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_INTENSITY => { + 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 = 0x60002; + +// Command IDs + +const EXISTS: u32 = 0; +const READ_INTENSITY: u32 = 1; diff --git a/unittest/src/fake/ambient_light/tests.rs b/unittest/src/fake/ambient_light/tests.rs new file mode 100644 index 00000000..5a961da6 --- /dev/null +++ b/unittest/src/fake/ambient_light/tests.rs @@ -0,0 +1,67 @@ +use crate::fake::{self, SyscallDriver}; +use fake::ambient_light::*; +use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn}; + +//Test the command implementation +#[test] +fn command() { + let amb = AmbientLight::new(); + + assert!(amb.command(EXISTS, 1, 2).is_success()); + + assert!(amb.command(READ_INTENSITY, 0, 0).is_success()); + + assert_eq!( + amb.command(READ_INTENSITY, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + + amb.set_value(100); + assert!(amb.command(READ_INTENSITY, 0, 1).is_success()); + amb.set_value(100); + + amb.set_value_sync(100); + assert!(amb.command(READ_INTENSITY, 0, 1).is_success()); + assert!(amb.command(READ_INTENSITY, 0, 1).is_success()); +} + +// Integration test that verifies AmbientLight works with fake::Kernel and +// libtock_platform::Syscalls. +#[test] +fn kernel_integration() { + use libtock_platform::Syscalls; + let kernel = fake::Kernel::new(); + let ambient_light = AmbientLight::new(); + kernel.add_driver(&ambient_light); + assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 0, 0).is_success()); + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + ambient_light.set_value(100); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 0, 1).is_success()); + + let listener = Cell::>::new(None); + share::scope(|subscribe| { + assert_eq!( + fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), + Ok(()) + ); + + ambient_light.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(listener.get(), Some((100,))); + + ambient_light.set_value(200); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + assert!(fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 0, 1).is_success()); + ambient_light.set_value(200); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + + ambient_light.set_value_sync(200); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 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 61e0141a..6818d5da 100644 --- a/unittest/src/fake/mod.rs +++ b/unittest/src/fake/mod.rs @@ -10,6 +10,7 @@ //! (e.g. `fake::Console`). mod alarm; +mod ambient_light; mod buttons; mod console; mod gpio; @@ -22,6 +23,7 @@ mod syscalls; mod temperature; pub use alarm::Alarm; +pub use ambient_light::AmbientLight; pub use buttons::Buttons; pub use console::Console; pub use gpio::{Gpio, GpioMode, InterruptEdge, PullMode};