bsec/
lib.rs

1//! Rust API to the
2//! [Bosch BSEC library](https://www.bosch-sensortec.com/software-tools/software/bsec/).
3//!
4//! This documentation will use *bsec* to refer to this crate, while
5//! *Bosch BSEC* is used to refer to the original BSEC library provided by
6//! Bosch.
7//!
8//! ## Important license information
9//!
10//! The *Bosch BSEC* library is proprietary. Thus, the *Bosch BSEC* library and
11//! its documentation cannot be included in the *bsec* Rust crate and need to be
12//! obtained separately.
13//!
14//! While the *bsec* documentation covers the Rust crate itself, you will likely
15//! have to refer to the *Bosch BSEC* documentation at some points to get a full
16//! understanding.
17//!
18//! You are responsible for adhering to the Bosch BSEC lincese terms in your
19//! products, despite the Rust API in this crate being published under a
20//! permissive license.
21//!
22//! * [Bosch BSEC website to obtain your copy](https://www.bosch-sensortec.com/software-tools/software/bsec/)
23//! * [Bosch BESC license terms at the time of writing](https://www.bosch-sensortec.com/media/boschsensortec/downloads/bsec/2017-07-17_clickthrough_license_terms_environmentalib_sw_clean.pdf)
24//!
25//!
26//! ## Getting started
27//!
28//! ### Setup paths to the Bosch BSEC library
29//!
30//! To be able to use this crate, it needs to know where to find the
31//! *Bosch BSEC* header files and library on your system. These paths are
32//! provided as the configuration options `besc_include_path` and
33//! `bsec_library_path` to the Rust compiler.
34//!
35//! You can do this by creating a `.cargo/config` file in your crate with the
36//! following content (adjust the paths accordingly):
37//!
38//! ```toml
39//! [build]
40//! rustflags = [
41//!     '--cfg', 'bsec_include_path="/path/to/BSEC_1.4.8.0_Generic_Release/algo/normal_version/inc"',
42//!     '--cfg', 'bsec_library_path="/path/to/BSEC_1.4.8.0_Generic_Release/algo/normal_version/bin/target-arch"',
43//! ]
44//! ```
45//!
46//! (You might want to also have a look at the instructions for
47//! [libalgobsec-sys](https://crates.io/crates/libalgobsec-sys) providing the
48//! actual low-level bindings.)
49//!
50//! ### Implement necessary traits
51//!
52//! To be able to use the *bsec* crate, you need implementations of the
53//! [`Clock`] and [`BmeSensor`] traits
54//!
55//! #### Clock
56//!
57//! The [`Clock`] traits allows the BSEC algorithm to obtain timestamps to
58//! schedule sensor measurements accordingly. Your implementation might depend
59//! on your hardware platform or you can use the generic implementation
60//! [`clock::TimePassed`].
61//!
62//! #### BmeSensor
63//!
64//! The [`BmeSensor`] trait allows the BSEC algorithm to communicate with your
65//! BME sensor and obtain measurements. You can implement it yourself or use
66//! a ready-made implementation shipped with *bsec*:
67//!
68//! * **BME680** implementation is provided as [`bme::bme680::Bme680Sensor`];
69//!   requires the **use-bme680** feature.
70//!
71//!
72//! ### Usage
73//!
74//! The following example demonstrates the basic *bsec* usage. Essentially, you
75//! need to
76//!
77//! * acquire an instance of the library (only one can be in use at any given
78//!   time),
79//! * subscribe to the desired outputs,
80//! * and perform measurements in accordance with the sampling rate.
81//!
82//! The outputs of the BSEC algorithm are also considered virtual sensors.
83//! The inputs to the BSEC algorithm are the sensor measurements and are
84//! considered physical sensors (though there are some special input that do
85//! not actually correspond to any physical sensor).
86//!
87//! ```
88//! use bsec::{Bsec, Input, InputKind, OutputKind, clock::Clock, SampleRate, SubscriptionRequest};
89//! use nb::block;
90//! use std::time::Duration;
91//! #
92//! # #[cfg(not(feature = "test-support"))]
93//! # fn main() { panic!("doctests must be run with `test-support` feature.") }
94//! #
95//! # #[cfg(feature = "test-support")]
96//! # type TimePassed = bsec::clock::test_support::FakeClock;
97//! #
98//! # fn sleep_for(duration: Duration) -> () {}
99//! #
100//! # #[cfg(feature = "test-support")]
101//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
102//! #
103//! # let clock = TimePassed::default();
104//! # let sensor = bsec::bme::test_support::FakeBmeSensor::new(Ok(vec![
105//! #    Input {
106//! #         sensor: InputKind::Temperature,
107//! #         signal: 22.,
108//! #     },
109//! #     Input {
110//! #         sensor: InputKind::Humidity,
111//! #         signal: 40.,
112//! #     },
113//! #     Input {
114//! #         sensor: InputKind::Pressure,
115//! #         signal: 1000.,
116//! #     },
117//! #     Input {
118//! #         sensor: InputKind::GasResistor,
119//! #         signal: 6000.,
120//! #     },
121//! # ]));
122//!
123//! // Acquire handle to the BSEC library.
124//! // Only one such handle can be acquired at any time.
125//! let mut bsec: Bsec<_, TimePassed, _> = Bsec::init(sensor, &clock)?;
126//!
127//! // Configure the outputs you want to subscribe to.
128//! bsec.update_subscription(&[
129//!     SubscriptionRequest {
130//!         sample_rate: SampleRate::Lp,
131//!         sensor: OutputKind::Iaq,
132//!     },
133//! ])?;
134//!
135//! // We need to feed BSEC regularly with new measurements.
136//! loop {
137//!     // Wait for when the next measurement is due.
138//!     sleep_for(Duration::from_nanos((bsec.next_measurement() - clock.timestamp_ns()) as u64));
139//!
140//!     // Start the measurement.
141//!     let wait_duration = block!(bsec.start_next_measurement())?;
142//!     sleep_for(wait_duration);
143//!     # clock.advance_by(wait_duration);
144//!
145//!     // Process the measurement when ready and print the BSEC outputs.
146//!     let outputs = block!(bsec.process_last_measurement())?;
147//!     for output in &outputs {
148//!         println!("{:?}: {}", output.sensor, output.signal);
149//!     }
150//! #
151//! #   let signals: std::collections::HashMap<bsec::OutputKind, &bsec::Output> =
152//! #       outputs.iter().map(|s| (s.sensor, s)).collect();
153//! #   assert!(
154//! #       (signals.get(&bsec::OutputKind::Iaq).unwrap().signal - 50.).abs()
155//! #           < f64::EPSILON
156//! #   );
157//! #   return Ok(())
158//! }
159//! # }
160//! ```
161//!
162//! ## Features
163//!
164//! * **use-bme680**: Enables the [`bme680`] module providing a [`BmeSensor`]
165//!   implementation for the BME680 sensor to use it with *bsec*.
166//! * **test-support**: Provides additional classes for unit testing.
167
168use crate::bme::{BmeSensor, BmeSettingsHandle};
169use crate::clock::Clock;
170use crate::error::{BsecError, ConversionError, Error};
171#[cfg(not(feature = "docs-rs"))]
172use libalgobsec_sys::{
173    bsec_bme_settings_t, bsec_do_steps, bsec_get_configuration, bsec_get_state, bsec_get_version,
174    bsec_init, bsec_input_t, bsec_library_return_t, bsec_output_t, bsec_physical_sensor_t,
175    bsec_reset_output, bsec_sensor_configuration_t, bsec_sensor_control, bsec_set_configuration,
176    bsec_set_state, bsec_update_subscription, bsec_version_t, bsec_virtual_sensor_t,
177    BSEC_MAX_PHYSICAL_SENSOR, BSEC_MAX_PROPERTY_BLOB_SIZE, BSEC_MAX_STATE_BLOB_SIZE,
178    BSEC_MAX_WORKBUFFER_SIZE,
179};
180use std::borrow::Borrow;
181use std::convert::{From, TryFrom, TryInto};
182use std::fmt::Debug;
183use std::hash::Hash;
184use std::marker::PhantomData;
185use std::sync::atomic::{AtomicBool, Ordering};
186use std::time::Duration;
187
188#[cfg(feature = "docs-rs")]
189#[allow(non_camel_case_types)]
190struct bsec_library_return_t {}
191
192#[cfg(feature = "docs-rs")]
193#[allow(non_camel_case_types)]
194struct bsec_output_t {}
195
196#[cfg(feature = "docs-rs")]
197#[allow(non_camel_case_types)]
198struct bsec_physical_sensor_t {}
199
200#[cfg(feature = "docs-rs")]
201#[allow(non_camel_case_types)]
202struct bsec_sensor_configuration_t {}
203
204#[cfg(feature = "docs-rs")]
205#[allow(non_camel_case_types)]
206struct bsec_virtual_sensor_t {}
207
208pub mod bme;
209pub mod clock;
210pub mod error;
211
212static BSEC_IN_USE: AtomicBool = AtomicBool::new(false);
213
214/// Handle to encapsulates the *Bosch BSEC* library and related state.
215pub struct Bsec<S: BmeSensor, C: Clock, B: Borrow<C>> {
216    bme: S,
217    subscribed: u32,
218    ulp_plus_queue: u32,
219    next_measurement: i64,
220    clock: B,
221    _clock_type: PhantomData<C>,
222}
223
224impl<S: BmeSensor, C: Clock, B: Borrow<C>> Bsec<S, C, B> {
225    /// Initialize the *Bosch BSEC* library and return a handle to interact with
226    /// it.
227    ///
228    /// * `bme`: [`BmeSensor`] implementation to communicate with the BME sensor.
229    /// * `clock`: [`Clock`] implementation to obtain timestamps.
230    pub fn init(bme: S, clock: B) -> Result<Self, Error<S::Error>> {
231        if BSEC_IN_USE
232            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
233            .is_ok()
234        {
235            unsafe {
236                bsec_init().into_result()?;
237            }
238            Ok(Self {
239                bme,
240                subscribed: 0,
241                ulp_plus_queue: 0,
242                next_measurement: clock.borrow().timestamp_ns(),
243                clock,
244                _clock_type: PhantomData,
245            })
246        } else {
247            Err(Error::BsecAlreadyInUse)
248        }
249    }
250
251    /// Change subscription to virtual sensor outputs.
252    ///
253    /// * `requests`: Configuration of virtual sensors and their sample
254    ///   rates to subscribe to.
255    ///
256    /// Returns a vector describing physical sensor and sampling rates required
257    /// as input to the BSEC algorithm.
258    pub fn update_subscription(
259        &mut self,
260        requests: &[SubscriptionRequest],
261    ) -> Result<Vec<RequiredInput>, Error<S::Error>> {
262        let bsec_requested_outputs: Vec<bsec_sensor_configuration_t> =
263            requests.iter().map(From::from).collect();
264        let mut required_sensor_settings = [bsec_sensor_configuration_t {
265            sample_rate: 0.,
266            sensor_id: 0,
267        }; BSEC_MAX_PHYSICAL_SENSOR as usize];
268        let mut n_required_sensor_settings = BSEC_MAX_PHYSICAL_SENSOR as u8;
269        unsafe {
270            bsec_update_subscription(
271                bsec_requested_outputs.as_ptr(),
272                requests
273                    .len()
274                    .try_into()
275                    .or(Err(Error::ArgumentListTooLong))?,
276                required_sensor_settings.as_mut_ptr(),
277                &mut n_required_sensor_settings,
278            )
279            .into_result()?
280        }
281        for changed in requests.iter() {
282            match changed.sample_rate {
283                SampleRate::Disabled => {
284                    self.subscribed &= !(changed.sensor as u32);
285                    self.ulp_plus_queue &= !(changed.sensor as u32);
286                }
287                SampleRate::UlpMeasurementOnDemand => {
288                    self.ulp_plus_queue |= changed.sensor as u32;
289                }
290                _ => {
291                    self.subscribed |= changed.sensor as u32;
292                }
293            }
294        }
295        Ok(required_sensor_settings
296            .iter()
297            .take(n_required_sensor_settings as usize)
298            .map(RequiredInput::from)
299            .collect())
300    }
301
302    /// Returns the timestamp when the next measurement has to be triggered.
303    pub fn next_measurement(&self) -> i64 {
304        self.next_measurement
305    }
306
307    /// Trigger the next measurement.
308    ///
309    /// Returns the duration until the measurement becomes available. Call
310    /// [`Self::process_last_measurement`] after the duration has passed.
311    pub fn start_next_measurement(&mut self) -> nb::Result<Duration, Error<S::Error>> {
312        let mut bme_settings = bsec_bme_settings_t {
313            next_call: 0,
314            process_data: 0,
315            heater_temperature: 0,
316            heating_duration: 0,
317            run_gas: 0,
318            pressure_oversampling: 0,
319            temperature_oversampling: 0,
320            humidity_oversampling: 0,
321            trigger_measurement: 0,
322        };
323        unsafe {
324            bsec_sensor_control(self.clock.borrow().timestamp_ns(), &mut bme_settings)
325                .into_result()
326                .map_err(Error::BsecError)?;
327        }
328        self.next_measurement = bme_settings.next_call;
329        if bme_settings.trigger_measurement != 1 {
330            return Err(nb::Error::WouldBlock);
331        }
332        self.bme
333            .start_measurement(&BmeSettingsHandle::new(&bme_settings))
334            .map_err(Error::BmeSensorError)
335            .map_err(nb::Error::Other)
336    }
337
338    /// Process the last triggered measurement.
339    ///
340    /// Call this method after the duration returned from a call to
341    /// [`Self::start_next_measurement`] has passed.
342    ///
343    /// Returns a vector of virtual sensor outputs calculated by the
344    /// *Bosch BSEC* library.
345    pub fn process_last_measurement(&mut self) -> nb::Result<Vec<Output>, Error<S::Error>> {
346        let time_stamp = self.clock.borrow().timestamp_ns();
347        let inputs: Vec<bsec_input_t> = self
348            .bme
349            .get_measurement()
350            .map_err(|e| e.map(Error::BmeSensorError))?
351            .iter()
352            .map(|o| bsec_input_t {
353                time_stamp,
354                signal: o.signal,
355                signal_dimensions: 1,
356                sensor_id: o.sensor.into(),
357            })
358            .collect();
359        let mut outputs = vec![
360            bsec_output_t {
361                time_stamp: 0,
362                signal: 0.,
363                signal_dimensions: 1,
364                sensor_id: 0,
365                accuracy: 0,
366            };
367            (self.subscribed | self.ulp_plus_queue).count_ones() as usize
368        ];
369        let mut num_outputs: u8 = outputs
370            .len()
371            .try_into()
372            .or(Err(Error::ArgumentListTooLong))?;
373        self.ulp_plus_queue = 0;
374        unsafe {
375            bsec_do_steps(
376                inputs.as_ptr(),
377                inputs
378                    .len()
379                    .try_into()
380                    .or(Err(Error::ArgumentListTooLong))?,
381                outputs.as_mut_ptr(),
382                &mut num_outputs,
383            )
384            .into_result()
385            .map_err(Error::BsecError)?;
386        }
387
388        let signals: Result<Vec<Output>, Error<S::Error>> = outputs
389            .iter()
390            .take(num_outputs.into())
391            .map(|x| Output::try_from(x).map_err(Error::<S::Error>::from))
392            .collect();
393        Ok(signals?)
394    }
395
396    /// Get the current raw *Bosch BSEC* state, e.g. to persist it before
397    /// shutdown.
398    pub fn get_state(&self) -> Result<Vec<u8>, Error<S::Error>> {
399        let mut state = [0u8; BSEC_MAX_STATE_BLOB_SIZE as usize];
400        let mut work_buffer = [0u8; BSEC_MAX_WORKBUFFER_SIZE as usize];
401        let mut state_length = BSEC_MAX_STATE_BLOB_SIZE;
402        unsafe {
403            bsec_get_state(
404                0,
405                state.as_mut_ptr(),
406                state.len() as u32,
407                work_buffer.as_mut_ptr(),
408                work_buffer.len() as u32,
409                &mut state_length,
410            )
411            .into_result()?;
412        }
413        Ok(state[..state_length as usize].into())
414    }
415
416    /// Set the raw *Bosch BSEC* state, e.g. to restore persisted state after
417    /// shutdown.
418    pub fn set_state(&mut self, state: &[u8]) -> Result<(), Error<S::Error>> {
419        let mut work_buffer = [0u8; BSEC_MAX_WORKBUFFER_SIZE as usize];
420        unsafe {
421            bsec_set_state(
422                state.as_ptr(),
423                state.len() as u32,
424                work_buffer.as_mut_ptr(),
425                work_buffer.len() as u32,
426            )
427            .into_result()?;
428        }
429        Ok(())
430    }
431
432    /// Get the current (raw) *Bosch BSEC* configuration.
433    pub fn get_configuration(&self) -> Result<Vec<u8>, Error<S::Error>> {
434        let mut serialized_settings = [0u8; BSEC_MAX_PROPERTY_BLOB_SIZE as usize];
435        let mut serialized_settings_length = 0u32;
436        let mut work_buffer = [0u8; BSEC_MAX_WORKBUFFER_SIZE as usize];
437        unsafe {
438            bsec_get_configuration(
439                0,
440                serialized_settings.as_mut_ptr(),
441                serialized_settings.len() as u32,
442                work_buffer.as_mut_ptr(),
443                work_buffer.len() as u32,
444                &mut serialized_settings_length,
445            )
446            .into_result()?;
447        }
448        Ok(serialized_settings[..serialized_settings_length as usize].into())
449    }
450
451    /// Set the (raw) *Bosch BSEC* configuration.
452    ///
453    /// Your copy of the *Bosch BSEC* library should contain several different
454    /// configuration files. See the Bosch BSEC documentation for more
455    /// information.
456    pub fn set_configuration(&mut self, serialized_settings: &[u8]) -> Result<(), Error<S::Error>> {
457        let mut work_buffer = [0u8; BSEC_MAX_WORKBUFFER_SIZE as usize];
458        unsafe {
459            bsec_set_configuration(
460                serialized_settings.as_ptr(),
461                serialized_settings.len() as u32,
462                work_buffer.as_mut_ptr(),
463                work_buffer.len() as u32,
464            )
465            .into_result()?
466        }
467        Ok(())
468    }
469
470    /// See documentation of `bsec_reset_output` in the *Bosch BSEC*
471    /// documentation.
472    pub fn reset_output(&mut self, sensor: OutputKind) -> Result<(), Error<S::Error>> {
473        unsafe {
474            bsec_reset_output(bsec_virtual_sensor_t::from(sensor) as u8).into_result()?;
475        }
476        Ok(())
477    }
478}
479
480impl<S: BmeSensor, C: Clock, B: Borrow<C>> Drop for Bsec<S, C, B> {
481    fn drop(&mut self) {
482        BSEC_IN_USE.store(false, Ordering::Release);
483    }
484}
485
486/// Return the *Bosch BSEC* version.
487///
488/// The returned tuple consists of *major*, *minor*, *major bugfix*, and
489/// *minor bugfix* version.
490pub fn get_version() -> Result<(u8, u8, u8, u8), BsecError> {
491    let mut version = bsec_version_t {
492        major: 0,
493        minor: 0,
494        major_bugfix: 0,
495        minor_bugfix: 0,
496    };
497    unsafe {
498        bsec_get_version(&mut version).into_result()?;
499    }
500    Ok((
501        version.major,
502        version.minor,
503        version.major_bugfix,
504        version.minor_bugfix,
505    ))
506}
507
508/// Encapsulates data read from a BME physical sensor.
509#[derive(Clone, Copy, Debug, PartialEq)]
510pub struct Input {
511    /// The sensor value read.
512    pub signal: f32,
513
514    /// The sensor read.
515    pub sensor: InputKind,
516}
517
518/// Single virtual sensor output of the BSEC algorithm.
519#[derive(Clone, Copy, Debug, PartialEq)]
520pub struct Output {
521    /// Timestamp (nanoseconds) of the measurement.
522    ///
523    /// This timestamp is based on the [`Clock`] instance used by [`Bsec`].
524    pub timestamp_ns: i64,
525
526    /// Signal value of the virtual sensor.
527    pub signal: f64,
528
529    /// Type of virtual sensor.
530    pub sensor: OutputKind,
531
532    /// Accuracy of the virtual sensor.
533    pub accuracy: Accuracy,
534}
535
536impl TryFrom<&bsec_output_t> for Output {
537    type Error = ConversionError;
538    fn try_from(output: &bsec_output_t) -> Result<Self, ConversionError> {
539        Ok(Self {
540            timestamp_ns: output.time_stamp,
541            signal: output.signal.into(),
542            sensor: output.sensor_id.try_into()?,
543            accuracy: output.accuracy.try_into()?,
544        })
545    }
546}
547
548/// Sensor accuracy level.
549#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
550pub enum Accuracy {
551    Unreliable = 0,
552    LowAccuracy = 1,
553    MediumAccuracy = 2,
554    HighAccuracy = 3,
555}
556
557impl TryFrom<u8> for Accuracy {
558    type Error = ConversionError;
559    fn try_from(accuracy: u8) -> Result<Self, ConversionError> {
560        use Accuracy::*;
561        match accuracy {
562            0 => Ok(Unreliable),
563            1 => Ok(LowAccuracy),
564            2 => Ok(MediumAccuracy),
565            3 => Ok(HighAccuracy),
566            _ => Err(ConversionError::InvalidAccuracy(accuracy)),
567        }
568    }
569}
570
571/// Describes a virtual sensor output to request from the *Bosch BSEC* library.
572#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
573pub struct SubscriptionRequest {
574    /// Desired sample rate of the virtual sensor output.
575    pub sample_rate: SampleRate,
576    /// Desired virtual output to sample.
577    pub sensor: OutputKind,
578}
579
580impl From<&SubscriptionRequest> for bsec_sensor_configuration_t {
581    fn from(sensor_configuration: &SubscriptionRequest) -> Self {
582        Self {
583            sample_rate: sensor_configuration.sample_rate.into(),
584            sensor_id: bsec_virtual_sensor_t::from(sensor_configuration.sensor) as u8,
585        }
586    }
587}
588
589/// Describes a physical BME sensor that needs to be sampled.
590#[derive(Clone, Copy, Debug, PartialEq)]
591pub struct RequiredInput {
592    /// Sample rate
593    pub sample_rate: f32,
594    /// Sensor that needs to be sampled
595    pub sensor: InputKind,
596}
597
598impl From<&bsec_sensor_configuration_t> for RequiredInput {
599    fn from(sensor_configuration: &bsec_sensor_configuration_t) -> Self {
600        Self {
601            sample_rate: sensor_configuration.sample_rate,
602            sensor: InputKind::from(sensor_configuration.sensor_id),
603        }
604    }
605}
606
607/// Valid sampling rates for the BSEC algorithm.
608#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
609pub enum SampleRate {
610    /// Disabled, not being sampled.
611    Disabled,
612    /// Ultra-low power, see *Bosch BSEC* documentation.
613    Ulp,
614    /// Continuous mode for testing, see *Bosch BSEC* documentation.
615    Continuous,
616    /// Low power, see *Bosch BSEC* documentation.
617    Lp,
618    /// Perform a single measurement on demand between sampling intervals.
619    ///
620    /// See *Bosch BSEC* documentation.
621    UlpMeasurementOnDemand,
622}
623
624impl From<SampleRate> for f32 {
625    fn from(sample_rate: SampleRate) -> Self {
626        f64::from(sample_rate) as f32
627    }
628}
629
630impl From<SampleRate> for f64 {
631    fn from(sample_rate: SampleRate) -> Self {
632        use libalgobsec_sys::*;
633        use SampleRate::*;
634        match sample_rate {
635            Disabled => BSEC_SAMPLE_RATE_DISABLED,
636            Ulp => BSEC_SAMPLE_RATE_ULP,
637            Continuous => BSEC_SAMPLE_RATE_CONT,
638            Lp => BSEC_SAMPLE_RATE_LP,
639            UlpMeasurementOnDemand => BSEC_SAMPLE_RATE_ULP_MEASUREMENT_ON_DEMAND,
640        }
641    }
642}
643
644/// Identifies a physical BME sensor.
645#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
646pub enum InputKind {
647    /// Pressure sensor.
648    Pressure,
649    /// Humidity sensor.
650    Humidity,
651    /// Temperature sensor.
652    Temperature,
653    /// Gas resistance sensor.
654    GasResistor,
655    /// Compensation for nearby heat sources.
656    HeatSource,
657    /// Pseudo-sensor to disable the baseline tracker.
658    DisableBaselineTracker,
659    /// Other sensor only known by magic number.
660    Other(u32),
661}
662
663impl From<u8> for InputKind {
664    fn from(physical_sensor: u8) -> Self {
665        Self::from(physical_sensor as u32)
666    }
667}
668
669impl From<u32> for InputKind {
670    fn from(physical_sensor: u32) -> Self {
671        #![allow(non_upper_case_globals)]
672        use libalgobsec_sys::*;
673        use InputKind::*;
674        match physical_sensor {
675            bsec_physical_sensor_t_BSEC_INPUT_PRESSURE => Pressure,
676            bsec_physical_sensor_t_BSEC_INPUT_HUMIDITY => Humidity,
677            bsec_physical_sensor_t_BSEC_INPUT_TEMPERATURE => Temperature,
678            bsec_physical_sensor_t_BSEC_INPUT_GASRESISTOR => GasResistor,
679            bsec_physical_sensor_t_BSEC_INPUT_HEATSOURCE => HeatSource,
680            bsec_physical_sensor_t_BSEC_INPUT_DISABLE_BASELINE_TRACKER => DisableBaselineTracker,
681            physical_sensor => Other(physical_sensor),
682        }
683    }
684}
685
686impl From<InputKind> for bsec_physical_sensor_t {
687    fn from(physical_sensor: InputKind) -> Self {
688        use libalgobsec_sys::*;
689        use InputKind::*;
690        match physical_sensor {
691            Pressure => bsec_physical_sensor_t_BSEC_INPUT_PRESSURE,
692            Humidity => bsec_physical_sensor_t_BSEC_INPUT_HUMIDITY,
693            Temperature => bsec_physical_sensor_t_BSEC_INPUT_TEMPERATURE,
694            GasResistor => bsec_physical_sensor_t_BSEC_INPUT_GASRESISTOR,
695            HeatSource => bsec_physical_sensor_t_BSEC_INPUT_HEATSOURCE,
696            DisableBaselineTracker => bsec_physical_sensor_t_BSEC_INPUT_DISABLE_BASELINE_TRACKER,
697            Other(sensor) => sensor,
698        }
699    }
700}
701
702impl From<InputKind> for u8 {
703    fn from(physical_sensor: InputKind) -> Self {
704        bsec_physical_sensor_t::from(physical_sensor) as Self
705    }
706}
707
708/// *Bosch BSEC* virtual sensor output.
709///
710/// See *Bosch BSEC* documentation.
711#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
712pub enum OutputKind {
713    Iaq = 0x0001,
714    StaticIaq = 0x0002,
715    Co2Equivalent = 0x0004,
716    BreathVocEquivalent = 0x0008,
717    RawTemperature = 0x0010,
718    RawPressure = 0x0020,
719    RawHumidity = 0x0040,
720    RawGas = 0x0080,
721    StabilizationStatus = 0x0100,
722    RunInStatus = 0x0200,
723    SensorHeatCompensatedTemperature = 0x0400,
724    SensorHeatCompensatedHumidity = 0x0800,
725    GasPercentage = 0x2000,
726}
727
728impl From<OutputKind> for bsec_virtual_sensor_t {
729    fn from(virtual_sensor: OutputKind) -> Self {
730        use libalgobsec_sys::*;
731        use OutputKind::*;
732        match virtual_sensor {
733            Iaq => bsec_virtual_sensor_t_BSEC_OUTPUT_IAQ,
734            StaticIaq => bsec_virtual_sensor_t_BSEC_OUTPUT_STATIC_IAQ,
735            Co2Equivalent => bsec_virtual_sensor_t_BSEC_OUTPUT_CO2_EQUIVALENT,
736            BreathVocEquivalent => bsec_virtual_sensor_t_BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
737            RawTemperature => bsec_virtual_sensor_t_BSEC_OUTPUT_RAW_TEMPERATURE,
738            RawPressure => bsec_virtual_sensor_t_BSEC_OUTPUT_RAW_PRESSURE,
739            RawHumidity => bsec_virtual_sensor_t_BSEC_OUTPUT_RAW_HUMIDITY,
740            RawGas => bsec_virtual_sensor_t_BSEC_OUTPUT_RAW_GAS,
741            StabilizationStatus => bsec_virtual_sensor_t_BSEC_OUTPUT_STABILIZATION_STATUS,
742            RunInStatus => bsec_virtual_sensor_t_BSEC_OUTPUT_RUN_IN_STATUS,
743            SensorHeatCompensatedTemperature => {
744                bsec_virtual_sensor_t_BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE
745            }
746            SensorHeatCompensatedHumidity => {
747                bsec_virtual_sensor_t_BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY
748            }
749            GasPercentage => bsec_virtual_sensor_t_BSEC_OUTPUT_GAS_PERCENTAGE,
750        }
751    }
752}
753
754impl TryFrom<bsec_virtual_sensor_t> for OutputKind {
755    type Error = ConversionError;
756    fn try_from(virtual_sensor: bsec_virtual_sensor_t) -> Result<Self, ConversionError> {
757        #![allow(non_upper_case_globals)]
758        use libalgobsec_sys::*;
759        use OutputKind::*;
760        match virtual_sensor {
761            bsec_virtual_sensor_t_BSEC_OUTPUT_IAQ => Ok(Iaq),
762            bsec_virtual_sensor_t_BSEC_OUTPUT_STATIC_IAQ => Ok(StaticIaq),
763            bsec_virtual_sensor_t_BSEC_OUTPUT_CO2_EQUIVALENT => Ok(Co2Equivalent),
764            bsec_virtual_sensor_t_BSEC_OUTPUT_BREATH_VOC_EQUIVALENT => Ok(BreathVocEquivalent),
765            bsec_virtual_sensor_t_BSEC_OUTPUT_RAW_TEMPERATURE => Ok(RawTemperature),
766            bsec_virtual_sensor_t_BSEC_OUTPUT_RAW_PRESSURE => Ok(RawPressure),
767            bsec_virtual_sensor_t_BSEC_OUTPUT_RAW_HUMIDITY => Ok(RawHumidity),
768            bsec_virtual_sensor_t_BSEC_OUTPUT_RAW_GAS => Ok(RawGas),
769            bsec_virtual_sensor_t_BSEC_OUTPUT_STABILIZATION_STATUS => Ok(StabilizationStatus),
770            bsec_virtual_sensor_t_BSEC_OUTPUT_RUN_IN_STATUS => Ok(RunInStatus),
771            bsec_virtual_sensor_t_BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE => {
772                Ok(SensorHeatCompensatedTemperature)
773            }
774            bsec_virtual_sensor_t_BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY => {
775                Ok(SensorHeatCompensatedHumidity)
776            }
777            bsec_virtual_sensor_t_BSEC_OUTPUT_GAS_PERCENTAGE => Ok(GasPercentage),
778            _ => Err(ConversionError::InvalidVirtualSensorId(virtual_sensor)),
779        }
780    }
781}
782
783impl TryFrom<u8> for OutputKind {
784    type Error = ConversionError;
785    fn try_from(virtual_sensor: u8) -> Result<Self, ConversionError> {
786        Self::try_from(virtual_sensor as bsec_virtual_sensor_t)
787    }
788}
789
790trait IntoResult {
791    fn into_result(self) -> Result<(), BsecError>;
792}
793
794impl IntoResult for bsec_library_return_t {
795    fn into_result(self) -> Result<(), BsecError> {
796        #![allow(non_upper_case_globals)]
797        match self {
798            libalgobsec_sys::bsec_library_return_t_BSEC_OK => Ok(()),
799            error_code => Err(BsecError::from(error_code)),
800        }
801    }
802}
803
804#[cfg(test)]
805pub mod tests {
806    use super::*;
807    use crate::bme::test_support::FakeBmeSensor;
808    use crate::clock::test_support::FakeClock;
809    use serial_test::serial;
810    use std::collections::HashMap;
811
812    #[test]
813    #[serial]
814    fn cannot_create_mulitple_bsec_at_the_same_time() {
815        let clock = FakeClock::default();
816        let first: Bsec<_, FakeClock, _> = Bsec::init(FakeBmeSensor::default(), &clock).unwrap();
817        assert!(Bsec::<_, FakeClock, _>::init(FakeBmeSensor::default(), &clock).is_err());
818        drop(first);
819        let _another = Bsec::<_, FakeClock, _>::init(FakeBmeSensor::default(), &clock).unwrap();
820    }
821
822    #[test]
823    #[serial]
824    fn basic_bsec_operation_smoke_test() {
825        let clock = FakeClock::default();
826        let sensor = FakeBmeSensor::new(Ok(vec![
827            Input {
828                sensor: InputKind::Temperature,
829                signal: 22.,
830            },
831            Input {
832                sensor: InputKind::Humidity,
833                signal: 40.,
834            },
835            Input {
836                sensor: InputKind::Pressure,
837                signal: 1000.,
838            },
839            Input {
840                sensor: InputKind::GasResistor,
841                signal: 6000.,
842            },
843        ]));
844        let mut bsec: Bsec<_, FakeClock, _> = Bsec::init(sensor, &clock).unwrap();
845        bsec.update_subscription(&[
846            SubscriptionRequest {
847                sample_rate: SampleRate::Lp,
848                sensor: OutputKind::RawTemperature,
849            },
850            SubscriptionRequest {
851                sample_rate: SampleRate::Lp,
852                sensor: OutputKind::RawHumidity,
853            },
854            SubscriptionRequest {
855                sample_rate: SampleRate::Lp,
856                sensor: OutputKind::RawPressure,
857            },
858            SubscriptionRequest {
859                sample_rate: SampleRate::Lp,
860                sensor: OutputKind::RawGas,
861            },
862        ])
863        .unwrap();
864
865        clock.advance_by(bsec.start_next_measurement().unwrap());
866        let outputs = bsec.process_last_measurement().unwrap();
867        assert!(bsec.next_measurement() >= 3_000_000_000);
868
869        let signals: HashMap<OutputKind, &Output> = outputs.iter().map(|s| (s.sensor, s)).collect();
870        assert!(
871            (signals.get(&OutputKind::RawTemperature).unwrap().signal - 22.).abs() < f64::EPSILON
872        );
873        assert!((signals.get(&OutputKind::RawHumidity).unwrap().signal - 40.).abs() < f64::EPSILON);
874        assert!(
875            (signals.get(&OutputKind::RawPressure).unwrap().signal - 1000.).abs() < f64::EPSILON
876        );
877        assert!((signals.get(&OutputKind::RawGas).unwrap().signal - 6000.).abs() < f64::EPSILON);
878    }
879
880    #[test]
881    #[serial]
882    fn roundtrip_state_smoke_test() {
883        let clock = FakeClock::default();
884        let sensor = FakeBmeSensor::default();
885        let mut bsec: Bsec<_, FakeClock, _> = Bsec::init(sensor, &clock).unwrap();
886        let state = bsec.get_state().unwrap();
887        bsec.set_state(&state).unwrap();
888    }
889
890    #[test]
891    #[serial]
892    fn configuration_roundtrip_smoke_test() {
893        let clock = FakeClock::default();
894        let sensor = FakeBmeSensor::default();
895        let mut bsec: Bsec<_, FakeClock, _> = Bsec::init(sensor, &clock).unwrap();
896        let config = bsec.get_configuration().unwrap();
897        bsec.set_configuration(&config).unwrap();
898    }
899
900    #[test]
901    fn get_version_smoke_test() {
902        let version = get_version().unwrap();
903        assert!(version.0 == 1);
904        assert!(version.1 >= 4);
905        assert!(version.1 > 4 || version.2 >= 8);
906    }
907
908    #[test]
909    #[serial]
910    fn reset_output_smoke_test() {
911        let clock = FakeClock::default();
912        let sensor = FakeBmeSensor::default();
913        let mut bsec: Bsec<_, FakeClock, _> = Bsec::init(sensor, &clock).unwrap();
914        bsec.reset_output(OutputKind::Iaq).unwrap();
915    }
916}