Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Air Quality API #464

Merged
merged 10 commits into from
May 19, 2023
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://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));
RaresCon marked this conversation as resolved.
Show resolved Hide resolved
}

#[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