diff --git a/Cargo.toml b/Cargo.toml index aefbca93..7df7bb5b 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_runtime = { path = "runtime" } libtock_temperature = { path = "apis/temperature" } +libtock_humidity = { path = "apis/humidity" } [profile.dev] panic = "abort" diff --git a/apis/humidity/Cargo.toml b/apis/humidity/Cargo.toml new file mode 100644 index 00000000..1b2a2b21 --- /dev/null +++ b/apis/humidity/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "libtock_humidity" +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 humidity driver" + +[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..a05df404 --- /dev/null +++ b/apis/humidity/src/lib.rs @@ -0,0 +1,83 @@ +#![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 humidity_read() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, READ_HUMIDITY, 0, 0).to_result() + } + + /// Register an events listener + pub fn register_listener<'share, F: Fn(i32)>( + 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. + /// ## Hello + /// Returns Ok(humidity_value) if the operation was successful + /// humidity_value is returned in hundreds of centigrades + pub fn humidity_read_sync() -> Result { + let humidity_cell: Cell> = Cell::new(None); + let listener = HumidityListener(|hum_val| { + humidity_cell.set(Some(hum_val)); + }); + share::scope(|subscribe| { + if let Ok(()) = Self::register_listener(&listener, subscribe) { + if let Ok(()) = Self::humidity_read() { + while humidity_cell.get() == None { + S::yield_wait(); + } + } + } + }); + + match humidity_cell.get() { + None => Err(ErrorCode::Busy), + Some(hum_val) => Ok(hum_val), + } + } +} + +pub struct HumidityListener(pub F); +impl Upcall> for HumidityListener { + fn upcall(&self, hum_val: u32, _arg1: u32, _arg2: u32) { + self.0(hum_val as i32) + } +} + +#[cfg(test)] +mod tests; + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x60001; + +// Command IDs + +const EXISTS: u32 = 0; +const READ_HUMIDITY: u32 = 1; diff --git a/apis/humidity/src/tests.rs b/apis/humidity/src/tests.rs new file mode 100644 index 00000000..15705f31 --- /dev/null +++ b/apis/humidity/src/tests.rs @@ -0,0 +1,81 @@ +use core::cell::Cell; +use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn}; +use libtock_unittest::fake; + +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 humidity_read() { + let kernel = fake::Kernel::new(); + let driver = fake::Humidity::new(); + kernel.add_driver(&driver); + + assert_eq!(Humidity::humidity_read(), Ok(())); + assert!(driver.is_busy()); + + assert_eq!(Humidity::humidity_read(), Err(ErrorCode::Busy)); + assert_eq!(Humidity::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 = crate::HumidityListener(|temp_val| { + humidity_cell.set(Some(temp_val)); + }); + share::scope(|subscribe| { + assert_eq!(Humidity::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::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::humidity_read(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + }); +} + +#[test] +fn humidity_read_sync() { + let kernel = fake::Kernel::new(); + let driver = fake::Humidity::new(); + kernel.add_driver(&driver); + + driver.set_value_sync(1000); + assert_eq!(Humidity::humidity_read_sync(), Ok(1000)); +} + +#[test] +fn negative_value() { + let kernel = fake::Kernel::new(); + let driver = fake::Humidity::new(); + kernel.add_driver(&driver); + + driver.set_value_sync(-1000); + assert_eq!(Humidity::humidity_read_sync(), Ok(-1000)); +} diff --git a/examples/humidity.rs b/examples/humidity.rs new file mode 100644 index 00000000..0f2d7fa0 --- /dev/null +++ b/examples/humidity.rs @@ -0,0 +1,41 @@ +//! 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::runtime::{set_main, stack_size}; +use libtock::humidity::Humidity; + +set_main! {main} +stack_size! {0x200} + +fn main() { + match Humidity::exists() { + Ok(()) => writeln!(Console::writer(), "humidity driver available").unwrap(), + Err(_) => { + writeln!(Console::writer(), "humidity driver unavailable").unwrap(); + return; + } + } + + loop { + match Humidity::humidity_read_sync() { + Ok(hum_val) => writeln!( + Console::writer(), + "Humidity: {}{}.{}%\n", + if hum_val > 0 { "" } else { "-" }, + i32::abs(hum_val) / 100, + i32::abs(hum_val) % 100 + ) + .unwrap(), + Err(_) => writeln!(Console::writer(), "error while reading humidity",).unwrap(), + } + + Alarm::sleep_for(Milliseconds(2000)).unwrap(); + } +} diff --git a/runner/src/tockloader.rs b/runner/src/tockloader.rs index bc4fe829..057224bd 100644 --- a/runner/src/tockloader.rs +++ b/runner/src/tockloader.rs @@ -58,7 +58,7 @@ pub fn deploy(cli: &Cli, platform: String, tab_path: PathBuf) -> Child { } // Invoke tockloader uninstall to remove the process binary, if present. - let mut uninstall = Command::new("tockloader"); + let mut uninstall = Command::new("/Users/radueduard39/Library/Python/3.10/bin/tockloader"); uninstall.arg("uninstall"); uninstall.args(flags); if cli.verbose { @@ -75,7 +75,7 @@ pub fn deploy(cli: &Cli, platform: String, tab_path: PathBuf) -> Child { } // Invoke tockloader install to deploy the new process binary. - let mut install = Command::new("tockloader"); + let mut install = Command::new("/Users/radueduard39/Library/Python/3.10/bin/tockloader"); install.arg("install"); install.args(flags); install.arg(tab_path); @@ -94,7 +94,7 @@ pub fn deploy(cli: &Cli, platform: String, tab_path: PathBuf) -> Child { ); // Invoke tockloader listen to receive messages from the Tock system. - let mut listen = Command::new("tockloader"); + let mut listen = Command::new("/Users/radueduard39/Library/Python/3.10/bin/tockloader"); listen.arg("listen"); listen.args(flags); listen.stdout(Stdio::piped()); diff --git a/runtime/layouts/microbit_v2.ld b/runtime/layouts/microbit_v2.ld index 684810e2..e33f84b8 100644 --- a/runtime/layouts/microbit_v2.ld +++ b/runtime/layouts/microbit_v2.ld @@ -2,7 +2,7 @@ MEMORY { FLASH (X) : ORIGIN = 0x00040000, LENGTH = 256K - RAM (W) : ORIGIN = 0x20004000, LENGTH = 112K + RAM (W) : ORIGIN = 0x20004800, LENGTH = 112K } TBF_HEADER_SIZE = 0x60; diff --git a/src/lib.rs b/src/lib.rs index c13b39db..2bfb3c26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,3 +44,9 @@ pub mod temperature { pub type Temperature = temperature::Temperature; pub use temperature::TemperatureListener; } + +pub mod humidity { + use libtock_humidity as humidity; + pub type Humidity = humidity::Humidity; + pub use humidity::HumidityListener; +} \ No newline at end of file diff --git a/unittest/src/fake/humidity/mod.rs b/unittest/src/fake/humidity/mod.rs new file mode 100644 index 00000000..b2dd8265 --- /dev/null +++ b/unittest/src/fake/humidity/mod.rs @@ -0,0 +1,84 @@ +//! Fake implementation of the Humidity API, documented here: +//! +//! 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 temperature 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 bf29e4ff..4e560c42 100644 --- a/unittest/src/fake/mod.rs +++ b/unittest/src/fake/mod.rs @@ -19,6 +19,7 @@ mod low_level_debug; mod syscall_driver; mod syscalls; mod temperature; +mod humidity; pub use alarm::Alarm; pub use buttons::Buttons; @@ -30,6 +31,7 @@ pub use low_level_debug::{LowLevelDebug, Message}; pub use syscall_driver::SyscallDriver; pub use syscalls::Syscalls; pub use temperature::Temperature; +pub use humidity::Humidity; #[cfg(test)] mod kernel_tests;