Skip to content

Commit

Permalink
feat(eclss): add SHT41 sensor support
Browse files Browse the repository at this point in the history
  • Loading branch information
hawkw committed Jun 10, 2024
1 parent c21f70c commit 387a008
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 1 deletion.
59 changes: 59 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ eclss-axum = { path = "lib/eclss-axum" }
ens160 = { version = "0.6.1", default-features = false }
embedded-hal = { version = "1" }
embedded-hal-async = { version = "1" }
fixed = "1.20.0"
futures = "0.3"
heapless = "0.8"
humantime = "2"
Expand All @@ -36,6 +37,7 @@ pmsa003i = { path = "lib/pmsa003i" }
reqwest = { version = "0.12.4", default-features = false }
scd4x = { version = "0.3.0", default-features = false }
sgp30 = { version = "0.4.0", default-features = false }
sht4x = { version = "0.2.0", default-features = false }
serde = { version = "1.0", default-features = false }
spin_sleep = { version = "1.2.0" }

Expand All @@ -44,4 +46,5 @@ bosch-bme680 = { git = "https://github.com/hawkw/bosch-bme680", branch = "eliza/
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" }
sht4x = { git = "https://github.com/hawkw/sht4x", branch = "eliza/embedded-hal-async" }
sensirion-i2c = { git = "https://github.com/sensirion/sensirion-i2c-rs", rev = "f7b9f3a81b777bc6e6b2f0acb4c1ef9c57dfa06d" }
3 changes: 2 additions & 1 deletion eclssd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ readme = "README.md"
license = "MIT"

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

#[cfg(feature = "sht41")]
sensors.spawn({
let sensor = sensor::Sht41::new(eclss, GoodDelay::default());

let backoff = backoff.clone();
async move {
tracing::info!("starting SHT41...");
eclss
.run_sensor(sensor, backoff.clone(), linux_embedded_hal::Delay)
.await
.unwrap()
}
});

#[cfg(feature = "ens160")]
sensors.spawn({
let sensor = sensor::Ens160::new(eclss, GoodDelay::default());
Expand Down
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"linux-embedded-hal-0.4.0" = "sha256-2CxZcBMWaGP0DTiyQoDkwVFoNbEBojGySirSb2z+40U=";
"scd4x-0.3.0" = "sha256-2pbYEDX2454lP2701eyhtRWu1sSW8RSStVt6iOF0fmI=";
"sgp30-0.4.0" = "sha256-5LRVMFMTxpgQpxrN0sMTiedL4Sw6NBRd86+ZvRP8CVk=";
"sht4x-0.2.0" = "sha256-LrOvkvNXFNL4EM9aAZfSDFM7zs6M54BGeixtDN5pFCo=";
"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
3 changes: 3 additions & 0 deletions lib/eclss/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ serde = ["dep:serde", "tinymetrics/serde"]
scd40 = ["dep:scd4x"]
scd41 = ["dep:scd4x", "scd4x/scd41"]
sgp30 = ["dep:sgp30"]
sht41 = ["dep:sht4x", "dep:fixed"]
default = ["pmsa003i", "scd40", "ens160", "sgp30", "bme680"]

[dependencies]
Expand All @@ -19,10 +20,12 @@ eclss-api = { workspace = true, features = ["fmt"] }
ens160 = { workspace = true, optional = true, features = ["async"] }
embedded-hal-async = { workspace = true }
embedded-hal = { workspace = true }
fixed = { workspace = true, optional = true }
maitake-sync = { workspace = true }
tinymetrics = { workspace = true, default-features = false }
scd4x = { workspace = true, optional = true, default-features = false, features = ["embedded-hal-async"] }
sgp30 = { workspace = true, optional = true, default-features = false, features = ["embedded-hal-async"] }
sht4x = { workspace = true, optional = true, default-features = false, features = ["embedded-hal-async"] }
serde = { workspace = true, optional = true }
tracing = { workspace = true, optional = true, default-features = false, features = ["attributes"] }
pmsa003i = { workspace = true, optional = true, features = ["embedded-hal-async"] }
5 changes: 5 additions & 0 deletions lib/eclss/src/sensor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ pub mod sgp30;
#[cfg(feature = "sgp30")]
pub use sgp30::Sgp30;

#[cfg(feature = "sht41")]
pub mod sht41;
#[cfg(feature = "sht41")]
pub use sht41::Sht41;

#[cfg(feature = "ens160")]
pub mod ens160;
#[cfg(feature = "ens160")]
Expand Down
137 changes: 137 additions & 0 deletions lib/eclss/src/sensor/sht41.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use crate::{
error::{Context, EclssError, SensorError},
metrics::{Gauge, SensorLabel},
sensor::Sensor,
SharedBus,
};
use core::{fmt, num::Wrapping, time::Duration};
use embedded_hal_async::{
delay::DelayNs,
i2c::{self, I2c},
};
use sht4x::AsyncSht4x;
pub use sht4x::Precision;

#[must_use = "sensors do nothing unless polled"]
pub struct Sht41<I: 'static, D> {
sensor: AsyncSht4x<&'static SharedBus<I>, D>,
temp: &'static Gauge,
rel_humidity: &'static Gauge,
abs_humidity: &'static Gauge,
abs_humidity_interval: usize,
precision: Precision,
polls: Wrapping<usize>,
delay: D,
}

pub struct Sht4xError<E>(sht4x::Error<E>);

const NAME: &str = "SHT41";

impl<I, D> Sht41<I, D>
where
I: I2c + 'static,
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);
// This is the default I2C address of the Adafruit breakout board.
// TODO(eliza): make this configurable
let address = sht4x::Address::Address0x44;

Self {
sensor: AsyncSht4x::new_with_address(&eclss.i2c, address),
temp: metrics.temp.register(LABEL).unwrap(),
rel_humidity: metrics.rel_humidity.register(LABEL).unwrap(),
abs_humidity: metrics.abs_humidity.register(LABEL).unwrap(),
polls: Wrapping(0),
abs_humidity_interval: 1,
precision: Precision::Medium,
delay,
}
}

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

pub fn with_precision(self, precision: Precision) -> Self {
Self { precision, ..self }
}
}

impl<I, D> Sensor for Sht41<I, D>
where
I: I2c + 'static,
D: DelayNs,
{
const NAME: &'static str = NAME;
const POLL_INTERVAL: Duration = Duration::from_secs(1);
type Error = EclssError<Sht4xError<I::Error>>;

async fn init(&mut self) -> Result<(), Self::Error> {
let serial = self
.sensor
.serial_number(&mut self.delay)
.await
.context("error reading SHT41 serial number")?;
tracing::info!("Connected to {NAME}, serial number: {serial:#x}");
Ok(())
}

async fn poll(&mut self) -> Result<(), Self::Error> {
let reading = self
.sensor
.measure(self.precision, &mut self.delay)
.await
.context("error reading SHT41 measurement")?;

let temp = reading.temperature_celsius().to_num::<f64>();
let rel_humidity = reading.humidity_percent().to_num::<f64>();
self.temp.set_value(temp);
self.rel_humidity.set_value(rel_humidity);
tracing::debug!("{NAME}: Temp: {temp}°C, Humidity: {rel_humidity}%");

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

self.polls += 1;

Ok(())
}
}

impl<E> From<sht4x::Error<E>> for Sht4xError<E> {
fn from(value: sht4x::Error<E>) -> Self {
Self(value)
}
}

impl<E: i2c::Error> SensorError for Sht4xError<E> {
fn i2c_error(&self) -> Option<i2c::ErrorKind> {
match self {
Self(sht4x::Error::I2c(i)) => Some(i.kind()),
_ => None,
}
}
}

impl<E: fmt::Display> fmt::Display for Sht4xError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self(sht4x::Error::I2c(i)) => fmt::Display::fmt(i, f),
Self(sht4x::Error::Crc) => write!(f, "{NAME} CRC checksum validation failed"),
Self(_) => write!(f, "unknown {NAME} error"),
}
}
}

0 comments on commit 387a008

Please sign in to comment.