-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
9 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
} |
Oops, something went wrong.