Skip to content

Commit

Permalink
Merge #465
Browse files Browse the repository at this point in the history
465: Ambient Light API r=jrvanwhy a=DanutAldea

### Pull Request Overview
This PR adds an Ambient Light API, which includes:

- function for checking the existence of the driver
- functions for initating readings of the luminance, registering and unregistering a listener
- function for synchronous reading of values of luminance and 2 public functions for easier use of the API
- unit tests

Alongside the API, this PR adds a fake driver (and unit tests for it) for testing the API.

### Testing Strategy
This pull request was tested using unit tests made specifically for this API and fake driver.

### TODO or Help Wanted
This pull request still needs feedback / code review.
Help is needed to test this on a board, as I do not have access to one which has a light sensor module
`@hudson-ayers` could you, please, test it on a imix

Co-authored-by: Danut Aldea <danutz.aldea23@gmail.com>
Co-authored-by: George Danut Aldea <45511762+DanutAldea@users.noreply.github.com>
  • Loading branch information
3 people committed May 16, 2023
2 parents bd77788 + b70eb0c commit be72de6
Show file tree
Hide file tree
Showing 9 changed files with 374 additions and 0 deletions.
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_alarm = { path = "apis/alarm" }
libtock_ambient_light = { path = "apis/ambient_light" }
libtock_buttons = { path = "apis/buttons" }
libtock_console = { path = "apis/console" }
libtock_debug_panic = { path = "panic_handlers/debug_panic" }
Expand Down Expand Up @@ -43,6 +44,7 @@ members = [
"apis/low_level_debug",
"apis/proximity",
"apis/temperature",
"apis/ambient_light",
"panic_handlers/debug_panic",
"panic_handlers/small_panic",
"platform",
Expand Down
14 changes: 14 additions & 0 deletions apis/ambient_light/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "libtock_ambient_light"
version = "0.1.0"
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
license = "MIT/Apache-2.0"
edition = "2021"
repository = "https://www.github.com/tock/libtock-rs"
description = "libtock ambient light driver"

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

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

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

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

impl<S: Syscalls> AmbientLight<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 light intensity reading.
pub fn read_intensity() -> Result<(), ErrorCode> {
S::command(DRIVER_NUM, READ_INTENSITY, 0, 0).to_result()
}

/// Register an events listener
pub fn register_listener<'share, F: Fn(u32)>(
listener: &'share IntensityListener<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 light intensity measurement.
/// Returns Ok(intensity_value) if the operation was successful
/// intensity_value is returned in lux
pub fn read_intensity_sync() -> Result<u32, ErrorCode> {
let intensity_cell: Cell<Option<u32>> = Cell::new(None);
let listener = IntensityListener(|intensity_val| {
intensity_cell.set(Some(intensity_val));
});

share::scope(|subscribe| {
Self::register_listener(&listener, subscribe)?;
Self::read_intensity()?;
while intensity_cell.get() == None {
S::yield_wait();
}

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

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

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

#[cfg(test)]
mod tests;

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

const DRIVER_NUM: u32 = 0x60002;

// Command IDs

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

use crate::IntensityListener;

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

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

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

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

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

assert_eq!(AmbientLight::read_intensity(), Ok(()));
assert!(driver.is_busy());

assert_eq!(AmbientLight::read_intensity(), Err(ErrorCode::Busy));
assert_eq!(AmbientLight::read_intensity_sync(), Err(ErrorCode::Busy));
}

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

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

assert_eq!(
AmbientLight::register_listener(&listener, subscribe),
Ok(())
);
assert_eq!(AmbientLight::read_intensity(), Ok(()));
driver.set_value(100);
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall);
assert_eq!(intensity_cell.get(), Some(100));

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

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

driver.set_value_sync(1000);
assert_eq!(AmbientLight::read_intensity_sync(), Ok(1000));
}
36 changes: 36 additions & 0 deletions examples/ambient_light.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//! A simple libtock-rs example. Checks for ambient light 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::ambient_light::AmbientLight;
use libtock::runtime::{set_main, stack_size};

set_main! {main}
stack_size! {0x200}

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

loop {
match AmbientLight::read_intensity_sync() {
Ok(intensity_val) => writeln!(
Console::writer(),
"Light intensity: {} lux\n",
intensity_val
)
.unwrap(),
Err(_) => writeln!(Console::writer(), "error while reading light intensity",).unwrap(),
}

Alarm::sleep_for(Milliseconds(2000)).unwrap();
}
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ pub mod alarm {
pub type Alarm = alarm::Alarm<super::runtime::TockSyscalls>;
pub use alarm::{Convert, Hz, Milliseconds, Ticks};
}
pub mod ambient_light {
use libtock_ambient_light as ambient_light;
pub type AmbientLight = ambient_light::AmbientLight<super::runtime::TockSyscalls>;
pub use ambient_light::IntensityListener;
}
pub mod buttons {
use libtock_buttons as buttons;
pub type Buttons = buttons::Buttons<super::runtime::TockSyscalls>;
Expand Down
85 changes: 85 additions & 0 deletions unittest/src/fake/ambient_light/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! Fake implementation of the Ambient Light API, documented here:
//! https://github.com/tock/tock/blob/master/doc/syscalls/60002_luminance.md
//!
//! Like the real API, `AmbientLight` controls a fake ambient light sensor. It provides
//! a function `set_value` used to immediately call an upcall with a intensity 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 temperature read,
// because it was impossible to schedule an upcall during the `synchronous` read in other ways.
pub struct AmbientLight {
busy: Cell<bool>,
upcall_on_command: Cell<Option<u32>>,
share_ref: DriverShareRef,
}

impl AmbientLight {
pub fn new() -> std::rc::Rc<AmbientLight> {
std::rc::Rc::new(AmbientLight {
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: 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_command.set(Some(value));
}
}

impl crate::fake::SyscallDriver for AmbientLight {
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_INTENSITY => {
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 = 0x60002;

// Command IDs

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

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

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

assert!(amb.command(READ_INTENSITY, 0, 0).is_success());

assert_eq!(
amb.command(READ_INTENSITY, 0, 0).get_failure(),
Some(ErrorCode::Busy)
);

amb.set_value(100);
assert!(amb.command(READ_INTENSITY, 0, 1).is_success());
amb.set_value(100);

amb.set_value_sync(100);
assert!(amb.command(READ_INTENSITY, 0, 1).is_success());
assert!(amb.command(READ_INTENSITY, 0, 1).is_success());
}

// Integration test that verifies AmbientLight works with fake::Kernel and
// libtock_platform::Syscalls.
#[test]
fn kernel_integration() {
use libtock_platform::Syscalls;
let kernel = fake::Kernel::new();
let ambient_light = AmbientLight::new();
kernel.add_driver(&ambient_light);
assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success());
assert!(fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 0, 0).is_success());
assert_eq!(
fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 0, 0).get_failure(),
Some(ErrorCode::Busy)
);
ambient_light.set_value(100);
assert!(fake::Syscalls::command(DRIVER_NUM, READ_INTENSITY, 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(())
);

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

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

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

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

0 comments on commit be72de6

Please sign in to comment.