-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
464: Air Quality API r=jrvanwhy a=RaresCon ### Pull Request Overview This PR adds an Air Quality API, which includes: - function for checking the existence of the driver - functions for initating readings for CO2 and TVOC levels - a private function and enum for synchronous reading of values for CO2 and TVOC levels and 3 public functions for easier use of the API - unit tests Alongside the API, this PR adds a fake driver (and unit tests for it) for testing the API. ### Testing Strategy This pull request was tested using unit tests made specifically for this API and fake driver. ### TODO or Help Wanted This pull request still needs feedback / code review. I will add documentation in the files and an example application using this API. ### Documentation Updated - [x] No updates required. Co-authored-by: RaresCon <rares.constantin2002@gmail.com> Co-authored-by: Rareș Constantin <95525840+RaresCon@users.noreply.github.com>
- Loading branch information
Showing
8 changed files
with
487 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
[package] | ||
name = "libtock_air_quality" | ||
version = "0.1.0" | ||
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"] | ||
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" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
#![no_std] | ||
|
||
use core::cell::Cell; | ||
use libtock_platform::{share::scope, share::Handle, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall}; | ||
use libtock_platform::subscribe::OneId; | ||
use Value::{Tvoc, CO2}; | ||
|
||
enum Value { | ||
CO2 = READ_CO2 as isize, | ||
Tvoc = READ_TVOC as isize, | ||
} | ||
|
||
pub struct AirQuality<S: Syscalls>(S); | ||
|
||
impl<S: Syscalls> AirQuality<S> { | ||
pub fn exists() -> Result<(), ErrorCode> { | ||
S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() | ||
} | ||
|
||
pub fn register_listener<'share, F: Fn(u32)>( | ||
listener: &'share AirQualityListener<F>, | ||
subscribe: Handle<Subscribe<'share, S, DRIVER_NUM, 0>>, | ||
) -> Result<(), ErrorCode> { | ||
S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) | ||
} | ||
|
||
pub fn unregister_listener() { | ||
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_sync() -> Result<u32, ErrorCode> { | ||
Self::read_data_sync(CO2) | ||
} | ||
|
||
pub fn read_tvoc_sync() -> Result<u32, ErrorCode> { | ||
Self::read_data_sync(Tvoc) | ||
} | ||
|
||
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<u32, ErrorCode> { | ||
let data_cell: Cell<Option<u32>> = Cell::new(None); | ||
let listener = AirQualityListener(|data_val| { | ||
data_cell.set(Some(data_val)); | ||
}); | ||
|
||
scope(|subscribe| { | ||
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(); | ||
} | ||
|
||
match data_cell.get() { | ||
None => Err(ErrorCode::Fail), | ||
Some(tvoc_value) => Ok(tvoc_value) | ||
} | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
|
||
pub struct AirQualityListener<F: Fn(u32)>(pub F); | ||
impl<F: Fn(u32)> Upcall<OneId<DRIVER_NUM, 0>> for AirQualityListener<F> { | ||
fn upcall(&self, data_val: u32, _arg1: u32, _arg2: u32) { | ||
self.0(data_val) | ||
} | ||
} | ||
|
||
#[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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
use core::cell::Cell; | ||
use libtock_platform::{share::scope, ErrorCode, Syscalls, YieldNoWaitReturn}; | ||
use libtock_unittest::fake; | ||
use crate::{AirQuality, AirQualityListener}; | ||
|
||
#[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 data_cell: Cell<Option<u32>> = Cell::new(None); | ||
let listener = AirQualityListener(|data_val| { | ||
data_cell.set(Some(data_val)); | ||
}); | ||
|
||
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!(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!(data_cell.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)); | ||
} | ||
|
||
#[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))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
use crate::{DriverInfo, DriverShareRef}; | ||
use libtock_platform::{CommandReturn, ErrorCode}; | ||
use std::cell::Cell; | ||
|
||
pub struct AirQuality { | ||
busy: Cell<bool>, | ||
co2_available: Cell<bool>, | ||
tvoc_available: Cell<bool>, | ||
upcall_on_read: Cell<Option<u32>>, | ||
upcall_on_tuple_read: Cell<Option<(u32, u32)>>, | ||
share_ref: DriverShareRef, | ||
} | ||
|
||
impl AirQuality { | ||
pub fn new() -> std::rc::Rc<AirQuality> { | ||
std::rc::Rc::new(AirQuality { | ||
busy: Cell::new(false), | ||
co2_available: Cell::new(true), | ||
tvoc_available: Cell::new(true), | ||
upcall_on_read: Cell::new(None), | ||
upcall_on_tuple_read: 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_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))); | ||
} | ||
} | ||
|
||
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_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 => { | ||
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_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), | ||
} | ||
} | ||
} | ||
|
||
#[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; |
Oops, something went wrong.