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 @@ -34,6 +35,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" }
100 changes: 100 additions & 0 deletions apis/air_quality/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#![no_std]

use core::cell::Cell;
use libtock_platform::{
share::scope, share::Handle, DefaultConfig, ErrorCode, Subscribe, Syscalls,
};
use Value::{Tvoc, CO2};

#[cfg(test)]
mod tests;

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>(
listener: &'share Cell<Option<(u32,)>>,
RaresCon marked this conversation as resolved.
Show resolved Hide resolved
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 listener: Cell<Option<(u32,)>> = Cell::new(None);

scope(|subscribe| {
if let Ok(()) = Self::register_listener(&listener, subscribe) {
RaresCon marked this conversation as resolved.
Show resolved Hide resolved
match read_type {
CO2 => {
if let Ok(()) = Self::read_co2() {
while listener.get() == None {
S::yield_wait();
}
}
}
Tvoc => {
if let Ok(()) = Self::read_tvoc() {
while listener.get() == None {
S::yield_wait();
}
}
}
}
}
});
RaresCon marked this conversation as resolved.
Show resolved Hide resolved

match listener.get() {
RaresCon marked this conversation as resolved.
Show resolved Hide resolved
None => Err(ErrorCode::Busy),
Some((data_val,)) => Ok(data_val),
}
}
}

// -----------------------------------------------------------------------------
// 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;
116 changes: 116 additions & 0 deletions apis/air_quality/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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 listener: Cell<Option<(u32,)>> = Cell::new(None);

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!(listener.get(), Some((100,)));

assert_eq!(AirQuality::read_tvoc(), Ok(()));
driver.set_value(100);
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall);
assert_eq!(listener.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