Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ libtock_proximity = { path = "apis/proximity" }
libtock_runtime = { path = "runtime" }
libtock_sound_pressure = {path = "apis/sound_pressure"}
libtock_temperature = { path = "apis/temperature" }
libtock_humidity = { path = "apis/humidity" }

[profile.dev]
panic = "abort"
Expand All @@ -46,6 +47,7 @@ members = [
"apis/buttons",
"apis/buzzer",
"apis/console",
"apis/humidity",
"apis/leds",
"apis/low_level_debug",
"apis/ninedof",
Expand Down
12 changes: 12 additions & 0 deletions apis/humidity/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "libtock_humidity"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

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

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

use core::cell::Cell;
use libtock_platform::{
share, subscribe::OneId, DefaultConfig, ErrorCode, Subscribe, Syscalls, Upcall,
};

pub struct Humidity<S: Syscalls>(S);

impl<S: Syscalls> Humidity<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()
}

/// Initiate a humidity measurement.
///
/// This function is used both for synchronous and asynchronous readings
pub fn read() -> Result<(), ErrorCode> {
S::command(DRIVER_NUM, READ_HUM, 0, 0).to_result()
}

/// Register an events listener
pub fn register_listener<'share, F: Fn(u32)>(
listener: &'share HumidityListener<F>,
subscribe: share::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 synchronous humidity measurement.
/// Returns Ok(humidity_value) if the operation was successful
/// humidity_value is returned in hundreds of percent
pub fn read_sync() -> Result<u32, ErrorCode> {
let humidity_cell: Cell<Option<u32>> = Cell::new(None);
let listener = HumidityListener(|humidity_val| {
humidity_cell.set(Some(humidity_val));
});
share::scope(|subscribe| {
Self::register_listener(&listener, subscribe)?;
Self::read()?;
while humidity_cell.get() == None {
S::yield_wait();
}

match humidity_cell.get() {
None => Err(ErrorCode::Busy),
Some(humidity_val) => Ok(humidity_val),
}
})
}
}

/// A wrapper around a closure to be registered and called when
/// a humidity reading is done.
///
/// ```ignore
/// let listener = HumidityListener(|humidity_val| {
/// // make use of the humidity value
/// });
/// ```
pub struct HumidityListener<F: Fn(u32)>(pub F);

impl<F: Fn(u32)> Upcall<OneId<DRIVER_NUM, 0>> for HumidityListener<F> {
fn upcall(&self, humidity: u32, _arg1: u32, _arg2: u32) {
self.0(humidity)
}
}

#[cfg(test)]
mod tests;

// -----------------------------------------------------------------------------
// Driver number and command IDs
// -----------------------------------------------------------------------------

const DRIVER_NUM: u32 = 0x60001;

// Command IDs

const EXISTS: u32 = 0;
const READ_HUM: u32 = 1;
73 changes: 73 additions & 0 deletions apis/humidity/src/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use core::cell::Cell;
use libtock_platform::{share, ErrorCode, Syscalls, YieldNoWaitReturn};
use libtock_unittest::fake;

use crate::HumidityListener;

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

#[test]
fn no_driver() {
let _kernel = fake::Kernel::new();
assert_eq!(Humidity::exists(), Err(ErrorCode::NoDevice));
}

#[test]
fn driver_check() {
let kernel = fake::Kernel::new();
let driver = fake::Humidity::new();
kernel.add_driver(&driver);

assert_eq!(Humidity::exists(), Ok(()));
}

#[test]
fn read_humidity() {
let kernel = fake::Kernel::new();
let driver = fake::Humidity::new();
kernel.add_driver(&driver);

assert_eq!(Humidity::read(), Ok(()));
assert!(driver.is_busy());

assert_eq!(Humidity::read(), Err(ErrorCode::Busy));
assert_eq!(Humidity::read_sync(), Err(ErrorCode::Busy));
}

#[test]
fn register_unregister_listener() {
let kernel = fake::Kernel::new();
let driver = fake::Humidity::new();
kernel.add_driver(&driver);

let humidity_cell: Cell<Option<u32>> = Cell::new(None);
let listener = HumidityListener(|val| {
humidity_cell.set(Some(val));
});
share::scope(|subscribe| {
assert_eq!(Humidity::read(), Ok(()));
driver.set_value(100);
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall);

assert_eq!(Humidity::register_listener(&listener, subscribe), Ok(()));
assert_eq!(Humidity::read(), Ok(()));
driver.set_value(100);
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall);
assert_eq!(humidity_cell.get(), Some(100));

Humidity::unregister_listener();
assert_eq!(Humidity::read(), Ok(()));
driver.set_value(100);
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall);
});
}

#[test]
fn read_humidity_sync() {
let kernel = fake::Kernel::new();
let driver = fake::Humidity::new();
kernel.add_driver(&driver);

driver.set_value_sync(1000);
assert_eq!(Humidity::read_sync(), Ok(1000));
}
37 changes: 37 additions & 0 deletions examples/humidity.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//! A simple libtock-rs example. Checks for humidity driver
//! and samples the sensor every 2 seconds.

#![no_main]
#![no_std]

use core::fmt::Write;
use libtock::console::Console;

use libtock::alarm::{Alarm, Milliseconds};
use libtock::humidity::Humidity;
use libtock::runtime::{set_main, stack_size};

set_main! {main}
stack_size! {0x200}

fn main() {
if Humidity::exists().is_err() {
writeln!(Console::writer(), "humidity driver unavailable").unwrap();
return;
}

loop {
match Humidity::read_sync() {
Ok(hum_val) => writeln!(
Console::writer(),
"Humidity: {}.{}%\n",
hum_val / 100,
hum_val % 100
)
.unwrap(),
Err(_) => writeln!(Console::writer(), "error while reading humidity",).unwrap(),
}

Alarm::sleep_for(Milliseconds(2000)).unwrap();
}
}
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ pub mod gpio {
PullDown, PullNone, PullUp,
};
}

pub mod humidity {
use libtock_humidity as humidity;
pub type Humidity = humidity::Humidity<super::runtime::TockSyscalls>;
pub use humidity::HumidityListener;
}

pub mod leds {
use libtock_leds as leds;
pub type Leds = leds::Leds<super::runtime::TockSyscalls>;
Expand Down
85 changes: 85 additions & 0 deletions unittest/src/fake/humidity/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! Fake implementation of the Humidity API, documented here:
//! https://github.com/tock/tock/blob/master/doc/syscalls/60001_humidity.md
//!
//! Like the real API, `Humidity` controls a fake humidity sensor. It provides
//! a function `set_value` used to immediately call an upcall with a humidity value read by the sensor
//! and a function 'set_value_sync' used to call the upcall when the read command is received.

use crate::{DriverInfo, DriverShareRef};
use libtock_platform::{CommandReturn, ErrorCode};
use std::cell::Cell;

// The `upcall_on_command` field is set to Some(value) if an upcall(with value as its argument) should be called when read command is received,
// or None otherwise. It was needed for testing `read_sync` library function which simulates a synchronous humidity read,
// because it was impossible to schedule an upcall during the `synchronous` read in other ways.
pub struct Humidity {
busy: Cell<bool>,
upcall_on_command: Cell<Option<i32>>,
share_ref: DriverShareRef,
}

impl Humidity {
pub fn new() -> std::rc::Rc<Humidity> {
std::rc::Rc::new(Humidity {
busy: Cell::new(false),
upcall_on_command: Cell::new(None),
share_ref: Default::default(),
})
}

pub fn is_busy(&self) -> bool {
self.busy.get()
}
pub fn set_value(&self, value: i32) {
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: i32) {
self.upcall_on_command.set(Some(value));
}
}

impl crate::fake::SyscallDriver for Humidity {
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_HUM => {
if self.busy.get() {
return crate::command_return::failure(ErrorCode::Busy);
}
self.busy.set(true);
if let Some(val) = self.upcall_on_command.take() {
self.set_value(val);
}
crate::command_return::success()
}
_ => crate::command_return::failure(ErrorCode::NoSupport),
}
}
}

#[cfg(test)]
mod tests;
// -----------------------------------------------------------------------------
// Driver number and command IDs
// -----------------------------------------------------------------------------

const DRIVER_NUM: u32 = 0x60001;

// Command IDs

const EXISTS: u32 = 0;
const READ_HUM: u32 = 1;
67 changes: 67 additions & 0 deletions unittest/src/fake/humidity/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::fake::{self, SyscallDriver};
use fake::humidity::*;
use libtock_platform::{share, DefaultConfig, YieldNoWaitReturn};

//Test the command implementation
#[test]
fn command() {
let hum = Humidity::new();

assert!(hum.command(EXISTS, 1, 2).is_success());

assert!(hum.command(READ_HUM, 0, 0).is_success());

assert_eq!(
hum.command(READ_HUM, 0, 0).get_failure(),
Some(ErrorCode::Busy)
);

hum.set_value(100);
assert!(hum.command(READ_HUM, 0, 1).is_success());
hum.set_value(100);

hum.set_value_sync(100);
assert!(hum.command(READ_HUM, 0, 1).is_success());
assert!(hum.command(READ_HUM, 0, 1).is_success());
}

// Integration test that verifies Humidity works with fake::Kernel and
// libtock_platform::Syscalls.
#[test]
fn kernel_integration() {
use libtock_platform::Syscalls;
let kernel = fake::Kernel::new();
let hum = Humidity::new();
kernel.add_driver(&hum);
assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success());
assert!(fake::Syscalls::command(DRIVER_NUM, READ_HUM, 0, 0).is_success());
assert_eq!(
fake::Syscalls::command(DRIVER_NUM, READ_HUM, 0, 0).get_failure(),
Some(ErrorCode::Busy)
);
hum.set_value(100);
assert!(fake::Syscalls::command(DRIVER_NUM, READ_HUM, 0, 1).is_success());

let listener = Cell::<Option<(u32,)>>::new(None);
share::scope(|subscribe| {
assert_eq!(
fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener),
Ok(())
);

hum.set_value(100);
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall);
assert_eq!(listener.get(), Some((100,)));

hum.set_value(200);
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall);

assert!(fake::Syscalls::command(DRIVER_NUM, READ_HUM, 0, 1).is_success());
hum.set_value(200);
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall);

hum.set_value_sync(200);
assert!(fake::Syscalls::command(DRIVER_NUM, READ_HUM, 0, 1).is_success());
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall);
});
}
Loading