Skip to content

Commit

Permalink
feat(eclss): add Bosch BME680 sensor support
Browse files Browse the repository at this point in the history
  • Loading branch information
hawkw committed Jun 9, 2024
1 parent 41e5d27 commit 5591e5e
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 2 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ resolver = "2"
[workspace.dependencies]
anyhow = "1.0"
axum = "0.7.5"
bosch-bme680 = "1.0.2"
clap = "4.0"
eclss = { path = "lib/eclss" }
eclss-api = { path = "lib/eclss-api" }
Expand Down Expand Up @@ -38,6 +39,7 @@ sgp30 = { version = "0.4.0", default-features = false }
serde = { version = "1.0", default-features = false }

[patch.crates-io]
bosch-bme680 = { git = "https://github.com/hawkw/bosch-bme680", branch = "eliza/async" }
linux-embedded-hal = { git = "https://github.com/rust-embedded/linux-embedded-hal/" }
scd4x = { git = "https://github.com/hawkw/scd4x-rs", branch = "eliza/async" }
sgp30 = { git = "https://github.com/hawkw/sgp30-rs", branch = "eliza/embedded-hal-async" }
Expand Down
3 changes: 2 additions & 1 deletion eclssd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ readme = "README.md"
license = "MIT"

[features]
default = ["scd41", "sgp30", "pmsa003i", "ens160", "mdns"]
default = ["bme680", "scd41", "sgp30", "pmsa003i", "ens160", "mdns"]
bme680 = ["eclss/bme680"]
scd41 = ["eclss/scd41"]
scd40 = ["eclss/scd40"]
sgp30 = ["eclss/sgp30"]
Expand Down
13 changes: 13 additions & 0 deletions eclssd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,19 @@ async fn main() -> anyhow::Result<()> {
}
});

#[cfg(feature = "bme680")]
sensors.spawn({
let sensor = sensor::Bme680::new(eclss, linux_embedded_hal::Delay);
let backoff = backoff.clone();
async move {
tracing::info!("starting BME680...");
eclss
.run_sensor(sensor, backoff, linux_embedded_hal::Delay)
.await
.unwrap()
}
});

while let Some(join) = sensors.join_next().await {
join.unwrap();
}
Expand Down
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"sgp30-0.4.0" = "sha256-5LRVMFMTxpgQpxrN0sMTiedL4Sw6NBRd86+ZvRP8CVk=";
"sensirion-i2c-0.3.0" = "sha256-HS6anAmUBBrhlP/kBSv243ArnK3ULK8+0JA8kpe6LAk=";
"tinymetrics-0.1.0" = "sha256-7y2pq8qBtOpXO6ii/j+NhwsglxRMLvz8hx7a/w6GRBU=";
"bosch-bme680-1.0.2" = "sha256-C4l3MLqI9HYt2t459iccyJHjcVnFsXEywM3D+kcC95o=";
};
};
});
Expand Down
4 changes: 3 additions & 1 deletion lib/eclss/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[features]
bme680 = ["dep:bosch-bme680"]
serde = ["dep:serde", "tinymetrics/serde"]
scd40 = ["dep:scd4x"]
scd41 = ["dep:scd4x", "scd4x/scd41"]
sgp30 = ["dep:sgp30"]
default = ["pmsa003i", "scd40", "ens160", "sgp30"]
default = ["pmsa003i", "scd40", "ens160", "sgp30", "bme680"]

[dependencies]
bosch-bme680 = { workspace = true, optional = true, features = ["embedded-hal-async"] }
eclss-api = { workspace = true, features = ["fmt"] }
ens160 = { workspace = true, optional = true, features = ["async"] }
embedded-hal-async = { workspace = true }
Expand Down
4 changes: 4 additions & 0 deletions lib/eclss/src/sensor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ use core::time::Duration;
use embedded_hal_async::delay::DelayNs;
mod status;

#[cfg(feature = "bme680")]
pub mod bme680;
pub use bme680::Bme680;

#[cfg(feature = "pmsa003i")]
pub mod pmsa003i;
#[cfg(feature = "pmsa003i")]
Expand Down
159 changes: 159 additions & 0 deletions lib/eclss/src/sensor/bme680.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
use crate::{
error::{Context, EclssError, SensorError},
metrics::{Gauge, SensorLabel},
sensor::Sensor,
SharedBus,
};
use bosch_bme680::{AsyncBme680, BmeError};
use core::fmt;
use core::num::Wrapping;
use embedded_hal_async::{
delay::DelayNs,
i2c::{self, Error as _, I2c},
};
pub struct Bme680<I: 'static, D> {
sensor: AsyncBme680<&'static SharedBus<I>, D>,
temp: &'static Gauge,
rel_humidity: &'static Gauge,
abs_humidity: &'static Gauge,
pressure: &'static Gauge,
gas_resistance: &'static Gauge,
abs_humidity_interval: usize,
polls: Wrapping<usize>,
}

impl<I, D> Bme680<I, D>
where
I: I2c<i2c::SevenBitAddress>,
D: DelayNs,
{
pub fn new<const SENSORS: usize>(
eclss: &'static crate::Eclss<I, { SENSORS }>,
delay: D,
) -> Self {
let metrics = &eclss.metrics;
const LABEL: SensorLabel = SensorLabel(NAME);

// the default I2C address of the Adafruit BME680 breakout board
// is the "secondary" address, 0x77.
let address = bosch_bme680::DeviceAddress::Secondary;
// TODO(eliza): get this from an ambient measurement...
let ambient_temp = 20;
Self {
sensor: AsyncBme680::new(&eclss.i2c, address, delay, ambient_temp),
temp: metrics.temp.register(LABEL).unwrap(),
pressure: metrics.pressure.register(LABEL).unwrap(),
rel_humidity: metrics.rel_humidity.register(LABEL).unwrap(),
abs_humidity: metrics.abs_humidity.register(LABEL).unwrap(),
gas_resistance: metrics.gas_resistance.register(LABEL).unwrap(),
polls: Wrapping(0),
abs_humidity_interval: 1,
}
}

pub fn with_abs_humidity_interval(mut self, interval: usize) -> Self {
self.abs_humidity_interval = interval;
self
}
}

#[derive(Debug)]
pub struct Error<E: embedded_hal::i2c::ErrorType>(BmeError<E>);

const NAME: &str = "BME680";

impl<I, D> Sensor for Bme680<I, D>
where
I: I2c + 'static,
I::Error: core::fmt::Display,
D: DelayNs,
{
const NAME: &'static str = NAME;
const POLL_INTERVAL: core::time::Duration = core::time::Duration::from_secs(2);
type Error = EclssError<Error<&'static SharedBus<I>>>;

async fn init(&mut self) -> Result<(), Self::Error> {
let config = bosch_bme680::Configuration::default();
self.sensor
.initialize(&config)
.await
.context("error initializing BME680")?;
tracing::info!("initialized BME680 with config: {config:?}");
Ok(())
}

async fn poll(&mut self) -> Result<(), Self::Error> {
let bosch_bme680::MeasurmentData {
temperature,
humidity,
pressure,
gas_resistance,
} = self
.sensor
.measure()
.await
.context("error reading BME680 measurements")?;
self.polls += 1;

// pretty sure the `bosch-bme680` library is off by a factor of 100 when
// representing pressures as hectopascals...
let pressure = pressure / 100f32;
self.pressure.set_value(pressure.into());
self.temp.set_value(temperature.into());
self.rel_humidity.set_value(humidity.into());
tracing::debug!("Temp: {temperature}°C, Humidity: {humidity}%, Pressure: {pressure} hPa");

if let Some(gas_resistance) = gas_resistance {
self.gas_resistance.set_value(gas_resistance.into());
tracing::debug!("Gas resistance: {gas_resistance} Ohms");
}

if self.polls.0 % self.abs_humidity_interval == 0 {
let abs_humidity = super::absolute_humidity(temperature, humidity);
self.abs_humidity.set_value(abs_humidity.into());
debug!("Absolute humidity: {abs_humidity} g/m³");
}

Ok(())
}
}

impl<E> SensorError for Error<E>
where
E: embedded_hal::i2c::ErrorType,
{
fn i2c_error(&self) -> Option<i2c::ErrorKind> {
match self.0 {
BmeError::WriteError(ref e) => Some(e.kind()),
BmeError::WriteReadError(ref e) => Some(e.kind()),
_ => None,
}
}
}

impl<E> fmt::Display for Error<E>
where
E: embedded_hal::i2c::ErrorType,
E::Error: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
BmeError::WriteError(ref e) => write!(f, "I2C write error: {e}"),
BmeError::WriteReadError(ref e) => write!(f, "I2C write-read error: {e}"),
BmeError::MeasuringTimeOut => f.write_str("BME680 measurement timed out"),
BmeError::UnexpectedChipId(id) => {
write!(f, "unexpected BME680 chip ID {id:#04x} (expected 0x61)")
}
BmeError::Uninitialized => f.write_str("BME680 sensor hasn't been initialized yet"),
}
}
}

impl<E> From<BmeError<E>> for Error<E>
where
E: embedded_hal::i2c::ErrorType,
{
fn from(e: BmeError<E>) -> Self {
Self(e)
}
}

0 comments on commit 5591e5e

Please sign in to comment.