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}