Skip to content

Commit

Permalink
Merge #464
Browse files Browse the repository at this point in the history
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
3 people committed May 16, 2023
2 parents bd77788 + 6873b50 commit af19b78
Show file tree
Hide file tree
Showing 8 changed files with 487 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repository = "https://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" }
Expand All @@ -35,6 +36,7 @@ debug = true
[workspace]
exclude = ["tock"]
members = [
"apis/air_quality",
"apis/alarm",
"apis/gpio",
"apis/buttons",
Expand Down
14 changes: 14 additions & 0 deletions apis/air_quality/Cargo.toml
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://github.com/tock/libtock-rs"
description = "libtock air quality driver"

[dependencies]
libtock_platform = { path = "../../platform" }

[dev-dependencies]
libtock_unittest = { path = "../../unittest" }
111 changes: 111 additions & 0 deletions apis/air_quality/src/lib.rs
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;
118 changes: 118 additions & 0 deletions apis/air_quality/src/tests.rs
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)))
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<super::runtime::TockSyscalls>;
}

pub mod alarm {
use libtock_alarm as alarm;
pub type Alarm = alarm::Alarm<super::runtime::TockSyscalls>;
Expand Down
119 changes: 119 additions & 0 deletions unittest/src/fake/air_quality/mod.rs
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;
Loading

0 comments on commit af19b78

Please sign in to comment.