From 70467b8367ee35f9bec110e126d7bf9e2f85e504 Mon Sep 17 00:00:00 2001 From: RaresCon Date: Mon, 27 Mar 2023 11:47:46 +0300 Subject: [PATCH 1/8] First iteration of air_quality API I've written the base of air_quality API, next I will add a fake sensor for testing the API, including unit testing for the fake sensor --- Cargo.toml | 2 + apis/air_quality/Cargo.toml | 14 ++++++ apis/air_quality/src/lib.rs | 90 +++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+) create mode 100644 apis/air_quality/Cargo.toml create mode 100644 apis/air_quality/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index aefbca93..6817f383 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_air_quality = { path = "apis/air_quality" } [profile.dev] panic = "abort" @@ -41,6 +42,7 @@ members = [ "apis/leds", "apis/low_level_debug", "apis/temperature", + "apis/air_quality", "panic_handlers/debug_panic", "panic_handlers/small_panic", "platform", diff --git a/apis/air_quality/Cargo.toml b/apis/air_quality/Cargo.toml new file mode 100644 index 00000000..856bfc4c --- /dev/null +++ b/apis/air_quality/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "libtock_air_quality" +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 air quality driver" + +[dependencies] +libtock_platform = { path = "../../platform" } + +[dev-dependencies] +libtock_unittest = { path = "../../unittest" } diff --git a/apis/air_quality/src/lib.rs b/apis/air_quality/src/lib.rs new file mode 100644 index 00000000..ba03caa0 --- /dev/null +++ b/apis/air_quality/src/lib.rs @@ -0,0 +1,90 @@ +#![no_std] + +use core::cell::Cell; +use libtock_platform::{ + share::scope, share::Handle, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, +}; + +pub struct AirQuality(S); + +impl AirQuality { + pub fn exists() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() + } + + pub fn register_listener<'share, F: Fn(i32)>( + listener: &'share AirQualityListener, + subscribe: Handle>, + ) -> Result<(), ErrorCode> { + S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) + } + + pub fn unregister_listener() { + S::unsubscribe(DRIVER_NUM, 0) + } + + pub fn read_tvoc() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, READ_TVOC, 0, 0).to_result() + } + + pub fn read_co2() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, READ_CO2, 0, 0).to_result() + } + + pub fn read_data_sync(read_type: u32) -> Result { + let data_cell: Cell> = Cell::new(None); + let listener = AirQualityListener(|data_val| { + data_cell.set(Some(data_val)); + }); + + let mut unsupported: bool = false; + scope(|subscribe| { + if let Ok(()) = Self::register_listener(&listener, subscribe) { + match read_type { + READ_CO2 => { + if let Ok(()) = Self::read_co2() { + while data_cell.get() == None { + S::yield_wait(); + } + } + } + READ_TVOC => { + if let Ok(()) = Self::read_tvoc() { + while data_cell.get() == None { + S::yield_wait(); + } + } + } + _ => { unsupported = true; } + } + } + }); + + if unsupported { + return Err(ErrorCode::NoSupport); + } + match data_cell.get() { + None => Err(ErrorCode::Busy), + Some(data_val) => Ok(data_val), + } + } +} + +pub struct AirQualityListener(pub F); +impl Upcall> for AirQualityListener { + fn upcall(&self, data_val: u32, _arg1: u32, _arg2: u32) { + self.0(data_val as i32) + } +} + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x60007; + +// Command IDs + +const EXISTS: u32 = 0; +const READ_CO2: u32 = 2; +const READ_TVOC: u32 = 3; From 47f5278ebee82e0916cebf3ef0fe56f24f4832ff Mon Sep 17 00:00:00 2001 From: RaresCon Date: Tue, 28 Mar 2023 01:04:03 +0300 Subject: [PATCH 2/8] Modified the API to include the suggested changes I've removed the `AirQualityListener` and used a `Cell>` instead. I've added a private Enum for choosing what kind of data reading to execute, a private method that uses this Enum and two other public functions as wrappers. --- apis/air_quality/src/lib.rs | 57 ++++++++++++++++++------------------- src/lib.rs | 5 ++++ 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/apis/air_quality/src/lib.rs b/apis/air_quality/src/lib.rs index ba03caa0..5ab528f0 100644 --- a/apis/air_quality/src/lib.rs +++ b/apis/air_quality/src/lib.rs @@ -2,18 +2,24 @@ use core::cell::Cell; use libtock_platform::{ - share::scope, share::Handle, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, + share::scope, share::Handle, DefaultConfig, ErrorCode, Subscribe, Syscalls, }; +use ReadType::{ReadCO2, ReadTVOC}; + +enum ReadType { + ReadCO2 = READ_CO2 as isize, + ReadTVOC = READ_TVOC as isize, +} pub struct AirQuality(S); -impl AirQuality { +impl AirQuality { pub fn exists() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() } - pub fn register_listener<'share, F: Fn(i32)>( - listener: &'share AirQualityListener, + pub fn register_listener<'share>( + listener: &'share Cell>, subscribe: Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) @@ -23,60 +29,53 @@ impl AirQuality { S::unsubscribe(DRIVER_NUM, 0) } + pub fn read_co2() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, READ_CO2, 0, 0).to_result() + } + pub fn read_tvoc() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_TVOC, 0, 0).to_result() } - pub fn read_co2() -> Result<(), ErrorCode> { - S::command(DRIVER_NUM, READ_CO2, 0, 0).to_result() + pub fn read_co2_sync() -> Result { + Self::read_data_sync(ReadCO2) } - pub fn read_data_sync(read_type: u32) -> Result { - let data_cell: Cell> = Cell::new(None); - let listener = AirQualityListener(|data_val| { - data_cell.set(Some(data_val)); - }); + pub fn read_tvoc_sync() -> Result { + Self::read_data_sync(ReadTVOC) + } + + fn read_data_sync(read_type: ReadType) -> Result { + let listener: Cell> = Cell::new(None); - let mut unsupported: bool = false; scope(|subscribe| { if let Ok(()) = Self::register_listener(&listener, subscribe) { match read_type { - READ_CO2 => { + ReadCO2 => { if let Ok(()) = Self::read_co2() { - while data_cell.get() == None { + while listener.get() == None { S::yield_wait(); } } } - READ_TVOC => { + ReadTVOC => { if let Ok(()) = Self::read_tvoc() { - while data_cell.get() == None { + while listener.get() == None { S::yield_wait(); } } } - _ => { unsupported = true; } } } }); - if unsupported { - return Err(ErrorCode::NoSupport); - } - match data_cell.get() { + match listener.get() { None => Err(ErrorCode::Busy), - Some(data_val) => Ok(data_val), + Some((data_val,)) => Ok(data_val), } } } -pub struct AirQualityListener(pub F); -impl Upcall> for AirQualityListener { - fn upcall(&self, data_val: u32, _arg1: u32, _arg2: u32) { - self.0(data_val as i32) - } -} - // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- diff --git a/src/lib.rs b/src/lib.rs index c13b39db..a8e29d87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,3 +44,8 @@ pub mod temperature { pub type Temperature = temperature::Temperature; pub use temperature::TemperatureListener; } + +pub mod air_quality { + use libtock_air_quality as air_quality; + pub type AirQuality = air_quality::AirQuality; +} From bfd3784c5777b9910dfb332af7eaab7a70b5ccd8 Mon Sep 17 00:00:00 2001 From: RaresCon Date: Thu, 30 Mar 2023 14:42:42 +0300 Subject: [PATCH 3/8] Added fake `AirQuality` driver I've added a fake `AirQuality` driver and some integration tests for it. Every test is passed and should reflect the behaviour of the real driver. --- unittest/src/fake/air_quality/mod.rs | 106 +++++++++++++++++++++++++ unittest/src/fake/air_quality/tests.rs | 101 +++++++++++++++++++++++ unittest/src/fake/mod.rs | 2 + 3 files changed, 209 insertions(+) create mode 100644 unittest/src/fake/air_quality/mod.rs create mode 100644 unittest/src/fake/air_quality/tests.rs diff --git a/unittest/src/fake/air_quality/mod.rs b/unittest/src/fake/air_quality/mod.rs new file mode 100644 index 00000000..a98ccf60 --- /dev/null +++ b/unittest/src/fake/air_quality/mod.rs @@ -0,0 +1,106 @@ +use crate::{DriverInfo, DriverShareRef}; +use libtock_platform::{CommandReturn, ErrorCode}; +use std::cell::Cell; + +pub struct AirQuality { + busy: Cell, + co2_available: Cell, + tvoc_available: Cell, + upcall_on_command: Cell>, + share_ref: DriverShareRef, +} + +impl AirQuality { + pub fn new() -> std::rc::Rc { + std::rc::Rc::new(AirQuality { + busy: Cell::new(false), + co2_available: Cell::new(true), + tvoc_available: Cell::new(true), + upcall_on_command: Cell::new(None), + share_ref: Default::default(), + }) + } + + pub fn set_co2_available(&self, co2_available: bool) { + self.co2_available.set(co2_available); + } + + pub fn set_tvoc_available(&self, tvoc_available: bool) { + self.tvoc_available.set(tvoc_available); + } + + 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 AirQuality { + 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_CO2 => { + if !self.co2_available.get() { + return crate::command_return::failure(ErrorCode::NoSupport); + } + 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() + } + READ_TVOC => { + if !self.tvoc_available.get() { + return crate::command_return::failure(ErrorCode::NoSupport); + } + 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 = 0x60007; + +// Command IDs + +const EXISTS: u32 = 0; +const READ_CO2: u32 = 2; +const READ_TVOC: u32 = 3; diff --git a/unittest/src/fake/air_quality/tests.rs b/unittest/src/fake/air_quality/tests.rs new file mode 100644 index 00000000..78949c64 --- /dev/null +++ b/unittest/src/fake/air_quality/tests.rs @@ -0,0 +1,101 @@ +use crate::fake::{self, SyscallDriver}; +use fake::air_quality::*; +use libtock_platform::{share::scope, DefaultConfig, YieldNoWaitReturn}; + +//Test the `command` implementation +#[test] +fn command() { + let driver = AirQuality::new(); + + assert!(driver.command(EXISTS, 0, 0).is_success()); + + driver.set_co2_available(false); + assert_eq!( + driver.command(READ_CO2, 0, 0).get_failure(), + Some(ErrorCode::NoSupport) + ); + driver.set_tvoc_available(false); + assert_eq!( + driver.command(READ_TVOC, 0, 0).get_failure(), + Some(ErrorCode::NoSupport) + ); + + driver.set_co2_available(true); + driver.set_tvoc_available(true); + + assert!(driver.command(READ_CO2, 0, 0).is_success()); + assert_eq!( + driver.command(READ_CO2, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + driver.set_value(100); + assert!(driver.command(READ_CO2, 0, 0).is_success()); + driver.set_value(100); + + assert!(driver.command(READ_TVOC, 0, 0).is_success()); + assert_eq!( + driver.command(READ_TVOC, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + driver.set_value(100); + assert!(driver.command(READ_TVOC, 0, 0).is_success()); + driver.set_value(100); + + driver.set_value_sync(100); + assert!(driver.command(READ_CO2, 0, 0).is_success()); + assert!(driver.command(READ_TVOC, 0, 0).is_success()); +} + +// Integration test that verifies Temperature works with fake::Kernel and +// libtock_platform::Syscalls. +#[test] +fn kernel_integration() { + use libtock_platform::Syscalls; + let kernel = fake::Kernel::new(); + let driver = AirQuality::new(); + kernel.add_driver(&driver); + + assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 0, 0).is_success()); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + driver.set_value(100); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); + driver.set_value(100); + + assert!(fake::Syscalls::command(DRIVER_NUM, READ_TVOC, 0, 0).is_success()); + assert_eq!( + fake::Syscalls::command(DRIVER_NUM, READ_TVOC, 0, 0).get_failure(), + Some(ErrorCode::Busy) + ); + driver.set_value(100); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_TVOC, 0, 0).is_success()); + + let listener = Cell::>::new(None); + scope(|subscribe| { + assert_eq!( + fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener), + Ok(()) + ); + + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(listener.get(), Some((100,))); + + driver.set_value(200); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + assert_eq!(listener.get(), Some((100,))); + + assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); + driver.set_value(200); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(listener.get(), Some((200,))); + + driver.set_value_sync(200); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_TVOC, 0, 0).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..1ea2584b 100644 --- a/unittest/src/fake/mod.rs +++ b/unittest/src/fake/mod.rs @@ -9,6 +9,7 @@ //! `use libtock_unittest::fake` and refer to the type with the `fake::` prefix //! (e.g. `fake::Console`). +mod air_quality; mod alarm; mod buttons; mod console; @@ -20,6 +21,7 @@ mod syscall_driver; mod syscalls; mod temperature; +pub use air_quality::AirQuality; pub use alarm::Alarm; pub use buttons::Buttons; pub use console::Console; From 8fe570b85d7cbd6dedd7ad91f1f5b761563da17c Mon Sep 17 00:00:00 2001 From: RaresCon Date: Tue, 4 Apr 2023 12:11:40 +0300 Subject: [PATCH 4/8] Added unittests for AirQuality API I've added unittests for each function of the AirQuality API, all pass as expected. Documentation for the API will be part of the next commit. --- apis/air_quality/src/lib.rs | 3 + apis/air_quality/src/tests.rs | 106 ++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 apis/air_quality/src/tests.rs diff --git a/apis/air_quality/src/lib.rs b/apis/air_quality/src/lib.rs index 5ab528f0..75c3fcb9 100644 --- a/apis/air_quality/src/lib.rs +++ b/apis/air_quality/src/lib.rs @@ -6,6 +6,9 @@ use libtock_platform::{ }; use ReadType::{ReadCO2, ReadTVOC}; +#[cfg(test)] +mod tests; + enum ReadType { ReadCO2 = READ_CO2 as isize, ReadTVOC = READ_TVOC as isize, diff --git a/apis/air_quality/src/tests.rs b/apis/air_quality/src/tests.rs new file mode 100644 index 00000000..a68671a5 --- /dev/null +++ b/apis/air_quality/src/tests.rs @@ -0,0 +1,106 @@ +use core::cell::Cell; +use libtock_platform::{share::scope, ErrorCode, Syscalls, YieldNoWaitReturn}; +use libtock_unittest::fake; + +type AirQuality = super::AirQuality; + +#[test] +fn no_driver() { + let _kernel = fake::Kernel::new(); + assert_eq!(AirQuality::exists(), Err(ErrorCode::NoDevice)); +} + +#[test] +fn driver_check() { + let kernel = fake::Kernel::new(); + let driver = fake::AirQuality::new(); + kernel.add_driver(&driver); + + assert_eq!(AirQuality::exists(), Ok(())); +} + +#[test] +fn read_co2() { + let kernel = fake::Kernel::new(); + let driver = fake::AirQuality::new(); + kernel.add_driver(&driver); + + assert_eq!(AirQuality::read_co2(), Ok(())); + assert!(driver.is_busy()); + + assert_eq!(AirQuality::read_co2(), Err(ErrorCode::Busy)); + assert_eq!(AirQuality::read_co2_sync(), Err(ErrorCode::Busy)); +} + +#[test] +fn read_tvoc() { + let kernel = fake::Kernel::new(); + let driver = fake::AirQuality::new(); + kernel.add_driver(&driver); + + assert_eq!(AirQuality::read_tvoc(), Ok(())); + assert!(driver.is_busy()); + + assert_eq!(AirQuality::read_tvoc(), Err(ErrorCode::Busy)); + assert_eq!(AirQuality::read_tvoc_sync(), Err(ErrorCode::Busy)); +} + +#[test] +fn register_unregister_listener() { + let kernel = fake::Kernel::new(); + let driver = fake::AirQuality::new(); + kernel.add_driver(&driver); + + let listener: Cell> = Cell::new(None); + + scope(|subscribe| { + assert_eq!(AirQuality::read_co2(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + assert_eq!(AirQuality::read_tvoc(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + assert_eq!(AirQuality::register_listener(&listener, subscribe), Ok(())); + + assert_eq!(AirQuality::read_co2(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(listener.get(), Some((100,))); + + assert_eq!(AirQuality::read_tvoc(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(listener.get(), Some((100,))); + + AirQuality::unregister_listener(); + assert_eq!(AirQuality::read_co2(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + assert_eq!(AirQuality::read_tvoc(), Ok(())); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + }); +} + +#[test] +fn read_co2_sync() { + let kernel = fake::Kernel::new(); + let driver = fake::AirQuality::new(); + kernel.add_driver(&driver); + + driver.set_value_sync(100); + assert_eq!(AirQuality::read_co2_sync(), Ok(100)); +} + +#[test] +fn read_tvoc_sync() { + let kernel = fake::Kernel::new(); + let driver = fake::AirQuality::new(); + kernel.add_driver(&driver); + + driver.set_value_sync(100); + assert_eq!(AirQuality::read_tvoc_sync(), Ok(100)); +} From 5afcd38ea3d7fad6aae00a7cd54c03249bcd29ce Mon Sep 17 00:00:00 2001 From: RaresCon Date: Thu, 13 Apr 2023 10:40:44 +0300 Subject: [PATCH 5/8] Added suggested changes to AirQuality API Added the suggested changes, including the `read_sync` function that return a tuple of both CO2 and TVOC values. Improved the unittests for the fake driver and added a unittest for the new function. --- apis/air_quality/src/lib.rs | 26 +++++++++++++++++--------- apis/air_quality/src/tests.rs | 10 ++++++++++ unittest/src/fake/air_quality/mod.rs | 23 ++++++++++++++++++----- unittest/src/fake/air_quality/tests.rs | 15 +++++++++++++++ 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/apis/air_quality/src/lib.rs b/apis/air_quality/src/lib.rs index 75c3fcb9..7a2c6de6 100644 --- a/apis/air_quality/src/lib.rs +++ b/apis/air_quality/src/lib.rs @@ -4,14 +4,14 @@ use core::cell::Cell; use libtock_platform::{ share::scope, share::Handle, DefaultConfig, ErrorCode, Subscribe, Syscalls, }; -use ReadType::{ReadCO2, ReadTVOC}; +use Value::{Tvoc, CO2}; #[cfg(test)] mod tests; -enum ReadType { - ReadCO2 = READ_CO2 as isize, - ReadTVOC = READ_TVOC as isize, +enum Value { + CO2 = READ_CO2 as isize, + Tvoc = READ_TVOC as isize, } pub struct AirQuality(S); @@ -41,27 +41,35 @@ impl AirQuality { } pub fn read_co2_sync() -> Result { - Self::read_data_sync(ReadCO2) + Self::read_data_sync(CO2) } pub fn read_tvoc_sync() -> Result { - Self::read_data_sync(ReadTVOC) + Self::read_data_sync(Tvoc) } - fn read_data_sync(read_type: ReadType) -> Result { + pub fn read_sync() -> Result<(u32, u32), ErrorCode> { + match (Self::read_data_sync(CO2), Self::read_data_sync(Tvoc)) { + (Ok(co2_value), Ok(tvoc_value)) => Ok((co2_value, tvoc_value)), + (Err(co2_error), _) => Err(co2_error), + (_, Err(tvoc_error)) => Err(tvoc_error), + } + } + + fn read_data_sync(read_type: Value) -> Result { let listener: Cell> = Cell::new(None); scope(|subscribe| { if let Ok(()) = Self::register_listener(&listener, subscribe) { match read_type { - ReadCO2 => { + CO2 => { if let Ok(()) = Self::read_co2() { while listener.get() == None { S::yield_wait(); } } } - ReadTVOC => { + Tvoc => { if let Ok(()) = Self::read_tvoc() { while listener.get() == None { S::yield_wait(); diff --git a/apis/air_quality/src/tests.rs b/apis/air_quality/src/tests.rs index a68671a5..d247f353 100644 --- a/apis/air_quality/src/tests.rs +++ b/apis/air_quality/src/tests.rs @@ -104,3 +104,13 @@ fn read_tvoc_sync() { driver.set_value_sync(100); assert_eq!(AirQuality::read_tvoc_sync(), Ok(100)); } + +#[test] +fn read_sync() { + let kernel = fake::Kernel::new(); + let driver = fake::AirQuality::new(); + kernel.add_driver(&driver); + + driver.set_values_sync(100, 200); + assert_eq!(AirQuality::read_sync(), Ok((100, 200))) +} diff --git a/unittest/src/fake/air_quality/mod.rs b/unittest/src/fake/air_quality/mod.rs index a98ccf60..95f17495 100644 --- a/unittest/src/fake/air_quality/mod.rs +++ b/unittest/src/fake/air_quality/mod.rs @@ -6,7 +6,8 @@ pub struct AirQuality { busy: Cell, co2_available: Cell, tvoc_available: Cell, - upcall_on_command: Cell>, + upcall_on_read: Cell>, + upcall_on_tuple_read: Cell>, share_ref: DriverShareRef, } @@ -16,7 +17,8 @@ impl AirQuality { busy: Cell::new(false), co2_available: Cell::new(true), tvoc_available: Cell::new(true), - upcall_on_command: Cell::new(None), + upcall_on_read: Cell::new(None), + upcall_on_tuple_read: Cell::new(None), share_ref: Default::default(), }) } @@ -42,7 +44,10 @@ impl AirQuality { } } pub fn set_value_sync(&self, value: u32) { - self.upcall_on_command.set(Some(value)); + self.upcall_on_read.set(Some(value)); + } + pub fn set_values_sync(&self, co2_value: u32, tvoc_value: u32) { + self.upcall_on_tuple_read.set(Some((co2_value, tvoc_value))); } } @@ -67,9 +72,13 @@ impl crate::fake::SyscallDriver for AirQuality { } self.busy.set(true); - if let Some(val) = self.upcall_on_command.take() { + if let Some(val) = self.upcall_on_read.take() { self.set_value(val); } + if let Some((co2_val, _)) = self.upcall_on_tuple_read.get() { + self.set_value(co2_val); + } + crate::command_return::success() } READ_TVOC => { @@ -81,9 +90,13 @@ impl crate::fake::SyscallDriver for AirQuality { } self.busy.set(true); - if let Some(val) = self.upcall_on_command.take() { + if let Some(val) = self.upcall_on_read.take() { self.set_value(val); } + if let Some((_, tvoc_val)) = self.upcall_on_tuple_read.take() { + self.set_value(tvoc_val); + } + crate::command_return::success() } _ => crate::command_return::failure(ErrorCode::NoSupport), diff --git a/unittest/src/fake/air_quality/tests.rs b/unittest/src/fake/air_quality/tests.rs index 78949c64..ccbf9977 100644 --- a/unittest/src/fake/air_quality/tests.rs +++ b/unittest/src/fake/air_quality/tests.rs @@ -95,7 +95,22 @@ fn kernel_integration() { driver.set_value_sync(200); assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + + assert!(fake::Syscalls::command(DRIVER_NUM, READ_TVOC, 0, 0).is_success()); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + driver.set_value_sync(200); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + driver.set_value(100); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert_eq!(listener.get(), Some((100,))); + + driver.set_values_sync(100, 200); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); assert!(fake::Syscalls::command(DRIVER_NUM, READ_TVOC, 0, 0).is_success()); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + assert!(fake::Syscalls::command(DRIVER_NUM, READ_CO2, 0, 0).is_success()); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); }); } From 6e828224a4768c32e305d8ea73b2da5506b643b7 Mon Sep 17 00:00:00 2001 From: RaresCon Date: Thu, 20 Apr 2023 00:22:14 +0300 Subject: [PATCH 6/8] Changed alphabetical order inside `Cargo.toml` and `lib.rs` --- Cargo.toml | 4 ++-- src/lib.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6817f383..c2804850 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ repository = "https://www.github.com/tock/libtock-rs" version = "0.1.0" [dependencies] +libtock_air_quality = { path = "apis/air_quality" } libtock_alarm = { path = "apis/alarm" } libtock_buttons = { path = "apis/buttons" } libtock_console = { path = "apis/console" } @@ -20,7 +21,6 @@ libtock_low_level_debug = { path = "apis/low_level_debug" } libtock_platform = { path = "platform" } libtock_runtime = { path = "runtime" } libtock_temperature = { path = "apis/temperature" } -libtock_air_quality = { path = "apis/air_quality" } [profile.dev] panic = "abort" @@ -35,6 +35,7 @@ debug = true [workspace] exclude = ["tock"] members = [ + "apis/air_quality", "apis/alarm", "apis/gpio", "apis/buttons", @@ -42,7 +43,6 @@ members = [ "apis/leds", "apis/low_level_debug", "apis/temperature", - "apis/air_quality", "panic_handlers/debug_panic", "panic_handlers/small_panic", "platform", diff --git a/src/lib.rs b/src/lib.rs index a8e29d87..950689f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,11 @@ extern crate libtock_debug_panic; pub use libtock_platform as platform; pub use libtock_runtime as runtime; +pub mod air_quality { + use libtock_air_quality as air_quality; + pub type AirQuality = air_quality::AirQuality; +} + pub mod alarm { use libtock_alarm as alarm; pub type Alarm = alarm::Alarm; @@ -44,8 +49,3 @@ pub mod temperature { pub type Temperature = temperature::Temperature; pub use temperature::TemperatureListener; } - -pub mod air_quality { - use libtock_air_quality as air_quality; - pub type AirQuality = air_quality::AirQuality; -} From 6873b5014968689014ebfe6d3487d2db0db5c8cb Mon Sep 17 00:00:00 2001 From: RaresCon Date: Tue, 16 May 2023 09:34:56 +0300 Subject: [PATCH 7/8] Added requested changes I've added back the dedicated listener for this API and rewritten the returning errors of some functions. --- apis/air_quality/src/lib.rs | 67 ++++++++++++++++++++--------------- apis/air_quality/src/tests.rs | 12 ++++--- 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/apis/air_quality/src/lib.rs b/apis/air_quality/src/lib.rs index 7a2c6de6..7769173b 100644 --- a/apis/air_quality/src/lib.rs +++ b/apis/air_quality/src/lib.rs @@ -1,14 +1,10 @@ #![no_std] use core::cell::Cell; -use libtock_platform::{ - share::scope, share::Handle, DefaultConfig, ErrorCode, Subscribe, Syscalls, -}; +use libtock_platform::{share::scope, share::Handle, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall}; +use libtock_platform::subscribe::OneId; use Value::{Tvoc, CO2}; -#[cfg(test)] -mod tests; - enum Value { CO2 = READ_CO2 as isize, Tvoc = READ_TVOC as isize, @@ -21,8 +17,8 @@ impl AirQuality { S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() } - pub fn register_listener<'share>( - listener: &'share Cell>, + pub fn register_listener<'share, F: Fn(u32)>( + listener: &'share AirQualityListener, subscribe: Handle>, ) -> Result<(), ErrorCode> { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) @@ -57,36 +53,51 @@ impl AirQuality { } fn read_data_sync(read_type: Value) -> Result { - let listener: Cell> = Cell::new(None); + let data_cell: Cell> = Cell::new(None); + let listener = AirQualityListener(|data_val| { + data_cell.set(Some(data_val)); + }); scope(|subscribe| { - if let Ok(()) = Self::register_listener(&listener, subscribe) { - match read_type { - CO2 => { - if let Ok(()) = Self::read_co2() { - while listener.get() == None { - S::yield_wait(); - } - } + Self::register_listener(&listener, subscribe)?; + return match read_type { + CO2 => { + Self::read_co2()?; + while data_cell.get() == None { + S::yield_wait(); + } + + match data_cell.get() { + None => Err(ErrorCode::Fail), + Some(co2_value) => Ok(co2_value) + } + } + Tvoc => { + Self::read_tvoc()?; + while data_cell.get() == None { + S::yield_wait(); } - Tvoc => { - if let Ok(()) = Self::read_tvoc() { - while listener.get() == None { - S::yield_wait(); - } - } + + match data_cell.get() { + None => Err(ErrorCode::Fail), + Some(tvoc_value) => Ok(tvoc_value) } } } - }); + }) + } +} - match listener.get() { - None => Err(ErrorCode::Busy), - Some((data_val,)) => Ok(data_val), - } +pub struct AirQualityListener(pub F); +impl Upcall> for AirQualityListener { + fn upcall(&self, data_val: u32, _arg1: u32, _arg2: u32) { + self.0(data_val) } } +#[cfg(test)] +mod tests; + // ----------------------------------------------------------------------------- // Driver number and command IDs // ----------------------------------------------------------------------------- diff --git a/apis/air_quality/src/tests.rs b/apis/air_quality/src/tests.rs index d247f353..6251c5b8 100644 --- a/apis/air_quality/src/tests.rs +++ b/apis/air_quality/src/tests.rs @@ -1,8 +1,7 @@ use core::cell::Cell; use libtock_platform::{share::scope, ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; - -type AirQuality = super::AirQuality; +use crate::{AirQuality, AirQualityListener}; #[test] fn no_driver() { @@ -51,7 +50,10 @@ fn register_unregister_listener() { let driver = fake::AirQuality::new(); kernel.add_driver(&driver); - let listener: Cell> = Cell::new(None); + let data_cell: Cell> = Cell::new(None); + let listener = AirQualityListener(|data_val| { + data_cell.set(Some(data_val)); + }); scope(|subscribe| { assert_eq!(AirQuality::read_co2(), Ok(())); @@ -67,12 +69,12 @@ fn register_unregister_listener() { assert_eq!(AirQuality::read_co2(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); - assert_eq!(listener.get(), Some((100,))); + assert_eq!(data_cell.get(), Some(100)); assert_eq!(AirQuality::read_tvoc(), Ok(())); driver.set_value(100); assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); - assert_eq!(listener.get(), Some((100,))); + assert_eq!(data_cell.get(), Some(100)); AirQuality::unregister_listener(); assert_eq!(AirQuality::read_co2(), Ok(())); From 35cd6b79040bb9be698619e3d7819b6367d2d35d Mon Sep 17 00:00:00 2001 From: RaresCon Date: Thu, 18 May 2023 11:32:15 +0300 Subject: [PATCH 8/8] Added documentation and fixed CI bug I've added some documentation for the functions of the API and fixed an inclusion bug of the API in `tests.rs` that made the CI fail. --- apis/air_quality/src/lib.rs | 25 +++++++++++++++++++++---- apis/air_quality/src/tests.rs | 4 +++- src/lib.rs | 1 + 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/apis/air_quality/src/lib.rs b/apis/air_quality/src/lib.rs index 7769173b..715a6e4d 100644 --- a/apis/air_quality/src/lib.rs +++ b/apis/air_quality/src/lib.rs @@ -1,8 +1,10 @@ #![no_std] use core::cell::Cell; -use libtock_platform::{share::scope, share::Handle, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall}; use libtock_platform::subscribe::OneId; +use libtock_platform::{ + share::scope, share::Handle, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall, +}; use Value::{Tvoc, CO2}; enum Value { @@ -13,10 +15,13 @@ enum Value { pub struct AirQuality(S); impl AirQuality { + /// 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() } + /// Register an events listener pub fn register_listener<'share, F: Fn(u32)>( listener: &'share AirQualityListener, subscribe: Handle>, @@ -24,26 +29,36 @@ impl AirQuality { S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) } + /// Unregister the events listener pub fn unregister_listener() { S::unsubscribe(DRIVER_NUM, 0) } + /// Initiate a CO2 measurement. + /// + /// This function is used both for synchronous and asynchronous readings pub fn read_co2() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_CO2, 0, 0).to_result() } + /// Initiate a TVOC measurement. + /// + /// This function is used both for synchronous and asynchronous readings pub fn read_tvoc() -> Result<(), ErrorCode> { S::command(DRIVER_NUM, READ_TVOC, 0, 0).to_result() } + /// Public wrapper for `read_data_sync` for CO2 synchronous measurement pub fn read_co2_sync() -> Result { Self::read_data_sync(CO2) } + /// Public wrapper for `read_data_sync` for TVOC synchronous measurement pub fn read_tvoc_sync() -> Result { Self::read_data_sync(Tvoc) } + /// Read both CO2 and TVOC values synchronously pub fn read_sync() -> Result<(u32, u32), ErrorCode> { match (Self::read_data_sync(CO2), Self::read_data_sync(Tvoc)) { (Ok(co2_value), Ok(tvoc_value)) => Ok((co2_value, tvoc_value)), @@ -52,6 +67,8 @@ impl AirQuality { } } + /// Initiate a synchronous CO2 or TVOC measurement, based on the `read_type`. + /// Returns Ok(value) if the operation was successful fn read_data_sync(read_type: Value) -> Result { let data_cell: Cell> = Cell::new(None); let listener = AirQualityListener(|data_val| { @@ -60,7 +77,7 @@ impl AirQuality { scope(|subscribe| { Self::register_listener(&listener, subscribe)?; - return match read_type { + match read_type { CO2 => { Self::read_co2()?; while data_cell.get() == None { @@ -69,7 +86,7 @@ impl AirQuality { match data_cell.get() { None => Err(ErrorCode::Fail), - Some(co2_value) => Ok(co2_value) + Some(co2_value) => Ok(co2_value), } } Tvoc => { @@ -80,7 +97,7 @@ impl AirQuality { match data_cell.get() { None => Err(ErrorCode::Fail), - Some(tvoc_value) => Ok(tvoc_value) + Some(tvoc_value) => Ok(tvoc_value), } } } diff --git a/apis/air_quality/src/tests.rs b/apis/air_quality/src/tests.rs index 6251c5b8..de761683 100644 --- a/apis/air_quality/src/tests.rs +++ b/apis/air_quality/src/tests.rs @@ -1,7 +1,9 @@ +use crate::AirQualityListener; use core::cell::Cell; use libtock_platform::{share::scope, ErrorCode, Syscalls, YieldNoWaitReturn}; use libtock_unittest::fake; -use crate::{AirQuality, AirQualityListener}; + +type AirQuality = super::AirQuality; #[test] fn no_driver() { diff --git a/src/lib.rs b/src/lib.rs index 492b8abe..70553cee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub use libtock_runtime as runtime; pub mod air_quality { use libtock_air_quality as air_quality; pub type AirQuality = air_quality::AirQuality; + pub use air_quality::AirQualityListener; } pub mod alarm {