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

Temperature API #451

Merged
merged 12 commits into from
Feb 7, 2023
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ libtock_leds = { path = "apis/leds" }
libtock_low_level_debug = { path = "apis/low_level_debug" }
libtock_platform = { path = "platform" }
libtock_runtime = { path = "runtime" }
libtock_temperature = { path = "apis/temperature"}

[profile.dev]
panic = "abort"
Expand All @@ -38,6 +39,7 @@ members = [
"apis/console",
"apis/leds",
"apis/low_level_debug",
"apis/temperature",
"panic_handlers/debug_panic",
"panic_handlers/small_panic",
"platform",
Expand Down
14 changes: 14 additions & 0 deletions apis/temperature/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "libtock_temperature"
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 temperature driver"

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

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

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

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

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

/// Register an events listener
pub fn register_listener<'share, F: Fn(i32)>(
listener: &'share TemperatureListener<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 temperature measurement.
/// Returns Ok(temperature_value) if the operation was successful
/// temperature_value is returned in hundreds of centigrades
pub fn read_temperature_sync() -> Result<i32, ErrorCode> {
let temperature_cell: Cell<Option<i32>> = Cell::new(None);
let listener = TemperatureListener(|temp_val| {
temperature_cell.set(Some(temp_val));
});
jrvanwhy marked this conversation as resolved.
Show resolved Hide resolved
share::scope(|subscribe| {
if let Ok(()) = Self::register_listener(&listener, subscribe) {
if let Ok(()) = Self::read_temperature() {
while temperature_cell.get() == None {
S::yield_wait();
}
}
}
});

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

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

#[cfg(test)]
mod tests;

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

const DRIVER_NUM: u32 = 0x60000;

// Command IDs

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

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

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

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

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

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

assert_eq!(Temperature::read_temperature(), Ok(()));
assert!(driver.is_busy());

assert_eq!(Temperature::read_temperature(), Err(ErrorCode::Busy));
assert_eq!(Temperature::read_temperature_sync(), Err(ErrorCode::Busy));
}

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

let temperature_cell: Cell<Option<i32>> = Cell::new(None);
let listener = crate::TemperatureListener(|temp_val| {
temperature_cell.set(Some(temp_val));
});
share::scope(|subscribe| {
assert_eq!(Temperature::read_temperature(), Ok(()));
driver.set_value(100);
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall);

assert_eq!(Temperature::register_listener(&listener, subscribe), Ok(()));
assert_eq!(Temperature::read_temperature(), Ok(()));
driver.set_value(100);
assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall);
assert_eq!(temperature_cell.get(), Some(100));

Temperature::unregister_listener();
assert_eq!(Temperature::read_temperature(), 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::Temperature::new();
kernel.add_driver(&driver);

driver.set_value_sync(1000);
assert_eq!(Temperature::read_temperature_sync(), Ok(1000));
}

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

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

set_main! {main}
stack_size! {0x200}

fn main() {
match Temperature::exists() {
Ok(()) => writeln!(Console::writer(), "temperature driver available").unwrap(),
Err(_) => {
writeln!(Console::writer(), "temperature driver unavailable").unwrap();
return;
}
}

loop {
match Temperature::read_temperature_sync() {
Ok(temp_val) => {
if temp_val >= 0 {
writeln!(
Console::writer(),
"Temperature: {}.{}*C\n",
temp_val / 100,
temp_val % 100
)
.unwrap()
} else {
writeln!(
Console::writer(),
"Temperature: -{}.{}*C\n",
(0 - temp_val) / 100,
(0 - temp_val) % 100
)
.unwrap()
}
alexandruCarp marked this conversation as resolved.
Show resolved Hide resolved
}
Err(_) => writeln!(Console::writer(), "error while reading temperature",).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 @@ -28,3 +28,8 @@ pub mod low_level_debug {
pub type LowLevelDebug = lldb::LowLevelDebug<super::runtime::TockSyscalls>;
pub use lldb::AlertCode;
}

pub mod temperature {
use libtock_temperature as temperature;
pub type Temperature = temperature::Temperature<super::runtime::TockSyscalls>;
}
2 changes: 2 additions & 0 deletions unittest/src/fake/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod leds;
mod low_level_debug;
mod syscall_driver;
mod syscalls;
mod temperature;

pub use alarm::Alarm;
pub use buttons::Buttons;
Expand All @@ -28,6 +29,7 @@ pub use leds::Leds;
pub use low_level_debug::{LowLevelDebug, Message};
pub use syscall_driver::SyscallDriver;
pub use syscalls::Syscalls;
pub use temperature::Temperature;

#[cfg(test)]
mod kernel_tests;
85 changes: 85 additions & 0 deletions unittest/src/fake/temperature/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! Fake implementation of the Temperature API, documented here:
//! https://github.com/tock/tock/blob/master/doc/syscalls/60000_ambient_temperature.md
//!
//! Like the real API, `Temperature` controls a fake temperature sensor. It provides
//! a function `set_value` used to immediately call an upcall with a temperature 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 Temperature {
busy: Cell<bool>,
upcall_on_command: Cell<Option<i32>>,
share_ref: DriverShareRef,
}

impl Temperature {
pub fn new() -> std::rc::Rc<Temperature> {
std::rc::Rc::new(Temperature {
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 Temperature {
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_TEMP => {
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 = 0x60000;

// Command IDs

const EXISTS: u32 = 0;
const READ_TEMP: u32 = 1;
Loading