Skip to content

Commit

Permalink
Merge tock#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 authored May 19, 2023
2 parents 617b21a + 04f500a commit 0f7c976
Show file tree
Hide file tree
Showing 8 changed files with 508 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ version = "0.1.0"

[dependencies]
libtock_adc = { path = "apis/adc"}
libtock_air_quality = { path = "apis/air_quality" }
libtock_alarm = { path = "apis/alarm" }
libtock_ambient_light = { path = "apis/ambient_light" }
libtock_buttons = { path = "apis/buttons" }
Expand Down Expand Up @@ -41,6 +42,7 @@ debug = true
exclude = ["tock"]
members = [
"apis/adc",
"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://www.github.com/tock/libtock-rs"
description = "libtock air quality driver"

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

[dev-dependencies]
libtock_unittest = { path = "../../unittest" }
128 changes: 128 additions & 0 deletions apis/air_quality/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#![no_std]

use core::cell::Cell;
use libtock_platform::subscribe::OneId;
use libtock_platform::{
share::scope, share::Handle, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall,
};
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> {
/// 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<F>,
subscribe: Handle<Subscribe<'share, S, DRIVER_NUM, 0>>,
) -> 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 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<u32, ErrorCode> {
Self::read_data_sync(CO2)
}

/// Public wrapper for `read_data_sync` for TVOC synchronous measurement
pub fn read_tvoc_sync() -> Result<u32, ErrorCode> {
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)),
(Err(co2_error), _) => Err(co2_error),
(_, Err(tvoc_error)) => Err(tvoc_error),
}
}

/// 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<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)?;
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;
120 changes: 120 additions & 0 deletions apis/air_quality/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use crate::AirQualityListener;
use core::cell::Cell;
use libtock_platform::{share::scope, ErrorCode, Syscalls, YieldNoWaitReturn};
use libtock_unittest::fake;

type AirQuality = super::AirQuality<fake::Syscalls>;

#[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)))
}
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ pub mod adc {
pub type Adc = adc::Adc<super::runtime::TockSyscalls>;
pub use adc::ADCListener;
}

pub mod air_quality {
use libtock_air_quality as air_quality;
pub type AirQuality = air_quality::AirQuality<super::runtime::TockSyscalls>;
pub use air_quality::AirQualityListener;
}

pub mod alarm {
use libtock_alarm as alarm;
pub type Alarm = alarm::Alarm<super::runtime::TockSyscalls>;
Expand Down
Loading

0 comments on commit 0f7c976

Please sign in to comment.