capsules_extra/
air_quality.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Provides userspace with access to air quality sensors.
6//!
7//! Usage
8//! -----
9//!
10//! You need a device that provides the `hil::sensors::AirQualityDriver` trait.
11//!
12//! ```rust,ignore
13//! # use kernel::static_init;
14//!
15//! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability);
16//! let grant_temperature = board_kernel.create_grant(&grant_cap);
17//!
18//! let temp = static_init!(
19//!        capsules::temperature::AirQualitySensor<'static>,
20//!        capsules::temperature::AirQualitySensor::new(si7021,
21//!                                                 board_kernel.create_grant(&grant_cap)));
22//!
23//! kernel::hil::sensors::AirQualityDriver::set_client(si7021, temp);
24//! ```
25
26use core::cell::Cell;
27use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
28use kernel::hil;
29use kernel::syscall::{CommandReturn, SyscallDriver};
30use kernel::{ErrorCode, ProcessId};
31
32/// Syscall driver number.
33use capsules_core::driver;
34pub const DRIVER_NUM: usize = driver::NUM::AirQuality as usize;
35
36#[derive(Clone, Copy, PartialEq, Default)]
37enum Operation {
38    #[default]
39    None,
40    CO2,
41    TVOC,
42}
43
44#[derive(Default)]
45pub struct App {
46    operation: Operation,
47}
48
49pub struct AirQualitySensor<'a> {
50    driver: &'a dyn hil::sensors::AirQualityDriver<'a>,
51    apps: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
52    busy: Cell<bool>,
53}
54
55impl<'a> AirQualitySensor<'a> {
56    pub fn new(
57        driver: &'a dyn hil::sensors::AirQualityDriver<'a>,
58        grant: Grant<App, UpcallCount<1>, AllowRoCount<0>, AllowRwCount<0>>,
59    ) -> AirQualitySensor<'a> {
60        AirQualitySensor {
61            driver,
62            apps: grant,
63            busy: Cell::new(false),
64        }
65    }
66
67    fn enqueue_command(&self, processid: ProcessId, op: Operation) -> CommandReturn {
68        self.apps
69            .enter(processid, |app, _| {
70                if !self.busy.get() {
71                    self.busy.set(true);
72                    app.operation = op;
73
74                    let rcode = match op {
75                        Operation::None => Err(ErrorCode::FAIL),
76                        Operation::CO2 => self.driver.read_co2(),
77                        Operation::TVOC => self.driver.read_tvoc(),
78                    };
79                    let eres = ErrorCode::try_from(rcode);
80
81                    match eres {
82                        Ok(ecode) => CommandReturn::failure(ecode),
83                        _ => CommandReturn::success(),
84                    }
85                } else {
86                    CommandReturn::failure(ErrorCode::BUSY)
87                }
88            })
89            .unwrap_or_else(|err| CommandReturn::failure(err.into()))
90    }
91}
92
93impl hil::sensors::AirQualityClient for AirQualitySensor<'_> {
94    fn environment_specified(&self, _result: Result<(), ErrorCode>) {
95        unimplemented!();
96    }
97
98    fn co2_data_available(&self, value: Result<u32, ErrorCode>) {
99        for cntr in self.apps.iter() {
100            cntr.enter(|app, upcalls| {
101                if app.operation == Operation::CO2 {
102                    value
103                        .map(|co2| {
104                            self.busy.set(false);
105                            app.operation = Operation::None;
106                            upcalls.schedule_upcall(0, (co2 as usize, 0, 0)).ok();
107                        })
108                        .ok();
109                }
110            });
111        }
112    }
113
114    fn tvoc_data_available(&self, value: Result<u32, ErrorCode>) {
115        for cntr in self.apps.iter() {
116            cntr.enter(|app, upcalls| {
117                if app.operation == Operation::TVOC {
118                    value
119                        .map(|tvoc| {
120                            self.busy.set(false);
121                            app.operation = Operation::None;
122                            upcalls.schedule_upcall(0, (tvoc as usize, 0, 0)).ok();
123                        })
124                        .ok();
125                }
126            });
127        }
128    }
129}
130
131impl SyscallDriver for AirQualitySensor<'_> {
132    fn command(
133        &self,
134        command_num: usize,
135        _: usize,
136        _: usize,
137        processid: ProcessId,
138    ) -> CommandReturn {
139        match command_num {
140            // check whether the driver exists!!
141            0 => CommandReturn::success(),
142
143            // specify temperature and humidity (TODO)
144            1 => CommandReturn::failure(ErrorCode::NOSUPPORT),
145
146            // read CO2
147            2 => self.enqueue_command(processid, Operation::CO2),
148
149            // read TVOC
150            3 => self.enqueue_command(processid, Operation::TVOC),
151
152            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
153        }
154    }
155
156    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
157        self.apps.enter(processid, |_, _| {})
158    }
159}