fs3000_rs/
lib.rs

1//! A platform-agnostic, embedded-hal driver for FS3000 airflow sensors, either directly or via a [Sparkfun breakout board](https://www.sparkfun.com/products/18768).
2#![no_std]
3#![deny(missing_docs)]
4
5mod address;
6pub use address::DeviceAddr;
7mod protocol;
8mod types;
9pub use types::{Async, Blocking, ClientType};
10
11use protocol::Packet;
12pub use types::{DeviceType, FS3000_1005, FS3000_1015};
13
14/// Public module with all helpful types.
15pub mod prelude {
16    pub use crate::types::{FS3000_1005, FS3000_1015};
17    pub use crate::{Async, Blocking, DeviceAddr, FS3000};
18}
19
20/// Any error that can occur when using this library.
21#[derive(Debug, thiserror::Error)]
22#[cfg_attr(feature = "defmt", derive(defmt::Format))]
23pub enum Error<I2CError> {
24    /// A packet was received from the FS3000 but it's checksum was invalid.
25    /// This typically indicates a faulty link or device.
26    #[error("Checksum validation failed")]
27    ChecksumFailed,
28
29    /// Any I2C error that occurs when communicating with the device.
30    #[error("I2C Error: {0:?}")]
31    I2C(I2CError),
32}
33
34/// A client for a FS3000 device via I2C.
35///
36/// # Choosing a Client
37///
38/// When creating this client, you must decide:
39///
40/// - **Device type:**
41///   - Is the connected device a `FS3000-1005` or `FS3000-1015`? The latter can measure larger air velocities.
42/// - **Client type:**
43///   - Is the consuming code blocking or async?
44///
45/// Both of these decisions are documented using marker traits.
46///
47/// # Blocking Example
48///
49/// ```no_run
50/// # #[derive(Default)]
51/// # struct BlockingBus {};
52/// #
53/// # impl embedded_hal::i2c::ErrorType for BlockingBus {
54/// #    type Error = core::convert::Infallible;
55/// # }
56/// #
57/// # impl embedded_hal::i2c::I2c for BlockingBus {
58/// #   fn transaction(&mut self, address: embedded_hal::i2c::SevenBitAddress, operations: &mut [embedded_hal::i2c::Operation<'_>],) -> Result<(), Self::Error> {
59/// #     unimplemented!("hidden example code");
60/// #   }
61/// # }
62/// use fs3000_rs::prelude::*;
63///
64/// // The [`BlockingBus`] is a fake `embedded_hal::i2c::I2c` for this example.
65/// // In practice, you would create this [`embedded_hal::i2c::I2c`] via your platform hal.
66/// let blocking_bus = BlockingBus::default();
67///
68/// // Assumes the FS3000-1015 (wider measurement range), substitute FS3000_1005 if needed.
69/// let mut client = FS3000::<FS3000_1015, Blocking, _>::new(DeviceAddr::default(), blocking_bus);
70///
71/// let mps = client.read_meters_per_second()?;
72/// println!("We're going {mps} meters/second!");
73///
74/// # Ok::<(), fs3000_rs::Error<core::convert::Infallible>>(())
75/// ```
76///
77/// # Async Example
78///
79/// ```no_run
80/// # #[derive(Default)]
81/// # struct AsyncBus {};
82/// #
83/// # impl embedded_hal::i2c::ErrorType for AsyncBus {
84/// #    type Error = core::convert::Infallible;
85/// # }
86/// #
87/// # impl embedded_hal_async::i2c::I2c for AsyncBus {
88/// #   async fn transaction(&mut self, address: embedded_hal::i2c::SevenBitAddress, operations: &mut [embedded_hal::i2c::Operation<'_>],) -> Result<(), Self::Error> {
89/// #     unimplemented!("hidden example code");
90/// #   }
91/// # }
92/// use fs3000_rs::prelude::*;
93///
94/// // The [`BlockingBus`] is a fake `embedded_hal::i2c::I2c` for this example.
95/// // In practice, you would create this [`embedded_hal::i2c::I2c`] via your platform hal.
96/// let async_bus = AsyncBus::default();
97///
98/// # tokio_test::block_on(async {
99/// let mut client = FS3000::<FS3000_1015, Async, _>::new(DeviceAddr::default(), async_bus);
100///
101/// let mps = client.read_meters_per_second().await.unwrap();
102/// println!("We're going {mps} meters/second!");
103/// # });
104/// ```
105pub struct FS3000<Device: DeviceType, Client: ClientType, I2C> {
106    address: DeviceAddr,
107    i2c: I2C,
108    _client: core::marker::PhantomData<Client>,
109    _state: core::marker::PhantomData<Device>,
110}
111
112impl<Device: DeviceType, I2C> FS3000<Device, Blocking, I2C>
113where
114    I2C: embedded_hal::i2c::I2c,
115{
116    /// Create a new FS3000 instance.
117    pub fn new(address: DeviceAddr, i2c: I2C) -> Self {
118        Self {
119            i2c,
120            address,
121            _client: core::marker::PhantomData,
122            _state: core::marker::PhantomData,
123        }
124    }
125
126    /// Fetch a single, meters-per-second airflow measurement from the device.
127    pub fn read_meters_per_second(&mut self) -> Result<f32, Error<I2C::Error>> {
128        let measurement = self.read_raw()?;
129        Ok(protocol::raw_to_meters_per_second::<Device>(measurement))
130    }
131
132    /// Fetch a single, raw measurement from the device.
133    ///
134    /// The measurement must be translated to a real unit for usage, consult
135    /// the datasheet for details. Otherwise, use [`FS3000::read_meters_per_second`] to have
136    /// this conversion be handled for you.
137    pub fn read_raw(&mut self) -> Result<u16, Error<I2C::Error>> {
138        let mut packet = Packet([0; 5]);
139        self.i2c
140            .read(self.address.into(), &mut packet.0)
141            .map_err(Error::<I2C::Error>::I2C)?;
142
143        if !packet.valid() {
144            return Err(Error::ChecksumFailed);
145        }
146
147        Ok(packet.measurement())
148    }
149}
150
151impl<Device: DeviceType, I2C> FS3000<Device, Async, I2C>
152where
153    I2C: embedded_hal_async::i2c::I2c,
154{
155    /// Create a new FS3000 instance.
156    pub fn new(address: DeviceAddr, i2c: I2C) -> Self {
157        Self {
158            i2c,
159            address,
160            _client: core::marker::PhantomData,
161            _state: core::marker::PhantomData,
162        }
163    }
164
165    /// Fetch a single, meters-per-second airflow measurement from the device.
166    pub async fn read_meters_per_second(&mut self) -> Result<f32, Error<I2C::Error>> {
167        let measurement = self.read_raw().await?;
168        Ok(protocol::raw_to_meters_per_second::<Device>(measurement))
169    }
170
171    /// Fetch a single, raw measurement from the device.
172    ///
173    /// The measurement must be translated to a real unit for usage, consult
174    /// the datasheet for details. Otherwise, use [`FS3000::read_meters_per_second`] to have
175    /// this conversion be handled for you.
176    pub async fn read_raw(&mut self) -> Result<u16, Error<I2C::Error>> {
177        let mut packet = Packet([0; 5]);
178        self.i2c
179            .read(self.address.into(), &mut packet.0)
180            .await
181            .map_err(Error::<I2C::Error>::I2C)?;
182
183        if !packet.valid() {
184            return Err(Error::ChecksumFailed);
185        }
186
187        Ok(packet.measurement())
188    }
189}