Skip to content

Commit

Permalink
Merge #469
Browse files Browse the repository at this point in the history
469: Sound Pressure API r=jrvanwhy a=SheepSeb

## Pull Request Overview

This pull request adds:
- a sound pressure API , similar to the [libtock-c version]( https://github.com/tock/libtock-c/blob/master/libtock/sound_pressure.h ). The API provides a function that simulates a synchronous sound pressusre reading and also functions to initiate a reading, register/unregister a listener, check for driver existence, enable/disable the sound pressure measurement
- an example app which samples the sound pressure sensor every 1 second and prints the measured sound pressure
- a ```fake``` sound pressure driver, for testing
- unit tests for the fake driver and for the user space library

## Testing Strategy
This PR was tested with the unit tests and by running the example app on a microbit v2 board

## Documentation Updated
no updates are required

Co-authored-by: sebastian-nae <naesebi2000@gmail.com>
Co-authored-by: Sebastian Nae <sebnae@Sebastians-MacBook-Pro.local>
Co-authored-by: Sebastian Nae <naesebi2000@gmail.com>
  • Loading branch information
3 people authored May 17, 2023
2 parents c48f816 + 9c79ec5 commit db3560a
Show file tree
Hide file tree
Showing 9 changed files with 382 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ libtock_low_level_debug = { path = "apis/low_level_debug" }
libtock_platform = { path = "platform" }
libtock_proximity = { path = "apis/proximity" }
libtock_runtime = { path = "runtime" }
libtock_sound_pressure = {path = "apis/sound_pressure"}
libtock_temperature = { path = "apis/temperature" }

[profile.dev]
Expand Down
14 changes: 14 additions & 0 deletions apis/sound_pressure/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "libtock_sound_pressure"
version = "0.1.0"
authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
license = "MIT/Apache-2.0"
edition = "2018"
repository = "https://www.github.com/tock/libtock-rs"
description = "libtock sound pressure driver"

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

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

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

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

impl<S: Syscalls> SoundPressure<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 pressure measurement.
/// This function is used both for synchronous and asynchronous readings
pub fn read() -> Result<(), ErrorCode> {
S::command(DRIVER_NUM, READ_PRESSURE, 0, 0).to_result()
}

/// Register an events listener
pub fn register_listener<'share, F: Fn(u32)>(
listener: &'share SoundPressureListener<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)
}

/// Enable sound pressure measurement
pub fn enable() -> Result<(), ErrorCode> {
S::command(DRIVER_NUM, 2, 0, 0).to_result()
}

/// Disable sound pressure measurement
pub fn disable() -> Result<(), ErrorCode> {
S::command(DRIVER_NUM, 3, 0, 0).to_result()
}

/// Initiate a synchronous pressure measurement.
/// Returns Ok(pressure_value) if the operation was successful
/// pressure_value is between 0 and 255
pub fn read_sync() -> Result<u8, ErrorCode> {
let pressure_cell: Cell<Option<u32>> = Cell::new(None);
let listener = SoundPressureListener(|pressure_val| {
pressure_cell.set(Some(pressure_val));
});
share::scope(|subscribe| {
Self::register_listener(&listener, subscribe)?;
Self::read()?;
while pressure_cell.get() == None {
S::yield_wait();
}
match pressure_cell.get() {
None => Err(ErrorCode::Fail),
Some(pressure_val) => {
if !(0..=256).contains(&pressure_val) {
Err(ErrorCode::Invalid)
} else {
Ok(pressure_val as u8)
}
}
}
})
}
}

pub struct SoundPressureListener<F: Fn(u32)>(pub F);
impl<F: Fn(u32)> Upcall<OneId<DRIVER_NUM, 0>> for SoundPressureListener<F> {
fn upcall(&self, pressure_val: u32, _arg1: u32, _arg2: u32) {
(self.0)(pressure_val);
}
}

#[cfg(test)]
mod tests;

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

const DRIVER_NUM: u32 = 0x60006;

// Command IDs

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

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

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

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

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

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

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

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

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

let pressure_cell: Cell<Option<u32>> = Cell::new(None);
let listener = crate::SoundPressureListener(|pressure_val| {
pressure_cell.set(Some(pressure_val));
});

share::scope(|subscribe| {
assert_eq!(SoundPressure::read(), Ok(()));
driver.set_value(100);
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall);

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

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

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

driver.set_value_sync(100);
assert_eq!(SoundPressure::read_sync(), Ok(100));
}
45 changes: 45 additions & 0 deletions examples/sound_pressure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! This example shows how to use the sound pressure driver.
//! It checks for the sound pressure driver and samples the sensor every second.

#![no_main]
#![no_std]

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

use libtock::alarm::{Alarm, Milliseconds};
use libtock::runtime::{set_main, stack_size};
use libtock::sound_pressure::SoundPressure;

set_main! {main}
stack_size! {0x200}

fn main() {
if SoundPressure::exists().is_err() {
writeln!(Console::writer(), "Sound pressure driver not found").unwrap();
return;
}

writeln!(Console::writer(), "Sound pressure driver found").unwrap();
let enable = SoundPressure::enable();
match enable {
Ok(()) => {
writeln!(Console::writer(), "Sound pressure driver enabled").unwrap();
loop {
match SoundPressure::read_sync() {
Ok(sound_pressure_val) => writeln!(
Console::writer(),
"Sound Pressure: {}\n",
sound_pressure_val
)
.unwrap(),
Err(_) => {
writeln!(Console::writer(), "error while reading sound pressure",).unwrap()
}
}
Alarm::sleep_for(Milliseconds(1000)).unwrap();
}
}
Err(_e) => writeln!(Console::writer(), "Sound pressure driver enable failed",).unwrap(),
}
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ pub mod proximity {
use libtock_proximity as proximity;
pub type Proximity = proximity::Proximity<super::runtime::TockSyscalls>;
}
pub mod sound_pressure {
use libtock_sound_pressure as sound_pressure;
pub type SoundPressure = sound_pressure::SoundPressure<super::runtime::TockSyscalls>;
}
pub mod temperature {
use libtock_temperature as temperature;
pub type Temperature = temperature::Temperature<super::runtime::TockSyscalls>;
Expand Down
2 changes: 2 additions & 0 deletions unittest/src/fake/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ mod kernel;
mod leds;
mod low_level_debug;
mod proximity;
mod sound_pressure;
mod syscall_driver;
mod syscalls;
mod temperature;
Expand All @@ -33,6 +34,7 @@ pub use kernel::Kernel;
pub use leds::Leds;
pub use low_level_debug::{LowLevelDebug, Message};
pub use proximity::Proximity;
pub use sound_pressure::SoundPressure;
pub use syscall_driver::SyscallDriver;
pub use syscalls::Syscalls;
pub use temperature::Temperature;
Expand Down
84 changes: 84 additions & 0 deletions unittest/src/fake/sound_pressure/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//! Fake implementation of the SoundPressure API, documented here:
//!
//! Like the real API, `SoundPressure` controls a fake sound pressure sensor. It provides
//! a function `set_value` used to immediately call an upcall with a sound pressure 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 sound pressure read,
// because it was impossible to schedule an upcall during the `synchronous` read in other ways.
pub struct SoundPressure {
busy: Cell<bool>,
upcall_on_command: Cell<Option<u8>>,
share_ref: DriverShareRef,
}

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

impl crate::fake::SyscallDriver for SoundPressure {
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_PRESSURE => {
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 as u8);
}
crate::command_return::success()
}
_ => crate::command_return::failure(ErrorCode::NoSupport),
}
}
}

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

const DRIVER_NUM: u32 = 0x60006;

// Command IDs
const EXISTS: u32 = 0;
const READ_PRESSURE: u32 = 1;
Loading

0 comments on commit db3560a

Please sign in to comment.