Repo of no-std crates for my personal embedded projects

SHTC3 crate

Changed files
+1613 -7
sachy-shtc3
+386 -6
Cargo.lock
···
version = 4
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "defmt"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78"
dependencies = [
-
"bitflags",
"defmt-macros",
]
···
]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed"
dependencies = [
-
"defmt",
"hash32",
"stable_deref_trait",
]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
name = "quote"
-
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
···
name = "sachy-bthome"
version = "0.1.0"
dependencies = [
-
"defmt",
"heapless",
"sachy-fmt",
]
···
name = "sachy-fmt"
version = "0.1.0"
dependencies = [
-
"defmt",
]
[[package]]
···
]
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"proc-macro2",
"quote",
"syn",
]
[[package]]
···
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
···
version = 4
[[package]]
+
name = "autocfg"
+
version = "1.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
+
name = "bitflags"
+
version = "2.10.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+
+
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
+
name = "cast"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
+
[[package]]
+
name = "cc"
+
version = "1.2.48"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a"
+
dependencies = [
+
"find-msvc-tools",
+
"shlex",
+
]
+
+
[[package]]
+
name = "cfg-if"
+
version = "1.0.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+
[[package]]
+
name = "core-foundation"
+
version = "0.10.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63"
+
dependencies = [
+
"core-foundation-sys",
+
"libc",
+
]
+
+
[[package]]
+
name = "core-foundation-sys"
+
version = "0.8.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+
[[package]]
+
name = "defmt"
+
version = "0.3.100"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
+
dependencies = [
+
"defmt 1.0.1",
+
]
+
+
[[package]]
name = "defmt"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78"
dependencies = [
+
"bitflags 1.3.2",
"defmt-macros",
]
···
]
[[package]]
+
name = "embedded-hal"
+
version = "1.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
+
dependencies = [
+
"defmt 0.3.100",
+
]
+
+
[[package]]
+
name = "embedded-hal-async"
+
version = "1.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884"
+
dependencies = [
+
"embedded-hal",
+
]
+
+
[[package]]
+
name = "embedded-hal-mock"
+
version = "0.11.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "f9a0f04f8886106faf281c47b6a0e4054a369baedaf63591fdb8da9761f3f379"
+
dependencies = [
+
"embedded-hal",
+
"embedded-hal-nb",
+
]
+
+
[[package]]
+
name = "embedded-hal-nb"
+
version = "1.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605"
+
dependencies = [
+
"embedded-hal",
+
"nb",
+
]
+
+
[[package]]
+
name = "find-msvc-tools"
+
version = "0.1.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
+
+
[[package]]
+
name = "gpio-cdev"
+
version = "0.6.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "09831ec59b80be69e75d29cf36e16afbbe5fd1af9c1bf4689ad91c77db5aa6a6"
+
dependencies = [
+
"bitflags 2.10.0",
+
"libc",
+
"nix 0.27.1",
+
]
+
+
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed"
dependencies = [
+
"defmt 1.0.1",
"hash32",
"stable_deref_trait",
]
[[package]]
+
name = "i2cdev"
+
version = "0.6.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9b940f7497c4f95b863b21cd34c3737b53a67d80d94cf29055d7f7eeca6ffdb4"
+
dependencies = [
+
"bitflags 2.10.0",
+
"byteorder",
+
"libc",
+
"nix 0.26.4",
+
]
+
+
[[package]]
+
name = "io-kit-sys"
+
version = "0.4.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b"
+
dependencies = [
+
"core-foundation-sys",
+
"mach2",
+
]
+
+
[[package]]
+
name = "libc"
+
version = "0.2.178"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
+
+
[[package]]
+
name = "linux-embedded-hal"
+
version = "0.4.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2a8a605c95f708c78554738a12153b213f107d3bd5323f7ce32d6deb3faafb40"
+
dependencies = [
+
"cast",
+
"embedded-hal",
+
"embedded-hal-nb",
+
"gpio-cdev",
+
"i2cdev",
+
"nb",
+
"nix 0.27.1",
+
"serialport",
+
"spidev",
+
"sysfs_gpio",
+
]
+
+
[[package]]
+
name = "mach2"
+
version = "0.4.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
+
dependencies = [
+
"libc",
+
]
+
+
[[package]]
+
name = "memoffset"
+
version = "0.6.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+
dependencies = [
+
"autocfg",
+
]
+
+
[[package]]
+
name = "memoffset"
+
version = "0.7.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+
dependencies = [
+
"autocfg",
+
]
+
+
[[package]]
+
name = "nb"
+
version = "1.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
+
+
[[package]]
+
name = "nix"
+
version = "0.23.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
+
dependencies = [
+
"bitflags 1.3.2",
+
"cc",
+
"cfg-if",
+
"libc",
+
"memoffset 0.6.5",
+
]
+
+
[[package]]
+
name = "nix"
+
version = "0.26.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
+
dependencies = [
+
"bitflags 1.3.2",
+
"cfg-if",
+
"libc",
+
"memoffset 0.7.1",
+
"pin-utils",
+
]
+
+
[[package]]
+
name = "nix"
+
version = "0.27.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
+
dependencies = [
+
"bitflags 2.10.0",
+
"cfg-if",
+
"libc",
+
]
+
+
[[package]]
+
name = "pin-utils"
+
version = "0.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
[[package]]
name = "quote"
+
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
···
name = "sachy-bthome"
version = "0.1.0"
dependencies = [
+
"defmt 1.0.1",
"heapless",
"sachy-fmt",
]
···
name = "sachy-fmt"
version = "0.1.0"
dependencies = [
+
"defmt 1.0.1",
+
]
+
+
[[package]]
+
name = "sachy-shtc3"
+
version = "0.1.0"
+
dependencies = [
+
"defmt 1.0.1",
+
"embedded-hal",
+
"embedded-hal-async",
+
"embedded-hal-mock",
+
"linux-embedded-hal",
+
]
+
+
[[package]]
+
name = "scopeguard"
+
version = "1.2.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+
[[package]]
+
name = "serialport"
+
version = "4.8.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "21f60a586160667241d7702c420fc223939fb3c0bb8d3fac84f78768e8970dee"
+
dependencies = [
+
"bitflags 2.10.0",
+
"cfg-if",
+
"core-foundation",
+
"core-foundation-sys",
+
"io-kit-sys",
+
"mach2",
+
"nix 0.26.4",
+
"quote",
+
"scopeguard",
+
"unescaper",
+
"windows-sys",
+
]
+
+
[[package]]
+
name = "shlex"
+
version = "1.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+
[[package]]
+
name = "spidev"
+
version = "0.6.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "32dadd0a877f0652fa52dbc4d2ed9f4877bea5cd30725507b36e1970a5ef0519"
+
dependencies = [
+
"bitflags 2.10.0",
+
"libc",
+
"nix 0.26.4",
]
[[package]]
···
]
[[package]]
+
name = "sysfs_gpio"
+
version = "0.6.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e8808c55bc926565c62ef7838bcaa8add51585236803e2bdfa1472e3a3ab5e17"
+
dependencies = [
+
"nix 0.23.2",
+
]
+
+
[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
···
"proc-macro2",
"quote",
"syn",
+
]
+
+
[[package]]
+
name = "unescaper"
+
version = "0.1.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26"
+
dependencies = [
+
"thiserror",
]
[[package]]
···
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+
+
[[package]]
+
name = "windows-sys"
+
version = "0.52.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+
dependencies = [
+
"windows-targets",
+
]
+
+
[[package]]
+
name = "windows-targets"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+
dependencies = [
+
"windows_aarch64_gnullvm",
+
"windows_aarch64_msvc",
+
"windows_i686_gnu",
+
"windows_i686_gnullvm",
+
"windows_i686_msvc",
+
"windows_x86_64_gnu",
+
"windows_x86_64_gnullvm",
+
"windows_x86_64_msvc",
+
]
+
+
[[package]]
+
name = "windows_aarch64_gnullvm"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+
[[package]]
+
name = "windows_aarch64_msvc"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+
[[package]]
+
name = "windows_i686_gnu"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+
[[package]]
+
name = "windows_i686_gnullvm"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+
[[package]]
+
name = "windows_i686_msvc"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+
[[package]]
+
name = "windows_x86_64_gnu"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+
[[package]]
+
name = "windows_x86_64_gnullvm"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+
[[package]]
+
name = "windows_x86_64_msvc"
+
version = "0.52.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+1 -1
Cargo.toml
···
[workspace]
resolver = "3"
-
members = ["sachy-battery","sachy-bthome", "sachy-fmt"]
[workspace.package]
authors = ["Sachymetsu <sachymetsu@tutamail.com>"]
···
[workspace]
resolver = "3"
+
members = ["sachy-battery","sachy-bthome", "sachy-fmt", "sachy-shtc3"]
[workspace.package]
authors = ["Sachymetsu <sachymetsu@tutamail.com>"]
+22
sachy-shtc3/Cargo.toml
···
···
+
[package]
+
name = "sachy-shtc3"
+
description = "A SHTC3 driver crate"
+
version = { workspace = true }
+
edition = { workspace = true }
+
authors = { workspace = true }
+
repository = { workspace = true }
+
license = { workspace = true }
+
rust-version = { workspace = true }
+
+
[dependencies]
+
defmt = { version = "1.0.1", optional = true }
+
embedded-hal = { version = "1.0.0" }
+
embedded-hal-async = { version = "1.0.0" }
+
+
[dev-dependencies]
+
embedded-hal-mock = { version = "0.11.1", features = ["eh1"], default-features = false }
+
linux-embedded-hal = "0.4.0"
+
+
[features]
+
defmt = ["dep:defmt", "embedded-hal/defmt-03"]
+
default = []
+39
sachy-shtc3/src/crc.rs
···
···
+
/// Calculate the CRC8 checksum.
+
///
+
/// Implementation based on the reference implementation by Sensirion.
+
#[inline]
+
pub(crate) const fn crc8(data: &[u8]) -> u8 {
+
const CRC8_POLYNOMIAL: u8 = 0x31;
+
let mut crc: u8 = u8::MAX;
+
let mut i = 0;
+
+
while i < data.len() {
+
crc ^= data[i];
+
i += 1;
+
+
let mut c = 0;
+
while c < 8 {
+
c += 1;
+
if (crc & 0x80) > 0 {
+
crc = (crc << 1) ^ CRC8_POLYNOMIAL;
+
} else {
+
crc <<= 1;
+
}
+
}
+
}
+
+
crc
+
}
+
+
#[cfg(test)]
+
mod tests {
+
use super::*;
+
+
/// Test the crc8 function against the test value provided in the
+
/// SHTC3 datasheet (section 5.10).
+
#[test]
+
fn crc8_test_value() {
+
assert_eq!(crc8(&[0x00]), 0xac);
+
assert_eq!(crc8(&[0xbe, 0xef]), 0x92);
+
}
+
}
+966
sachy-shtc3/src/lib.rs
···
···
+
//! # Introduction
+
//!
+
//! This is a platform agnostic Rust driver for the Sensirion SHTC3 temperature /
+
//! humidity sensor, based on the
+
//! [`embedded-hal`](https://github.com/rust-embedded/embedded-hal) traits.
+
//!
+
//! ## Supported Devices
+
//!
+
//! Tested with the following sensors:
+
//! - [SHTC3](https://www.sensirion.com/shtc3/)
+
//!
+
//! ## Blocking / Non-Blocking Modes
+
//!
+
//! This driver provides blocking and non-blocking calls. The blocking calls delay the execution
+
//! until the measurement is done and return the results. The non-blocking ones just start the
+
//! measurement and allow the application code to do other stuff and get the results afterwards.
+
//!
+
//! ## Clock Stretching
+
//!
+
//! While the sensor would provide measurement commands with clock stretching to indicate when the
+
//! measurement is done, this is not implemented and probably won't be.
+
//!
+
//! ## Usage
+
//!
+
//! ### Setup
+
//!
+
//! Instantiate a new driver instance using a [blocking I²C HAL
+
//! implementation](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/i2c/index.html)
+
//! and a [blocking `Delay`
+
//! instance](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/delay/index.html).
+
//! For example, using `linux-embedded-hal` and an SHTC3 sensor:
+
//!
+
//! ```no_run
+
//! use linux_embedded_hal::{Delay, I2cdev};
+
//! use sachy_shtc3::ShtC3;
+
//!
+
//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
+
//! let mut sht = ShtC3::new(dev);
+
//! ```
+
//!
+
//! ### Device Info
+
//!
+
//! Then, you can query information about the sensor:
+
//!
+
//! ```no_run
+
//! use linux_embedded_hal::{Delay, I2cdev};
+
//! use sachy_shtc3::ShtC3;
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
+
//! let device_id = sht.device_identifier().unwrap();
+
//! let raw_id = sht.raw_id_register().unwrap();
+
//! ```
+
//!
+
//! ### Measurements (Blocking)
+
//!
+
//! For measuring your environment, you can either measure just temperature,
+
//! just humidity, or both:
+
//!
+
//! ```no_run
+
//! use linux_embedded_hal::{Delay, I2cdev};
+
//! use sachy_shtc3::{ShtC3, PowerMode};
+
//!
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
+
//! let mut delay = Delay;
+
//!
+
//! let temperature = sht.measure_temperature(PowerMode::NormalMode, &mut delay).unwrap();
+
//! let humidity = sht.measure_humidity(PowerMode::NormalMode, &mut delay).unwrap();
+
//! let combined = sht.measure(PowerMode::NormalMode, &mut delay).unwrap();
+
//!
+
//! println!("Temperature: {} °C", temperature.as_degrees_celsius());
+
//! println!("Humidity: {} %RH", humidity.as_percent());
+
//! println!("Combined: {} °C / {} %RH",
+
//! combined.temperature.as_degrees_celsius(),
+
//! combined.humidity.as_percent());
+
//! ```
+
//!
+
//! You can also use the low power mode for less power consumption, at the cost
+
//! of reduced repeatability and accuracy of the sensor signals. For more
+
//! information, see the ["Low Power Measurement Mode" application note][low-power].
+
//!
+
//! [low-power]: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHTC3_Low_Power_Measurement_Mode.pdf
+
//!
+
//! ```no_run
+
//! use linux_embedded_hal::{Delay, I2cdev};
+
//! use sachy_shtc3::{ShtC3, PowerMode};
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
+
//! let mut delay = Delay;
+
//! let measurement = sht.measure(PowerMode::LowPower, &mut delay).unwrap();
+
//! ```
+
//!
+
//! ### Measurements (Non-Blocking)
+
//!
+
//! If you want to avoid blocking measurements, you can use the non-blocking
+
//! commands instead. You are, however, responsible for ensuring the correct
+
//! timing of the calls.
+
//!
+
//! ```no_run
+
//! use linux_embedded_hal::I2cdev;
+
//! use sachy_shtc3::{ShtC3, PowerMode};
+
//!
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
+
//!
+
//! sht.start_measurement(PowerMode::NormalMode).unwrap();
+
//! // Wait for at least `max_measurement_duration(&sht, PowerMode::NormalMode)` µs
+
//! let result = sht.get_measurement_result().unwrap();
+
//! ```
+
//!
+
//! In non-blocking mode, if desired, you can also read the raw 16-bit
+
//! measurement results from the sensor by using the following two methods
+
//! instead:
+
//!
+
//! - [`get_raw_measurement_result`](crate::ShtC3::get_raw_measurement_result())
+
//! - [`get_raw_partial_measurement_result`](crate::ShtC3::get_raw_partial_measurement_result())
+
//!
+
//! The raw values are of type u16. They require a conversion formula for
+
//! conversion to a temperature / humidity value (see datasheet).
+
//!
+
//! Invoking any command other than
+
//! [`wakeup`](crate::ShtC3::wakeup()) while the sensor is in
+
//! sleep mode will result in an error.
+
//!
+
//! ### Soft Reset
+
//!
+
//! The SHTC3 provides a soft reset mechanism that forces the system into a
+
//! well-defined state without removing the power supply. If the system is in
+
//! its idle state (i.e. if no measurement is in progress) the soft reset
+
//! command can be sent. This triggers the sensor to reset all internal state
+
//! machines and reload calibration data from the memory.
+
//!
+
//! ```no_run
+
//! use linux_embedded_hal::{Delay, I2cdev};
+
//! use sachy_shtc3::{ShtC3, PowerMode};
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
+
//! let mut delay = Delay;
+
//! sht.reset(&mut delay).unwrap();
+
//! ```
+
#![deny(unsafe_code, missing_docs)]
+
#![no_std]
+
+
mod crc;
+
mod types;
+
+
use embedded_hal::{
+
delay::DelayNs as BlockingDelayNs,
+
i2c::{self, I2c, SevenBitAddress},
+
};
+
+
use crc::crc8;
+
use embedded_hal_async::delay::DelayNs;
+
pub use types::*;
+
+
/// Whether temperature or humidity is returned first when doing a measurement.
+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
enum MeasurementOrder {
+
TemperatureFirst,
+
HumidityFirst,
+
}
+
+
/// Measurement power mode: Normal mode or low power mode.
+
///
+
/// The sensors provides a low power measurement mode. Using the low power mode
+
/// significantly shortens the measurement duration and thus minimizes the
+
/// energy consumption per measurement. The benefit of ultra-low power
+
/// consumption comes at the cost of reduced repeatability of the sensor
+
/// signals: while the impact on the relative humidity signal is negligible and
+
/// does not affect accuracy, it has an effect on temperature accuracy.
+
///
+
/// More details can be found in the ["Low Power Measurement Mode" application
+
/// note][an-low-power] by Sensirion.
+
///
+
/// [an-low-power]: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHTC3_Low_Power_Measurement_Mode.pdf
+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
pub enum PowerMode {
+
/// Normal measurement.
+
NormalMode,
+
/// Low power measurement: Less energy consumption, but repeatability and
+
/// accuracy of measurements are negatively impacted.
+
LowPower,
+
}
+
+
/// All possible errors in this crate
+
#[derive(Debug, PartialEq, Clone)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
pub enum Error<E> {
+
/// I²C bus error
+
I2c(E),
+
/// CRC checksum validation failed
+
Crc,
+
}
+
+
impl<E> From<CrcError> for Error<E> {
+
fn from(_value: CrcError) -> Self {
+
Self::Crc
+
}
+
}
+
+
#[derive(Debug, PartialEq, Clone)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
struct CrcError;
+
+
impl<E> From<E> for Error<E>
+
where
+
E: i2c::Error,
+
{
+
fn from(e: E) -> Self {
+
Error::I2c(e)
+
}
+
}
+
+
/// I²C commands sent to the sensor.
+
#[derive(Debug, Copy, Clone)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
enum Command {
+
/// Go into sleep mode.
+
Sleep,
+
/// Wake up from sleep mode.
+
WakeUp,
+
/// Measurement commands.
+
Measure {
+
power_mode: PowerMode,
+
order: MeasurementOrder,
+
},
+
/// Software reset.
+
SoftwareReset,
+
/// Read ID register.
+
ReadIdRegister,
+
}
+
+
impl Command {
+
fn as_bytes(self) -> [u8; 2] {
+
match self {
+
Command::Sleep => [0xB0, 0x98],
+
Command::WakeUp => [0x35, 0x17],
+
Command::Measure {
+
power_mode: PowerMode::NormalMode,
+
order: MeasurementOrder::TemperatureFirst,
+
} => [0x78, 0x66],
+
Command::Measure {
+
power_mode: PowerMode::NormalMode,
+
order: MeasurementOrder::HumidityFirst,
+
} => [0x58, 0xE0],
+
Command::Measure {
+
power_mode: PowerMode::LowPower,
+
order: MeasurementOrder::TemperatureFirst,
+
} => [0x60, 0x9C],
+
Command::Measure {
+
power_mode: PowerMode::LowPower,
+
order: MeasurementOrder::HumidityFirst,
+
} => [0x40, 0x1A],
+
Command::ReadIdRegister => [0xEF, 0xC8],
+
Command::SoftwareReset => [0x80, 0x5D],
+
}
+
}
+
}
+
+
/// Driver for the SHTC3 sensor.
+
#[derive(Debug, Default)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
pub struct ShtC3<I2C> {
+
/// The concrete I²C device implementation.
+
i2c: I2C,
+
/// The I²C device address.
+
address: u8,
+
}
+
+
impl<I2C> ShtC3<I2C> {
+
/// Create a new instance of the driver for the SHTC3.
+
#[inline]
+
pub const fn new(i2c: I2C) -> Self {
+
Self { i2c, address: 0x70 }
+
}
+
+
/// Get the device's wakeup delay duration in microseconds
+
#[inline(always)]
+
pub const fn wakeup_duration(&self) -> u32 {
+
240
+
}
+
+
/// Destroy driver instance, return I²C bus instance.
+
pub fn destroy(self) -> I2C {
+
self.i2c
+
}
+
+
/// Return the maximum measurement duration (depending on the mode) in
+
/// microseconds.
+
///
+
/// Maximum measurement duration (SHTC3 datasheet 3.1):
+
/// - Normal mode: 12.1 ms
+
/// - Low power mode: 0.8 ms
+
#[inline(always)]
+
pub const fn max_measurement_duration(&self, mode: PowerMode) -> u32 {
+
match mode {
+
PowerMode::NormalMode => 12100,
+
PowerMode::LowPower => 800,
+
}
+
}
+
+
/// Returns the reset duration for the SHTC3 in microseconds
+
#[inline(always)]
+
pub const fn reset_duration(&self) -> u32 {
+
240_000
+
}
+
+
/// Iterate over the provided buffer and validate the CRC8 checksum.
+
///
+
/// If the checksum is wrong, return `CrcError`.
+
///
+
/// Note: This method will consider every third byte a checksum byte. If
+
/// the buffer size is not a multiple of 3, then not all data will be
+
/// validated.
+
fn validate_crc(&self, buf: &[u8]) -> Result<(), CrcError> {
+
let mut chunks = buf.chunks_exact(3);
+
+
for chunk in chunks.by_ref() {
+
if crc8(&chunk[..2]) != chunk[2] {
+
return Err(CrcError);
+
}
+
}
+
+
#[cfg(feature = "defmt")]
+
if !chunks.remainder().is_empty() {
+
defmt::warn!("Remaining data in buffer was not CRC8 validated");
+
}
+
+
Ok(())
+
}
+
}
+
+
impl<I2C> ShtC3<I2C>
+
where
+
I2C: embedded_hal_async::i2c::I2c<embedded_hal_async::i2c::SevenBitAddress>,
+
{
+
/// Write an I²C command to the sensor.
+
async fn send_command_async(&mut self, command: Command) -> Result<(), Error<I2C::Error>> {
+
self.i2c
+
.write(self.address, &command.as_bytes())
+
.await
+
.map_err(Error::I2c)
+
}
+
+
/// Read data into the provided buffer and validate the CRC8 checksum.
+
///
+
/// If the checksum is wrong, return `Error::Crc`.
+
///
+
/// Note: This method will consider every third byte a checksum byte. If
+
/// the buffer size is not a multiple of 3, then not all data will be
+
/// validated.
+
async fn read_with_crc_async(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> {
+
self.i2c.read(self.address, buf).await?;
+
self.validate_crc(buf)?;
+
Ok(())
+
}
+
+
/// Return the raw ID register.
+
pub async fn raw_id_register_async(&mut self) -> Result<u16, Error<I2C::Error>> {
+
// Request serial number
+
self.send_command_async(Command::ReadIdRegister).await?;
+
+
// Read id register
+
let mut buf = [0; 3];
+
self.read_with_crc_async(&mut buf).await?;
+
+
Ok(u16::from_be_bytes([buf[0], buf[1]]))
+
}
+
+
/// Return the 7-bit device identifier.
+
///
+
/// Should be 0x47 (71) for the SHTC3.
+
pub async fn device_identifier_async(&mut self) -> Result<u8, Error<I2C::Error>> {
+
let ident = self.raw_id_register_async().await?;
+
let lsb = (ident & 0b0011_1111) as u8;
+
let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8;
+
Ok(lsb | msb)
+
}
+
+
/// Set sensor to sleep mode.
+
///
+
/// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires
+
/// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C
+
/// communication.
+
pub async fn sleep_async(&mut self) -> Result<(), Error<I2C::Error>> {
+
self.send_command_async(Command::Sleep).await
+
}
+
+
/// Trigger a soft reset. (async)
+
///
+
/// The SHTC3 provides a soft reset mechanism that forces the system into a
+
/// well-defined state without removing the power supply. If the system is
+
/// in its idle state (i.e. if no measurement is in progress) the soft
+
/// reset command can be sent. This triggers the sensor to reset all
+
/// internal state machines and reload calibration data from the memory.
+
pub async fn reset_async(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<I2C::Error>> {
+
self.send_command_async(Command::SoftwareReset).await?;
+
// Table 5: 180-240 µs
+
delay.delay_us(self.reset_duration()).await;
+
Ok(())
+
}
+
+
/// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready. (async)
+
pub async fn wakeup_async(
+
&mut self,
+
delay: &mut impl DelayNs,
+
) -> Result<(), Error<I2C::Error>> {
+
self.send_command_async(Command::WakeUp).await?;
+
delay.delay_us(self.wakeup_duration()).await;
+
Ok(())
+
}
+
+
/// Run a temperature/humidity measurement and return the combined result.
+
///
+
/// This is an async function call.
+
pub async fn measure_async(
+
&mut self,
+
mode: PowerMode,
+
delay: &mut impl DelayNs,
+
) -> Result<Measurement, Error<I2C::Error>> {
+
self.send_command_async(Command::Measure {
+
power_mode: mode,
+
order: MeasurementOrder::TemperatureFirst,
+
})
+
.await?;
+
+
delay.delay_us(self.max_measurement_duration(mode)).await;
+
+
let mut buf = [0; 6];
+
self.read_with_crc_async(&mut buf).await?;
+
+
Ok(RawMeasurement {
+
temperature: u16::from_be_bytes([buf[0], buf[1]]),
+
humidity: u16::from_be_bytes([buf[3], buf[4]]),
+
}
+
.into())
+
}
+
}
+
+
/// General blocking functions.
+
impl<I2C> ShtC3<I2C>
+
where
+
I2C: I2c<SevenBitAddress>,
+
{
+
/// Write an I²C command to the sensor.
+
fn send_command(&mut self, command: Command) -> Result<(), Error<I2C::Error>> {
+
self.i2c
+
.write(self.address, &command.as_bytes())
+
.map_err(Error::I2c)
+
}
+
+
/// Read data into the provided buffer and validate the CRC8 checksum.
+
///
+
/// If the checksum is wrong, return `Error::Crc`.
+
///
+
/// Note: This method will consider every third byte a checksum byte. If
+
/// the buffer size is not a multiple of 3, then not all data will be
+
/// validated.
+
fn read_with_crc(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> {
+
self.i2c.read(self.address, buf)?;
+
self.validate_crc(buf)?;
+
Ok(())
+
}
+
+
/// Return the raw ID register.
+
pub fn raw_id_register(&mut self) -> Result<u16, Error<I2C::Error>> {
+
// Request serial number
+
self.send_command(Command::ReadIdRegister)?;
+
+
// Read id register
+
let mut buf = [0; 3];
+
self.read_with_crc(&mut buf)?;
+
+
Ok(u16::from_be_bytes([buf[0], buf[1]]))
+
}
+
+
/// Return the 7-bit device identifier.
+
///
+
/// Should be 0x47 (71) for the SHTC3.
+
pub fn device_identifier(&mut self) -> Result<u8, Error<I2C::Error>> {
+
let ident = self.raw_id_register()?;
+
let lsb = (ident & 0b0011_1111) as u8;
+
let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8;
+
Ok(lsb | msb)
+
}
+
+
/// Trigger a soft reset. (blocking)
+
///
+
/// The SHTC3 provides a soft reset mechanism that forces the system into a
+
/// well-defined state without removing the power supply. If the system is
+
/// in its idle state (i.e. if no measurement is in progress) the soft
+
/// reset command can be sent. This triggers the sensor to reset all
+
/// internal state machines and reload calibration data from the memory.
+
pub fn reset(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> {
+
self.send_command(Command::SoftwareReset)?;
+
// Table 5: 180-240 µs
+
delay.delay_us(self.reset_duration());
+
Ok(())
+
}
+
+
/// Trigger a soft reset.
+
///
+
/// The SHTC3 provides a soft reset mechanism that forces the system into a
+
/// well-defined state without removing the power supply. If the system is
+
/// in its idle state (i.e. if no measurement is in progress) the soft
+
/// reset command can be sent. This triggers the sensor to reset all
+
/// internal state machines and reload calibration data from the memory.
+
pub fn start_reset(&mut self) -> Result<(), Error<I2C::Error>> {
+
self.send_command(Command::SoftwareReset)
+
}
+
+
/// Set sensor to sleep mode.
+
///
+
/// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires
+
/// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C
+
/// communication.
+
pub fn sleep(&mut self) -> Result<(), Error<I2C::Error>> {
+
self.send_command(Command::Sleep)
+
}
+
+
/// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready.
+
pub fn wakeup(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> {
+
self.start_wakeup()?;
+
delay.delay_us(self.wakeup_duration());
+
Ok(())
+
}
+
}
+
+
/// Non-blocking functions for starting / reading measurements.
+
impl<I2C> ShtC3<I2C>
+
where
+
I2C: I2c<SevenBitAddress>,
+
{
+
/// Start a measurement with the specified measurement order and write the
+
/// result into the provided buffer.
+
///
+
/// If you just need one of the two measurements, provide a 3-byte buffer
+
/// instead of a 6-byte buffer.
+
fn start_measure_partial(
+
&mut self,
+
power_mode: PowerMode,
+
order: MeasurementOrder,
+
) -> Result<(), Error<I2C::Error>> {
+
// Request measurement
+
self.send_command(Command::Measure { power_mode, order })
+
}
+
+
/// Start a combined temperature / humidity measurement.
+
pub fn start_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> {
+
self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst)
+
}
+
+
/// Start a temperature measurement.
+
pub fn start_temperature_measurement(
+
&mut self,
+
mode: PowerMode,
+
) -> Result<(), Error<I2C::Error>> {
+
self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst)
+
}
+
+
/// Start a humidity measurement.
+
pub fn start_humidity_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> {
+
self.start_measure_partial(mode, MeasurementOrder::HumidityFirst)
+
}
+
+
/// Read the result of a temperature / humidity measurement.
+
pub fn get_measurement_result(&mut self) -> Result<Measurement, Error<I2C::Error>> {
+
let raw = self.get_raw_measurement_result()?;
+
Ok(raw.into())
+
}
+
+
/// Read the result of a temperature measurement.
+
pub fn get_temperature_measurement_result(&mut self) -> Result<Temperature, Error<I2C::Error>> {
+
let raw = self.get_raw_partial_measurement_result()?;
+
Ok(Temperature::from_raw(raw))
+
}
+
+
/// Read the result of a humidity measurement.
+
pub fn get_humidity_measurement_result(&mut self) -> Result<Humidity, Error<I2C::Error>> {
+
let raw = self.get_raw_partial_measurement_result()?;
+
Ok(Humidity::from_raw(raw))
+
}
+
+
/// Read the raw result of a combined temperature / humidity measurement.
+
pub fn get_raw_measurement_result(&mut self) -> Result<RawMeasurement, Error<I2C::Error>> {
+
let mut buf = [0; 6];
+
self.read_with_crc(&mut buf)?;
+
Ok(RawMeasurement {
+
temperature: u16::from_be_bytes([buf[0], buf[1]]),
+
humidity: u16::from_be_bytes([buf[3], buf[4]]),
+
})
+
}
+
+
/// Read the raw result of a partial temperature or humidity measurement.
+
///
+
/// Return the raw 3-byte buffer (after validating CRC).
+
pub fn get_raw_partial_measurement_result(&mut self) -> Result<u16, Error<I2C::Error>> {
+
let mut buf = [0; 3];
+
self.read_with_crc(&mut buf)?;
+
Ok(u16::from_be_bytes([buf[0], buf[1]]))
+
}
+
+
/// Wake up sensor from [sleep mode](#method.sleep).
+
pub fn start_wakeup(&mut self) -> Result<(), Error<I2C::Error>> {
+
self.send_command(Command::WakeUp)
+
}
+
}
+
+
/// Blocking functions for doing measurements.
+
impl<I2C> ShtC3<I2C>
+
where
+
I2C: I2c<SevenBitAddress>,
+
{
+
/// Wait the maximum time needed for the given measurement mode
+
pub fn wait_for_measurement(&mut self, mode: PowerMode, delay: &mut impl BlockingDelayNs) {
+
delay.delay_us(self.max_measurement_duration(mode));
+
}
+
+
/// Run a temperature/humidity measurement and return the combined result.
+
///
+
/// This is a blocking function call.
+
pub fn measure(
+
&mut self,
+
mode: PowerMode,
+
delay: &mut impl BlockingDelayNs,
+
) -> Result<Measurement, Error<I2C::Error>> {
+
self.start_measurement(mode)?;
+
self.wait_for_measurement(mode, delay);
+
self.get_measurement_result()
+
}
+
+
/// Run a temperature measurement and return the result.
+
///
+
/// This is a blocking function call.
+
///
+
/// Internally, it will request a measurement in "temperature first" mode
+
/// and only read the first half of the measurement response.
+
pub fn measure_temperature(
+
&mut self,
+
mode: PowerMode,
+
delay: &mut impl BlockingDelayNs,
+
) -> Result<Temperature, Error<I2C::Error>> {
+
self.start_temperature_measurement(mode)?;
+
self.wait_for_measurement(mode, delay);
+
self.get_temperature_measurement_result()
+
}
+
+
/// Run a humidity measurement and return the result.
+
///
+
/// This is a blocking function call.
+
///
+
/// Internally, it will request a measurement in "humidity first" mode
+
/// and only read the first half of the measurement response.
+
pub fn measure_humidity(
+
&mut self,
+
mode: PowerMode,
+
delay: &mut impl BlockingDelayNs,
+
) -> Result<Humidity, Error<I2C::Error>> {
+
self.start_humidity_measurement(mode)?;
+
self.wait_for_measurement(mode, delay);
+
self.get_humidity_measurement_result()
+
}
+
}
+
+
#[cfg(test)]
+
mod tests {
+
extern crate alloc;
+
+
use super::*;
+
+
use embedded_hal::i2c::ErrorKind;
+
use embedded_hal_mock::eh1::{
+
delay::NoopDelay,
+
i2c::{Mock as I2cMock, Transaction},
+
};
+
+
const SHT_ADDR: u8 = 0x70;
+
+
mod core {
+
use super::*;
+
+
/// Test whether the `send_command` function propagates I²C errors.
+
#[test]
+
fn send_command_error() {
+
let expectations =
+
[Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8])
+
.with_error(ErrorKind::Other)];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let err = sht.send_command(Command::ReadIdRegister).unwrap_err();
+
assert_eq!(err, Error::I2c(ErrorKind::Other));
+
sht.destroy().done();
+
}
+
+
/// Test the `validate_crc` function.
+
#[test]
+
fn validate_crc() {
+
let mock = I2cMock::new(&[]);
+
let sht = ShtC3::new(mock);
+
+
// Not enough data
+
sht.validate_crc(&[]).unwrap();
+
sht.validate_crc(&[0xbe]).unwrap();
+
sht.validate_crc(&[0xbe, 0xef]).unwrap();
+
+
// Valid CRC
+
sht.validate_crc(&[0xbe, 0xef, 0x92]).unwrap();
+
+
// Invalid CRC
+
match sht.validate_crc(&[0xbe, 0xef, 0x91]) {
+
Err(CrcError) => {}
+
Ok(_) => panic!("CRC check did not fail"),
+
}
+
+
// Valid CRC (8 bytes)
+
sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0x92, 0x00, 0x00])
+
.unwrap();
+
+
// Invalid CRC (8 bytes)
+
match sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0xff, 0x00, 0x00]) {
+
Err(CrcError) => {}
+
Ok(_) => panic!("CRC check did not fail"),
+
}
+
+
sht.destroy().done();
+
}
+
+
/// Test the `read_with_crc` function.
+
#[test]
+
fn read_with_crc() {
+
let mut buf = [0; 3];
+
+
// Valid CRC
+
let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x92])];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
sht.read_with_crc(&mut buf).unwrap();
+
assert_eq!(buf, [0xbe, 0xef, 0x92]);
+
sht.destroy().done();
+
+
// Invalid CRC
+
let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x00])];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
match sht.read_with_crc(&mut buf) {
+
Err(Error::Crc) => {}
+
Err(_) => panic!("Invalid error: Must be Crc"),
+
Ok(_) => panic!("CRC check did not fail"),
+
}
+
assert_eq!(buf, [0xbe, 0xef, 0x00]); // Buf was changed
+
sht.destroy().done();
+
}
+
}
+
+
mod factory_functions {
+
use super::*;
+
+
#[test]
+
fn new_shtc3() {
+
let mock = I2cMock::new(&[]);
+
let sht = ShtC3::new(mock);
+
assert_eq!(sht.address, 0x70);
+
sht.destroy().done();
+
}
+
}
+
+
mod device_info {
+
use super::*;
+
+
/// Test the `raw_id_register` function.
+
#[test]
+
fn raw_id_register() {
+
let msb = 0b00001000;
+
let lsb = 0b00000111;
+
let crc = crc8(&[msb, lsb]);
+
let expectations = [
+
Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]),
+
Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]),
+
];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let val = sht.raw_id_register().unwrap();
+
assert_eq!(val, (msb as u16) << 8 | (lsb as u16));
+
sht.destroy().done();
+
}
+
+
/// Test the `device_identifier` function.
+
#[test]
+
fn device_identifier() {
+
let msb = 0b00001000;
+
let lsb = 0b00000111;
+
let crc = crc8(&[msb, lsb]);
+
let expectations = [
+
Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]),
+
Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]),
+
];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let ident = sht.device_identifier().unwrap();
+
assert_eq!(ident, 0b01000111);
+
sht.destroy().done();
+
}
+
}
+
+
mod measurements {
+
use super::*;
+
+
#[test]
+
fn measure_normal() {
+
let expectations = [
+
// Expect a write command: Normal mode measurement, temperature
+
// first, no clock stretching.
+
Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]),
+
// Return the measurement result (using example values from the
+
// datasheet, section 5.4 "Measuring and Reading the Signals")
+
Transaction::read(
+
SHT_ADDR,
+
alloc::vec![
+
0b0110_0100,
+
0b1000_1011,
+
0b1100_0111,
+
0b1010_0001,
+
0b0011_0011,
+
0b0001_1100,
+
],
+
),
+
];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let mut delay = NoopDelay;
+
let measurement = sht.measure(PowerMode::NormalMode, &mut delay).unwrap();
+
assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
+
assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH
+
sht.destroy().done();
+
}
+
+
#[test]
+
fn measure_low_power() {
+
let expectations = [
+
// Expect a write command: Low power mode measurement, temperature
+
// first, no clock stretching.
+
Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C]),
+
// Return the measurement result (using example values from the
+
// datasheet, section 5.4 "Measuring and Reading the Signals")
+
Transaction::read(
+
SHT_ADDR,
+
alloc::vec![
+
0b0110_0100,
+
0b1000_1011,
+
0b1100_0111,
+
0b1010_0001,
+
0b0011_0011,
+
0b0001_1100,
+
],
+
),
+
];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let mut delay = NoopDelay;
+
let measurement = sht.measure(PowerMode::LowPower, &mut delay).unwrap();
+
assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
+
assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH
+
sht.destroy().done();
+
}
+
+
#[test]
+
fn measure_temperature_only() {
+
let expectations = [
+
// Expect a write command: Normal mode measurement, temperature
+
// first, no clock stretching.
+
Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]),
+
// Return the measurement result (using example values from the
+
// datasheet, section 5.4 "Measuring and Reading the Signals")
+
Transaction::read(SHT_ADDR, alloc::vec![0b0110_0100, 0b1000_1011, 0b1100_0111]),
+
];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let mut delay = NoopDelay;
+
let temperature = sht
+
.measure_temperature(PowerMode::NormalMode, &mut delay)
+
.unwrap();
+
assert_eq!(temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
+
sht.destroy().done();
+
}
+
+
#[test]
+
fn measure_humidity_only() {
+
let expectations = [
+
// Expect a write command: Normal mode measurement, humidity
+
// first, no clock stretching.
+
Transaction::write(SHT_ADDR, alloc::vec![0x58, 0xE0]),
+
// Return the measurement result (using example values from the
+
// datasheet, section 5.4 "Measuring and Reading the Signals")
+
Transaction::read(SHT_ADDR, alloc::vec![0b1010_0001, 0b0011_0011, 0b0001_1100]),
+
];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let mut delay = NoopDelay;
+
let humidity = sht
+
.measure_humidity(PowerMode::NormalMode, &mut delay)
+
.unwrap();
+
assert_eq!(humidity.as_millipercent(), 62_968); // 62.9 %RH
+
sht.destroy().done();
+
}
+
+
/// Ensure that I²C write errors are handled when measuring.
+
#[test]
+
fn measure_write_error() {
+
let expectations =
+
[Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C])
+
.with_error(ErrorKind::Other)];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let err = sht
+
.measure(PowerMode::LowPower, &mut NoopDelay)
+
.unwrap_err();
+
assert_eq!(err, Error::I2c(ErrorKind::Other));
+
sht.destroy().done();
+
}
+
}
+
+
mod power_management {
+
use super::*;
+
+
/// Test the `sleep` function.
+
#[test]
+
fn sleep() {
+
let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0xB0, 0x98])];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
sht.sleep().unwrap();
+
sht.destroy().done();
+
}
+
+
/// Test the `wakeup` function.
+
#[test]
+
fn wakeup() {
+
let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x35, 0x17])];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
sht.wakeup(&mut NoopDelay).unwrap();
+
sht.destroy().done();
+
}
+
+
/// Test the `reset` function.
+
#[test]
+
fn reset() {
+
let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x80, 0x5D])];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
sht.reset(&mut NoopDelay).unwrap();
+
sht.destroy().done();
+
}
+
}
+
+
mod max_measurement_duration {
+
use super::*;
+
+
#[test]
+
fn shortcut_function() {
+
let c3 = ShtC3::new(I2cMock::new(&[]));
+
+
assert_eq!(c3.max_measurement_duration(PowerMode::NormalMode), 12100);
+
assert_eq!(c3.max_measurement_duration(PowerMode::LowPower), 800);
+
+
c3.destroy().done();
+
}
+
}
+
}
+199
sachy-shtc3/src/types.rs
···
···
+
/// A temperature measurement.
+
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
pub struct Temperature(i32);
+
+
/// A humidity measurement.
+
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
pub struct Humidity(i32);
+
+
/// A combined temperature / humidity measurement.
+
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
pub struct Measurement {
+
/// The measured temperature.
+
pub temperature: Temperature,
+
/// The measured humidity.
+
pub humidity: Humidity,
+
}
+
+
impl core::ops::AddAssign for Measurement {
+
fn add_assign(&mut self, rhs: Self) {
+
self.temperature.0 += rhs.temperature.0;
+
self.humidity.0 += rhs.humidity.0;
+
}
+
}
+
+
impl core::ops::DivAssign<i32> for Measurement {
+
fn div_assign(&mut self, rhs: i32) {
+
self.temperature.0 /= rhs;
+
self.humidity.0 /= rhs;
+
}
+
}
+
+
/// A combined raw temperature / humidity measurement.
+
///
+
/// The raw values are of type u16. They require a conversion formula for
+
/// conversion to a temperature / humidity value (see datasheet).
+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
pub struct RawMeasurement {
+
/// The measured temperature (raw value).
+
pub temperature: u16,
+
/// The measured humidity (raw value).
+
pub humidity: u16,
+
}
+
+
impl From<RawMeasurement> for Measurement {
+
fn from(other: RawMeasurement) -> Self {
+
Self {
+
temperature: Temperature::from_raw(other.temperature),
+
humidity: Humidity::from_raw(other.humidity),
+
}
+
}
+
}
+
+
impl Temperature {
+
/// Create a new `Temperature` from a raw measurement result.
+
pub const fn from_raw(raw: u16) -> Self {
+
Self(convert_temperature(raw))
+
}
+
+
/// Return temperature in milli-degrees celsius.
+
pub const fn as_millidegrees_celsius(&self) -> i32 {
+
self.0
+
}
+
+
/// Return temperature in degrees celcius with 0.01 precision
+
pub const fn as_10mk_celsius(&self) -> i16 {
+
(self.0 / 10) as i16
+
}
+
+
/// Return temperature in degrees celsius.
+
pub const fn as_degrees_celsius(&self) -> f32 {
+
self.0 as f32 / 1000.0
+
}
+
}
+
+
impl Humidity {
+
/// Create a new `Humidity` from a raw measurement result.
+
pub const fn from_raw(raw: u16) -> Self {
+
Self(convert_humidity(raw))
+
}
+
+
/// Return relative humidity in 1/100 %RH
+
pub const fn as_10mk_percent(&self) -> u16 {
+
(self.0 / 10).unsigned_abs() as u16
+
}
+
+
/// Return relative humidity in 1/1000 %RH.
+
pub const fn as_millipercent(&self) -> i32 {
+
self.0
+
}
+
+
/// Return relative humidity in 1 %RH
+
pub const fn as_1k_percent(&self) -> u8 {
+
(self.0 / 1000).unsigned_abs() as u8
+
}
+
+
/// Return relative humidity in %RH.
+
pub const fn as_percent(&self) -> f32 {
+
self.0 as f32 / 1000.0
+
}
+
}
+
+
/// Convert raw temperature measurement to milli-degrees celsius.
+
///
+
/// Formula (datasheet 5.11): -45 + 175 * (val / 2^16),
+
/// optimized for fixed point math.
+
#[inline]
+
const fn convert_temperature(temp_raw: u16) -> i32 {
+
(((temp_raw as u32) * 21875) >> 13) as i32 - 45000
+
}
+
+
/// Convert raw humidity measurement to relative humidity.
+
///
+
/// Formula (datasheet 5.11): 100 * (val / 2^16),
+
/// optimized for fixed point math.
+
#[inline]
+
const fn convert_humidity(humi_raw: u16) -> i32 {
+
(((humi_raw as u32) * 12500) >> 13) as i32
+
}
+
+
#[cfg(test)]
+
mod tests {
+
use super::*;
+
+
/// Test conversion of raw measurement results into °C.
+
#[test]
+
fn test_convert_temperature() {
+
let test_data = [
+
(0x0000, -45000),
+
// Datasheet setion 5.11 "Conversion of Sensor Output"
+
((0b0110_0100_u16 << 8) | 0b1000_1011, 23730),
+
];
+
for td in &test_data {
+
assert_eq!(convert_temperature(td.0), td.1);
+
}
+
}
+
+
/// Test conversion of raw measurement results into %RH.
+
#[test]
+
fn test_convert_humidity() {
+
let test_data = [
+
(0x0000, 0),
+
// Datasheet setion 5.11 "Conversion of Sensor Output"
+
((0b1010_0001_u16 << 8) | 0b0011_0011, 62968),
+
];
+
for td in &test_data {
+
assert_eq!(convert_humidity(td.0), td.1);
+
}
+
}
+
+
/// Test conversion of raw measurement results into °C and %RH.
+
#[test]
+
fn measurement_conversion() {
+
// Datasheet setion 5.11 "Conversion of Sensor Output"
+
let temperature = convert_temperature((0b0110_0100_u16 << 8) | 0b1000_1011);
+
let humidity = convert_humidity((0b1010_0001_u16 << 8) | 0b0011_0011);
+
assert_eq!(temperature, 23730);
+
assert_eq!(humidity, 62968);
+
}
+
+
#[test]
+
fn temperature() {
+
let temp = Temperature(24123);
+
assert_eq!(temp.as_millidegrees_celsius(), 24123);
+
assert_eq!(temp.as_degrees_celsius(), 24.123);
+
}
+
+
#[test]
+
fn humidity() {
+
let humi = Humidity(65432);
+
assert_eq!(humi.as_millipercent(), 65432);
+
assert_eq!(humi.as_percent(), 65.432);
+
}
+
+
#[test]
+
fn measurement_from_into() {
+
// Datasheet setion 5.11 "Conversion of Sensor Output"
+
let raw = RawMeasurement {
+
temperature: (0b0110_0100_u16 << 8) | 0b1000_1011,
+
humidity: (0b1010_0001_u16 << 8) | 0b0011_0011,
+
};
+
+
// std::convert::From
+
let measurement1 = Measurement::from(raw);
+
assert_eq!(measurement1.temperature.0, 23730);
+
assert_eq!(measurement1.humidity.0, 62968);
+
+
// std::convert::Into
+
let measurement2: Measurement = raw.into();
+
assert_eq!(measurement2.temperature.0, 23730);
+
assert_eq!(measurement2.humidity.0, 62968);
+
+
// std::cmp::PartialEq
+
assert_eq!(measurement1, measurement2);
+
}
+
}