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 @@ -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};

RaresCon marked this conversation as resolved.
Show resolved Hide resolved
#[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)))
}
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