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

First iteration of air_quality API based on the temperature API #16

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ libtock_low_level_debug = { path = "apis/low_level_debug" }
libtock_platform = { path = "platform" }
libtock_runtime = { path = "runtime" }
libtock_temperature = { path = "apis/temperature" }
libtock_air_quality = { path = "apis/air_quality" }

[profile.dev]
panic = "abort"
Expand All @@ -41,6 +42,7 @@ members = [
"apis/leds",
"apis/low_level_debug",
"apis/temperature",
"apis/air_quality",
"panic_handlers/debug_panic",
"panic_handlers/small_panic",
"platform",
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,)>>,
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) {
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();
}
}
}
}
}
});

match listener.get() {
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));
}

#[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() {
RaresCon marked this conversation as resolved.
Show resolved Hide resolved
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() {
RaresCon marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -44,3 +44,8 @@ pub mod temperature {
pub type Temperature = temperature::Temperature<super::runtime::TockSyscalls>;
pub use temperature::TemperatureListener;
}

pub mod air_quality {
use libtock_air_quality as air_quality;
pub type AirQuality = air_quality::AirQuality<super::runtime::TockSyscalls>;
}
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