diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c53a03f..f187833 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,20 +17,6 @@ jobs: with: command: check - test: - name: Run Test Suite - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - uses: actions-rs/cargo@v1 - with: - command: test - fmt: name: Check Formatting runs-on: ubuntu-latest @@ -62,16 +48,3 @@ jobs: with: command: clippy args: -- -D warnings - - license-check: - name: Check Licenses and Security Advisories - runs-on: ubuntu-latest - strategy: - matrix: - checks: - - advisories bans licenses sources - steps: - - uses: actions/checkout@v2 - - uses: EmbarkStudios/cargo-deny-action@v1 - with: - command: check ${{ matrix.checks }} diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/bme680-rust.iml b/.idea/bme680-rust.iml new file mode 100644 index 0000000..29b11b5 --- /dev/null +++ b/.idea/bme680-rust.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..fbf8647 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index b29e573..dba02d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,30 +1,25 @@ [package] -authors = ["marcelbuesing "] +authors = ["marcelbuesing ", "denisnutiu "] description = "A pure Rust implementation for the BME680 environmental sensor." -documentation = "https://docs.rs/bme680" +documentation = "https://github.com/dnutiu/bme680-rust" license = "MIT" name = "bme680" -repository = "https://github.com/marcelbuesing/bme680-hal" -version = "0.6.0" -edition = "2018" +repository = "https://github.com/dnutiu/bme680-rust" +version = "0.9.0" +edition = "2021" [badges] -travis-ci = { repository = "https://github.com/marcelbuesing/bme680-hal", branch = "master" } maintenance = { status = "passively-maintained" } [dependencies] -bitflags = "1.2" -embedded-hal = "=1.0.0-alpha.5" +bitflags = "2.6" +embedded-hal = "=1.0.0" log = "0.4" -serde = { version = "1.0", optional = true, default-features = false } +serde = { version = "1.0", optional = true, default-features = false, features = ["derive"] } +anyhow = { version = "1.0" , default-features = false} [dev-dependencies] -env_logger = "0.9" -futures = { version = "0.3" } -i2cdev = "0.5" -influx_db_client = { version = "0.5", default-features= false, features = ["rustls-tls"] } -tokio = {version = "1.5", features = ["full"] } -url = "2.1" +env_logger = "0.11.5" -[target.'cfg(target_os = "linux")'.dev-dependencies] -linux-embedded-hal = "0.4.0-alpha.1" +[target.'cfg(target_os = "linux")'.dependencies] +linux-embedded-hal = "0.4.0" \ No newline at end of file diff --git a/README.md b/README.md index adb5c64..1fab7fc 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,27 @@ -BME680 + Rust [![Actions Status](https://github.com/marcelbuesing/bme680/workflows/Continuous%20integration/badge.svg)](https://github.com/marcelbuesing/bme680/actions?query=workflow%3A"Continuous+integration") -[![Cargo Deny Status](https://img.shields.io/badge/cargo--deny-license%20checked-green)](https://github.com/marcelbuesing/bme680/actions?query=workflow%3A"Continuous+integration") +BME680 Rust Library ============= -This repository contains a pure Rust implementation for the [BME680](https://www.bosch-sensortec.com/bst/products/all_products/bme680) environmental sensor. The library can be used to read the gas, pressure, humidity and temperature sensors via I²C. +This repository contains a pure Rust implementation for the [BME680](https://www.bosch-sensortec.com/bst/products/all_products/bme680) environmental sensor. +The library can be used to read the gas, pressure, humidity and temperature sensors via I²C. -The library uses the [embedded-hal](https://github.com/japaric/embedded-hal) library to abstract reading and writing via I²C. In the examples you can find a demo how to use the library in Linux using the [linux-embedded-hal](https://github.com/japaric/linux-embedded-hal) implementation. +It is a fork of the library written by Marcel Buessing: https://github.com/marcelbuesing/bme680. -# Alternative -[drogue-bme680](https://github.com/drogue-iot/drogue-bme680) +To use this library, create a new project and add it as a dependency: -# Example getting started Linux +```toml +[dependencies] +bme680 = {git = "https://github.com/dnutiu/bme680-rust.git", version = "0.9.0"} +``` + +# Getting started on Raspberry Pi + +Assuming that you have connected the sensor to the Raspberry PI's GPIO ports. + +Install required libraries for developing I2C. + +```bash +sudo apt-get install build-essential libi2c-dev i2c-tools python-dev libffi-dev +``` Determine the I2C device path @@ -34,11 +46,16 @@ pi@raspberrypi:~ $ i2cdetect -y 1 70: -- -- -- -- -- -- 76 ``` -# Example Influx Client -The examples folder contains an example for a simple influx database client inserting collected values. -Below you may find examples of Chronograf graphs of an indoor measurement over a period of 30 days. +The read-sensor-data from the examples folder is configured to use the I2C device `/dev/i2c-1` and the `0x76` address. + +To run the example, clone the repository and go to the examples' folder: -![Temperature Graph](examples/res/influx_temperature.png "Temperature measurement in C°") -![Humidity Graph](examples/res/influx_humidity.png "Humidity measurement in %") -![Air pressure Graph](examples/res/influx_pressure.png "Air pressure measurement in hPa") -![Gas resistance Graph](examples/res/influx_gas_resistance.png "Gas resistance measurement") +```bash +git clone https://github.com/dnutiu/bme680-rust.git && cd bme680-rust/examples/read-sensor-data +``` +Then run the example: + +```bash +export RUST_LOG=info +cargo run +``` diff --git a/ci/no_std_test/Cargo.toml b/ci/no_std_test/Cargo.toml deleted file mode 100644 index 4fe4c66..0000000 --- a/ci/no_std_test/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "no_std_test" -version = "0.1.0" -authors = ["marcelbuesing "] -edition = "2018" - -[dependencies] -bme680 = { path = "../../"} -log = "0.4" diff --git a/ci/no_std_test/src/lib.rs b/ci/no_std_test/src/lib.rs deleted file mode 100644 index ebb5111..0000000 --- a/ci/no_std_test/src/lib.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![no_std] - -use bme680::*; -use log::info; - -pub fn test_no_std() { - info!("chipid {:?}", BME680_CHIP_ID); -} \ No newline at end of file diff --git a/examples/influx_client.rs b/examples/influx_client.rs deleted file mode 100644 index b41e8cd..0000000 --- a/examples/influx_client.rs +++ /dev/null @@ -1,90 +0,0 @@ -/// -/// This example demonstrates how to read values from the sensor and -/// continously send them to an influx database. -/// Make sure you adapt the influx constants and likely also the i2c device id and I2CAddress. -/// -use bme680::{ - Bme680, FieldDataCondition, I2CAddress, IIRFilterSize, OversamplingSetting, PowerMode, - SettingsBuilder, -}; -use influx_db_client::{points, Client, Point, Points, Precision, Value}; -use linux_embedded_hal::*; -use std::time::Duration; -use tokio::time::sleep; -use url::Url; - -const INFLUX_ADDRESS: &str = "http://127.0.0.1:8086"; -const INFLUX_USER: &str = "user"; -const INFLUX_PASSWORD: &str = "pass"; -const INFLUX_DATABASE: &str = "influxdb"; - -#[tokio::main] -async fn main() -> Result<(), ()> { - // Init device - let i2c = I2cdev::new("/dev/i2c-1").unwrap(); - let mut delayer = Delay {}; - let mut dev = Bme680::init(i2c, &mut delayer, I2CAddress::Primary) - .map_err(|e| eprintln!("Init failed: {:?}", e))?; - - let settings = SettingsBuilder::new() - .with_humidity_oversampling(OversamplingSetting::OS2x) - .with_pressure_oversampling(OversamplingSetting::OS4x) - .with_temperature_oversampling(OversamplingSetting::OS8x) - .with_temperature_filter(IIRFilterSize::Size3) - .with_gas_measurement(Duration::from_millis(1500), 320, 25) - .with_run_gas(true) - .build(); - dev.set_sensor_settings(&mut delayer, settings) - .map_err(|e| eprintln!("Setting sensor settings failed: {:?}", e))?; - - let client = Client::new(Url::parse(INFLUX_ADDRESS).unwrap(), INFLUX_DATABASE) - .set_authentication(INFLUX_USER, INFLUX_PASSWORD); - - loop { - dev.set_sensor_mode(&mut delayer, PowerMode::ForcedMode) - .map_err(|e| eprintln!("Setting sensor mode failed: {:?}", e))?; - let (data, state) = dev - .get_sensor_data(&mut delayer) - .map_err(|e| eprintln!("Retrieving sensor data failed: {:?}", e))?; - - println!("State {:?}", state); - println!("Temperature {}°C", data.temperature_celsius()); - println!("Pressure {}hPa", data.pressure_hpa()); - println!("Humidity {}%", data.humidity_percent()); - println!("Gas Resistence {}Ω", data.gas_resistance_ohm()); - - if state == FieldDataCondition::NewData { - let temperature_f = ipoint( - "temperature", - Value::Float(data.temperature_celsius() as f64), - ); - let pressure_f = ipoint("pressure", Value::Float(data.pressure_hpa() as f64)); - let humidity_f = ipoint("humidity", Value::Float(data.humidity_percent() as f64)); - let gas_f = ipoint( - "gasresistence", - Value::Float(data.gas_resistance_ohm() as f64), - ); - - let points = points!(temperature_f, pressure_f, humidity_f, gas_f); - - if let Err(e) = client - .write_points(points, Some(Precision::Seconds), None) - .await - { - eprintln!("Error: {:?}", e); - } - } - sleep(Duration::from_secs(10)).await; - } -} - -/// Sends a measured value to the influx database -fn ipoint(type_name: &str, value: Value) -> Point { - let point = Point::new("sensor") - .add_field("value", value) - .add_tag("id", Value::String("VMAC".to_string())) - .add_tag("name", Value::String("bme680".to_string())) - .add_tag("type", Value::String(type_name.to_string())); - - point -} diff --git a/examples/read-sensor-data/Cargo.toml b/examples/read-sensor-data/Cargo.toml new file mode 100644 index 0000000..2fbb188 --- /dev/null +++ b/examples/read-sensor-data/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "read-sensor-data" +version = "0.1.0" +edition = "2021" + +[dependencies] +bme680 = {path = "../../"} +env_logger = "0.11.5" +linux-embedded-hal = "0.4.0" +anyhow = { version = "1.0.80" } +log = "0.4" +embedded-hal = "=1.0.0" \ No newline at end of file diff --git a/examples/reading_temperature.rs b/examples/read-sensor-data/src/main.rs similarity index 72% rename from examples/reading_temperature.rs rename to examples/read-sensor-data/src/main.rs index 83b7aa1..e79e9f6 100644 --- a/examples/reading_temperature.rs +++ b/examples/read-sensor-data/src/main.rs @@ -1,23 +1,19 @@ -#![no_std] - -use bme680::*; -use core::result; +use bme680::i2c::Address; +use bme680::{Bme680, IIRFilterSize, OversamplingSetting, PowerMode, SettingsBuilder}; use core::time::Duration; -use embedded_hal::blocking::delay::DelayMs; -use embedded_hal::blocking::i2c; +use embedded_hal::delay::DelayNs; use linux_embedded_hal as hal; use linux_embedded_hal::Delay; use log::info; -fn main( -) -> result::Result<(), Error<::Error, ::Error>> -{ +// Please export RUST_LOG=info in order to see logs in the console. +fn main() -> Result<(), anyhow::Error> { env_logger::init(); let i2c = hal::I2cdev::new("/dev/i2c-1").unwrap(); let mut delayer = Delay {}; - let mut dev = Bme680::init(i2c, &mut delayer, I2CAddress::Primary)?; + let mut dev = Bme680::init(i2c, &mut delayer, Address::Primary)?; let mut delay = Delay {}; let settings = SettingsBuilder::new() @@ -25,15 +21,14 @@ fn main( .with_pressure_oversampling(OversamplingSetting::OS4x) .with_temperature_oversampling(OversamplingSetting::OS8x) .with_temperature_filter(IIRFilterSize::Size3) - .with_gas_measurement(Duration::from_millis(1500), 320, 25) - .with_temperature_offset(-2.2) + .with_gas_measurement(Duration::from_millis(1500), 320, 23) .with_run_gas(true) .build(); - let profile_dur = dev.get_profile_dur(&settings.0)?; + let profile_dur = dev.get_profile_duration(&settings.0)?; info!("Profile duration {:?}", profile_dur); info!("Setting sensor settings"); - dev.set_sensor_settings(&mut delayer, settings)?; + dev.set_sensor_settings(&mut delayer, &settings)?; info!("Setting forced power modes"); dev.set_sensor_mode(&mut delayer, PowerMode::ForcedMode)?; @@ -41,17 +36,17 @@ fn main( info!("Sensor settings: {:?}", sensor_settings); loop { - delay.delay_ms(5000u32); + let _ = delay.delay_ms(5000u32); let power_mode = dev.get_sensor_mode(); info!("Sensor power mode: {:?}", power_mode); info!("Setting forced power modes"); dev.set_sensor_mode(&mut delayer, PowerMode::ForcedMode)?; info!("Retrieving sensor data"); - let (data, _state) = dev.get_sensor_data(&mut delayer)?; + let (data, _state) = dev.get_measurement(&mut delayer)?; info!("Sensor Data {:?}", data); info!("Temperature {}°C", data.temperature_celsius()); info!("Pressure {}hPa", data.pressure_hpa()); info!("Humidity {}%", data.humidity_percent()); info!("Gas Resistence {}Ω", data.gas_resistance_ohm()); } -} +} \ No newline at end of file diff --git a/examples/res/influx_gas_resistance.png b/examples/res/influx_gas_resistance.png deleted file mode 100644 index ccb0f6f..0000000 Binary files a/examples/res/influx_gas_resistance.png and /dev/null differ diff --git a/examples/res/influx_humidity.png b/examples/res/influx_humidity.png deleted file mode 100644 index 973e4ac..0000000 Binary files a/examples/res/influx_humidity.png and /dev/null differ diff --git a/examples/res/influx_pressure.png b/examples/res/influx_pressure.png deleted file mode 100644 index 0e873c3..0000000 Binary files a/examples/res/influx_pressure.png and /dev/null differ diff --git a/examples/res/influx_temperature.png b/examples/res/influx_temperature.png deleted file mode 100644 index 09def9e..0000000 Binary files a/examples/res/influx_temperature.png and /dev/null differ diff --git a/src/calc.rs b/src/calc.rs deleted file mode 100644 index 96546f2..0000000 --- a/src/calc.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::CalibData; -use core::time::Duration; - -pub struct Calc {} - -impl Calc { - pub fn calc_heater_res(calib: &CalibData, amb_temp: i8, temp: u16) -> u8 { - // cap temperature - let temp = if temp <= 400 { temp } else { 400 }; - - let var1 = amb_temp as i32 * calib.par_gh3 as i32 / 1000i32 * 256i32; - let var2 = (calib.par_gh1 as i32 + 784i32) - * (((calib.par_gh2 as i32 + 154009i32) * temp as i32 * 5i32 / 100i32 + 3276800i32) - / 10i32); - let var3 = var1 + var2 / 2i32; - let var4 = var3 / (calib.res_heat_range as i32 + 4i32); - let var5 = 131i32 * calib.res_heat_val as i32 + 65536i32; - let heatr_res_x100 = (var4 / var5 - 250i32) * 34i32; - ((heatr_res_x100 + 50i32) / 100i32) as u8 - } - - pub fn calc_heater_dur(duration: Duration) -> u8 { - let mut factor: u8 = 0u8; - // TODO replace once https://github.com/rust-lang/rust/pull/50167 has been merged - const MILLIS_PER_SEC: u64 = 1_000; - const NANOS_PER_MILLI: u64 = 1_000_000; - let mut dur = (duration.as_secs() as u64 * MILLIS_PER_SEC) - + (duration.subsec_nanos() as u64 / NANOS_PER_MILLI); - if dur as i32 >= 0xfc0i32 { - 0xffu8 // Max duration - } else { - loop { - if dur as i32 <= 0x3fi32 { - break; - } - dur = (dur as i32 / 4i32) as u64; - factor = (factor as i32 + 1i32) as u8; - } - (dur as i32 + factor as i32 * 64i32) as u8 - } - } - - /// - /// * `calib` - Calibration data used during initalization - /// * `temp_adc` - /// * `temp_offset` - If set, the temperature t_fine will be increased by given - /// value in celsius. Temperature offset in Celsius, e.g. 4, -8, 1.25 - pub fn calc_temperature( - calib: &CalibData, - temp_adc: u32, - temp_offset: Option, - ) -> (i16, i32) { - let var1: i64 = (temp_adc as i64 >> 3) - ((calib.par_t1 as i64) << 1); - let var2: i64 = (var1 * (calib.par_t2 as i64)) >> 11; - let var3: i64 = ((var1 >> 1) * (var1 >> 1)) >> 12; - let var3: i64 = (var3 * ((calib.par_t3 as i64) << 4)) >> 14; - - let temp_offset = match temp_offset { - None => 0i32, - Some(offset) if offset == 0.0 => 0i32, - Some(offset) => { - let signum: i32 = if offset.gt(&0.0) { 1 } else { -1 }; - signum * (((((offset * 100.0) as i32).abs() << 8) - 128) / 5) - } - }; - - let t_fine: i32 = (var2 + var3) as i32 + temp_offset; - let calc_temp: i16 = (((t_fine * 5) + 128) >> 8) as i16; - (calc_temp, t_fine) - } - - pub fn calc_pressure(calib: &CalibData, t_fine: i32, pres_adc: u32) -> u32 { - let mut var1: i32 = (t_fine >> 1) - 64000; - let mut var2: i32 = ((((var1 >> 2) * (var1 >> 2)) >> 11) * calib.par_p6 as i32) >> 2; - var2 += (var1 * (calib.par_p5 as i32)) << 1; - var2 = (var2 >> 2i32) + ((calib.par_p4 as i32) << 16i32); - var1 = (((((var1 >> 2i32) * (var1 >> 2i32)) >> 13i32) * ((calib.par_p3 as i32) << 5i32)) - >> 3i32) - + ((calib.par_p2 as i32 * var1) >> 1i32); - var1 >>= 18i32; - var1 = ((32768i32 + var1) * calib.par_p1 as i32) >> 15i32; - let mut pressure_comp: i32 = 1048576u32.wrapping_sub(pres_adc) as i32; - pressure_comp = ((pressure_comp - (var2 >> 12i32)) as u32).wrapping_mul(3125u32) as i32; - if pressure_comp >= 0x40000000i32 { - pressure_comp = ((pressure_comp as u32).wrapping_div(var1 as u32) << 1i32) as i32; - } else { - pressure_comp = ((pressure_comp << 1i32) as u32).wrapping_div(var1 as u32) as i32; - } - var1 = (calib.par_p9 as i32 - * (((pressure_comp >> 3i32) * (pressure_comp >> 3i32)) >> 13i32)) - >> 12i32; - var2 = ((pressure_comp >> 2i32) * calib.par_p8 as i32) >> 13i32; - let var3: i32 = ((pressure_comp >> 8i32) - * (pressure_comp >> 8i32) - * (pressure_comp >> 8i32) - * calib.par_p10 as i32) - >> 17i32; - pressure_comp += (var1 + var2 + var3 + ((calib.par_p7 as i32) << 7i32)) >> 4i32; - pressure_comp as u32 - } - - pub fn calc_humidity(calib: &CalibData, t_fine: i32, hum_adc: u16) -> u32 { - let temp_scaled: i32 = (t_fine * 5i32 + 128i32) >> 8i32; - let var1: i32 = hum_adc as i32 - - calib.par_h1 as i32 * 16i32 - - ((temp_scaled * calib.par_h3 as i32 / 100i32) >> 1i32); - let var2: i32 = (calib.par_h2 as i32 - * (temp_scaled * calib.par_h4 as i32 / 100i32 - + ((temp_scaled * (temp_scaled * calib.par_h5 as i32 / 100i32)) >> 6i32) / 100i32 - + (1i32 << 14i32))) - >> 10i32; - let var3: i32 = var1 * var2; - let var4: i32 = (calib.par_h6 as i32) << 7i32; - let var4: i32 = (var4 + temp_scaled * calib.par_h7 as i32 / 100i32) >> 4i32; - let var5: i32 = ((var3 >> 14i32) * (var3 >> 14i32)) >> 10i32; - let var6: i32 = (var4 * var5) >> 1i32; - let mut calc_hum: i32 = (((var3 + var6) >> 10i32) * 1000i32) >> 12i32; - if calc_hum > 100000i32 { - calc_hum = 100000i32; - } else if calc_hum < 0i32 { - calc_hum = 0i32; - } - calc_hum as u32 - } - - pub fn calc_gas_resistance(calib: &CalibData, gas_res_adc: u16, gas_range: u8) -> u32 { - let lookup_table1: [u32; 16] = [ - 2147483647u32, - 2147483647u32, - 2147483647u32, - 2147483647u32, - 2147483647u32, - 2126008810u32, - 2147483647u32, - 2130303777u32, - 2147483647u32, - 2147483647u32, - 2143188679u32, - 2136746228u32, - 2147483647u32, - 2126008810u32, - 2147483647u32, - 2147483647u32, - ]; - let lookup_table2: [u32; 16] = [ - 4096000000u32, - 2048000000u32, - 1024000000u32, - 512000000u32, - 255744255u32, - 127110228u32, - 64000000u32, - 32258064u32, - 16016016u32, - 8000000u32, - 4000000u32, - 2000000u32, - 1, - 500000u32, - 250000u32, - 125000u32, - ]; - let var1: i64 = ((1340 + 5 * calib.range_sw_err as i64) - * lookup_table1[gas_range as usize] as i64) - >> 16; - let var2: u64 = (((gas_res_adc as i64) << 15) - 16777216 + var1) as u64; - let var3: i64 = (lookup_table2[gas_range as usize] as i64 * var1) >> 9; - let calc_gas_res: u32 = ((var3 + ((var2 as i64) >> 1i64)) / var2 as i64) as u32; - calc_gas_res - } -} diff --git a/src/calculation.rs b/src/calculation.rs new file mode 100644 index 0000000..45bcce5 --- /dev/null +++ b/src/calculation.rs @@ -0,0 +1,203 @@ +use crate::CalibrationData; +use core::time::Duration; + +/// Calculates values needed or returned by the BME80 Sensor. +pub struct Calculation {} + +static GAS_LOOKUP_TABLE_1: [u32; 16] = [ + 2147483647u32, + 2147483647u32, + 2147483647u32, + 2147483647u32, + 2147483647u32, + 2126008810u32, + 2147483647u32, + 2130303777u32, + 2147483647u32, + 2147483647u32, + 2143188679u32, + 2136746228u32, + 2147483647u32, + 2126008810u32, + 2147483647u32, + 2147483647u32, +]; + +static GAS_LOOKUP_TABLE_2: [u32; 16] = [ + 4096000000u32, + 2048000000u32, + 1024000000u32, + 512000000u32, + 255744255u32, + 127110228u32, + 64000000u32, + 32258064u32, + 16016016u32, + 8000000u32, + 4000000u32, + 2000000u32, + 1, + 500000u32, + 250000u32, + 125000u32, +]; + +impl Calculation { + /// Calculates and returns the sensor's heater resistance. + /// * `calibration_data` - The calibration data of the sensor. + /// * `ambient_temperature` - The ambient temperature. + /// * `heater_temperature` - The heater temperature. + pub fn heater_resistance( + calibration_data: &CalibrationData, + ambient_temperature: i8, + heater_temperature: u16, + ) -> u8 { + // cap temperature + let temp = if heater_temperature <= 400 { + heater_temperature + } else { + 400 + }; + + let var1 = ambient_temperature as i32 * calibration_data.par_gh3 as i32 / 1000i32 * 256i32; + let var2 = (calibration_data.par_gh1 as i32 + 784i32) + * (((calibration_data.par_gh2 as i32 + 154009i32) * temp as i32 * 5i32 / 100i32 + + 3276800i32) + / 10i32); + let var3 = var1 + var2 / 2i32; + let var4 = var3 / (calibration_data.res_heat_range as i32 + 4i32); + let var5 = 131i32 * calibration_data.res_heat_val as i32 + 65536i32; + let heatr_res_x100 = (var4 / var5 - 250i32) * 34i32; + ((heatr_res_x100 + 50i32) / 100i32) as u8 + } + + /// Calculates and returns the heater duration. + /// * `duration` The duration time + pub fn heater_duration(duration: Duration) -> u8 { + let mut factor: u8 = 0u8; + const MILLIS_PER_SEC: u64 = 1_000; + const NANOS_PER_MILLI: u64 = 1_000_000; + let mut dur = (duration.as_secs() * MILLIS_PER_SEC) + + (duration.subsec_nanos() as u64 / NANOS_PER_MILLI); + if dur as i32 >= 0xfc0i32 { + 0xffu8 // Max duration + } else { + loop { + if dur as i32 <= 0x3fi32 { + break; + } + dur = (dur as i32 / 4i32) as u64; + factor = (factor as i32 + 1i32) as u8; + } + (dur as i32 + factor as i32 * 64i32) as u8 + } + } + + /// Calculates and returns the sensor temperature. + /// * `calibration_data` - Calibration data used during initialization + /// * `temp_adc` - The temperature reading of the analog to digital converter. + /// * `temp_offset` - If set, the temperature t_fine will be increased by given + /// value in celsius. Temperature offset in Celsius, e.g. 4, -8, 1.25 + pub fn temperature( + calibration_data: &CalibrationData, + temp_adc: u32, + temp_offset: Option, + ) -> (i16, i32) { + let var1: i64 = (temp_adc as i64 >> 3) - ((calibration_data.par_t1 as i64) << 1); + let var2: i64 = (var1 * (calibration_data.par_t2 as i64)) >> 11; + let var3: i64 = ((var1 >> 1) * (var1 >> 1)) >> 12; + let var3: i64 = (var3 * ((calibration_data.par_t3 as i64) << 4)) >> 14; + + let temp_offset = match temp_offset { + None => 0i32, + Some(offset) if offset == 0.0 => 0i32, + Some(offset) => { + let signum: i32 = if offset.gt(&0.0) { 1 } else { -1 }; + signum * (((((offset * 100.0) as i32).abs() << 8) - 128) / 5) + } + }; + + let t_fine: i32 = (var2 + var3) as i32 + temp_offset; + let calc_temp: i16 = (((t_fine * 5) + 128) >> 8) as i16; + (calc_temp, t_fine) + } + + /// Calculates and returns the pressure of the sensor. + /// + /// * `t_fine` - The resolution temperature obtained after applying calibration data. + /// * `pressure_adc` - The pressure value as returned by the analog to digital converter. + pub fn pressure(calibration_data: &CalibrationData, t_fine: i32, pressure_adc: u32) -> u32 { + let mut var1: i32 = (t_fine >> 1) - 64000; + let mut var2: i32 = + ((((var1 >> 2) * (var1 >> 2)) >> 11) * calibration_data.par_p6 as i32) >> 2; + var2 += (var1 * (calibration_data.par_p5 as i32)) << 1; + var2 = (var2 >> 2i32) + ((calibration_data.par_p4 as i32) << 16i32); + var1 = (((((var1 >> 2i32) * (var1 >> 2i32)) >> 13i32) + * ((calibration_data.par_p3 as i32) << 5i32)) + >> 3i32) + + ((calibration_data.par_p2 as i32 * var1) >> 1i32); + var1 >>= 18i32; + var1 = ((32768i32 + var1) * calibration_data.par_p1 as i32) >> 15i32; + let mut pressure_comp: i32 = 1048576u32.wrapping_sub(pressure_adc) as i32; + pressure_comp = ((pressure_comp - (var2 >> 12i32)) as u32).wrapping_mul(3125u32) as i32; + if pressure_comp >= 0x40000000i32 { + pressure_comp = ((pressure_comp as u32).wrapping_div(var1 as u32) << 1i32) as i32; + } else { + pressure_comp = ((pressure_comp << 1i32) as u32).wrapping_div(var1 as u32) as i32; + } + var1 = (calibration_data.par_p9 as i32 + * (((pressure_comp >> 3i32) * (pressure_comp >> 3i32)) >> 13i32)) + >> 12i32; + var2 = ((pressure_comp >> 2i32) * calibration_data.par_p8 as i32) >> 13i32; + let var3: i32 = ((pressure_comp >> 8i32) + * (pressure_comp >> 8i32) + * (pressure_comp >> 8i32) + * calibration_data.par_p10 as i32) + >> 17i32; + pressure_comp += (var1 + var2 + var3 + ((calibration_data.par_p7 as i32) << 7i32)) >> 4i32; + pressure_comp as u32 + } + + /// Calculates and returns the humidity of the sensor. + /// + /// * `t_fine` - The resolution temperature obtained after applying calibration data. + /// * `humidity_adc` - The humidity value as returned by the analog to digital converter. + pub fn humidity(calibration_data: &CalibrationData, t_fine: i32, humidity_adc: u16) -> u32 { + let temp_scaled: i32 = (t_fine * 5i32 + 128i32) >> 8i32; + let var1: i32 = humidity_adc as i32 + - calibration_data.par_h1 as i32 * 16i32 + - ((temp_scaled * calibration_data.par_h3 as i32 / 100i32) >> 1i32); + let var2: i32 = (calibration_data.par_h2 as i32 + * (temp_scaled * calibration_data.par_h4 as i32 / 100i32 + + ((temp_scaled * (temp_scaled * calibration_data.par_h5 as i32 / 100i32)) + >> 6i32) + / 100i32 + + (1i32 << 14i32))) + >> 10i32; + let var3: i32 = var1 * var2; + let var4: i32 = (calibration_data.par_h6 as i32) << 7i32; + let var4: i32 = (var4 + temp_scaled * calibration_data.par_h7 as i32 / 100i32) >> 4i32; + let var5: i32 = ((var3 >> 14i32) * (var3 >> 14i32)) >> 10i32; + let var6: i32 = (var4 * var5) >> 1i32; + let calc_hum: i32 = (((var3 + var6) >> 10i32) * 1000i32) >> 12i32; + calc_hum.clamp(0, 100000) as u32 + } + + /// Calculates and returns the gas resistance. + /// + /// * `gas_resistance_adc` - The gas resistance reading from the analog to digital converter. + /// * `gas_range` - The lookup table gas range. + pub fn gas_resistance( + calibration_data: &CalibrationData, + gas_resistance_adc: u16, + gas_range: u8, + ) -> u32 { + let var1: i64 = ((1340 + 5 * calibration_data.range_sw_err as i64) + * GAS_LOOKUP_TABLE_1[gas_range as usize] as i64) + >> 16; + let var2: u64 = (((gas_resistance_adc as i64) << 15) - 16777216 + var1) as u64; + let var3: i64 = (GAS_LOOKUP_TABLE_2[gas_range as usize] as i64 * var1) >> 9; + let calc_gas_res: u32 = ((var3 + ((var2 as i64) >> 1i64)) / var2 as i64) as u32; + calc_gas_res + } +} diff --git a/src/i2c.rs b/src/i2c.rs new file mode 100644 index 0000000..a610b0a --- /dev/null +++ b/src/i2c.rs @@ -0,0 +1,105 @@ +use anyhow::anyhow; +use core::fmt::{Display, Formatter}; +use embedded_hal::i2c::I2c; + +/// +/// Represents the I2C address of the BME680 Sensor. +/// +#[derive(Debug, Clone, Copy, Default)] +pub enum Address { + /// Primary Address 0x76 + #[default] + Primary, + /// Secondary Address 0x77 + Secondary, + /// Alternative address + Other(u8), +} + +impl Address { + pub fn addr(&self) -> u8 { + match &self { + Address::Primary => 0x76u8, + Address::Secondary => 0x77u8, + Address::Other(addr) => *addr, + } + } +} + +impl Display for Address { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "Address={:#01x}", self.addr()) + } +} + +/// I2CUtility is a simple wrapper over the I2c trait to make reading and writing data easier. +pub(crate) struct I2CUtility {} + +impl I2CUtility { + /// Reads a byte from the I2C bus. + pub fn read_byte( + i2c_handle: &mut I2C, + device_address: u8, + register_address: u8, + ) -> Result { + let mut buf = [0; 1]; + + i2c_handle + .write(device_address, &[register_address]) + .map_err(|e| { + anyhow!( + "Failed to write a byte {} to device {}: {:?}", + register_address, + device_address, + e + ) + })?; + + match i2c_handle.read(device_address, &mut buf) { + Ok(()) => Ok(buf[0]), + Err(_e) => Err(anyhow!( + "Failed to read byte {} from device {}", + register_address, + device_address + )), + } + } + + /// Reads bytes from the I2C bus. + pub fn read_bytes( + i2c_handle: &mut I2C, + device_address: u8, + register_address: u8, + buffer: &mut [u8], + ) -> Result<(), anyhow::Error> { + i2c_handle + .write(device_address, &[register_address]) + .map_err(|_e| { + anyhow!( + "Failed to write a byte {} from device {}", + register_address, + device_address + ) + })?; + + match i2c_handle.read(device_address, buffer) { + Ok(()) => Ok(()), + Err(_e) => Err(anyhow!( + "Failed to read bytes from register {} and device {}", + register_address, + device_address + )), + } + } + + /// Writes bytes to the I2C bus. + pub fn write_bytes( + i2c_handle: &mut I2C, + device_address: u8, + buffer: &[u8], + ) -> Result<(), anyhow::Error> { + i2c_handle + .write(device_address, buffer) + .map_err(|_e| anyhow!("Failed to write bytes to address {}", device_address)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 61e007a..ff0a5a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,104 +3,32 @@ //! //! The library uses the embedded-hal crate to abstract reading and writing via I²C. //! In the examples you can find a demo how to use the library in Linux using the linux-embedded-hal crate (e.g. on a RPI). -//! ```no_run - -//! extern crate bme680; -//! extern crate embedded_hal; -//! // Note that you'll have to import your board crates types corresponding to -//! // Delay and I2cdev. -//! -//! use bme680::*; -//! use embedded_hal::blocking::i2c; -//! use hal::*; -//! use std::result; -//! use std::time::Duration; -//! use std::thread::sleep; -//! -//! # mod hal { -//! # use super::*; -//! # use embedded_hal::blocking::delay; -//! # -//! # #[derive(Debug)] -//! # pub struct Delay {} -//! # -//! # impl delay::DelayMs for Delay { -//! # fn delay_ms(&mut self, _ms: u8) {} -//! # } -//! # -//! # #[derive(Debug)] -//! # pub enum I2CError {} -//! # -//! # pub struct I2cdev {} -//! # -//! # impl i2c::Write for I2cdev { -//! # type Error = I2CError; -//! # -//! # fn write<'w>(&mut self, addr: u8, bytes: &'w [u8]) -> result::Result<(), Self::Error> { -//! # Ok(()) -//! # } -//! # } -//! # -//! # impl i2c::Read for I2cdev { -//! # type Error = I2CError; -//! # -//! # fn read<'w>(&mut self, addr: u8, bytes: &'w mut [u8]) -> result::Result<(), Self::Error> { -//! # Ok(()) -//! # } -//! # } -//! # } -//! -//! fn main() -> result::Result<(), Error<::Error, ::Error>> -//! { -//! // Initialize device -//! let i2c = I2cdev {}; // Your I2C device construction will look different, perhaps using I2cdev::new(..) -//! let mut delayer = Delay {}; // Your Delay construction will look different, perhaps using Delay::new(..) -//! let mut dev = Bme680::init(i2c, &mut delayer, I2CAddress::Primary)?; -//! let settings = SettingsBuilder::new() -//! .with_humidity_oversampling(OversamplingSetting::OS2x) -//! .with_pressure_oversampling(OversamplingSetting::OS4x) -//! .with_temperature_oversampling(OversamplingSetting::OS8x) -//! .with_temperature_filter(IIRFilterSize::Size3) -//! .with_gas_measurement(Duration::from_millis(1500), 320, 25) -//! .with_run_gas(true) -//! .build(); -//! dev.set_sensor_settings(&mut delayer, settings)?; -//! let profile_duration = dev.get_profile_dur(&settings.0)?; -//! -//! // Read sensor data -//! dev.set_sensor_mode(&mut delayer, PowerMode::ForcedMode)?; -//! sleep(profile_duration); -//! let (data, _state) = dev.get_sensor_data(&mut delayer)?; -//! -//! println!("Temperature {}°C", data.temperature_celsius()); -//! println!("Pressure {}hPa", data.pressure_hpa()); -//! println!("Humidity {}%", data.humidity_percent()); -//! println!("Gas Resistence {}Ω", data.gas_resistance_ohm()); -//! -//! Ok(()) -//! } -//! ``` #![no_std] #![forbid(unsafe_code)] pub use self::settings::{ - DesiredSensorSettings, GasSett, IIRFilterSize, OversamplingSetting, SensorSettings, Settings, - SettingsBuilder, TphSett, + DesiredSensorSettings, GasSettings, IIRFilterSize, OversamplingSetting, SensorSettings, + Settings, SettingsBuilder, TemperatureSettings, }; +use core::cell::RefCell; -mod calc; +mod calculation; +pub mod i2c; mod settings; -use crate::calc::Calc; -use crate::hal::delay::blocking::DelayMs; -use crate::hal::i2c::blocking::{Read, Write}; +use crate::calculation::Calculation; +use crate::hal::delay::DelayNs; +use crate::hal::i2c::I2c; +use anyhow::anyhow; +use core::marker::PhantomData; +use core::ops::DerefMut; use core::time::Duration; -use core::{marker::PhantomData, result}; use embedded_hal as hal; use log::{debug, error, info}; +use i2c::{Address, I2CUtility}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -167,42 +95,8 @@ const BME680_HEAT_STAB_MSK: u8 = 0x10; const BME680_TMP_BUFFER_LENGTH: usize = 40; const BME680_REG_BUFFER_LENGTH: usize = 6; -/// All possible errors in this crate -#[derive(Debug)] -pub enum Error { - /// - /// aka BME680_E_COM_FAIL - /// - I2CWrite(W), - I2CRead(R), - Delay, - /// - /// aka BME680_E_DEV_NOT_FOUND - /// - DeviceNotFound, - /// - /// aka BME680_E_INVALID_LENGTH - /// - InvalidLength, - /// - /// Warning aka BME680_W_DEFINE_PWR_MODE - /// - DefinePwrMode, - /// - /// Warning aka BME680_W_DEFINE_PWR_MODE - /// - NoNewData, - /// - /// Warning Boundary Check - /// - BoundaryCheckFailure(&'static str), -} - -/// Abbreviates `std::result::Result` type -pub type Result = result::Result>; - /// -/// Power mode settings +/// Power mode settings of the sensor. /// #[derive(Debug, PartialEq, Clone, Copy)] pub enum PowerMode { @@ -211,7 +105,6 @@ pub enum PowerMode { } impl PowerMode { - // TODO replace with TryFrom once stabilized fn from(power_mode: u8) -> Self { match power_mode { BME680_SLEEP_MODE => PowerMode::SleepMode, @@ -220,6 +113,7 @@ impl PowerMode { } } + /// Retrieves the power mode value. fn value(&self) -> u8 { match self { PowerMode::SleepMode => BME680_SLEEP_MODE, @@ -228,44 +122,10 @@ impl PowerMode { } } -/// -/// I2C Slave Address -/// To determine the slave address of your device you can use `i2cdetect -y 1` on linux. -/// The 7-bit device address is 111011x. The 6 MSB bits are fixed. -/// The last bit is changeable by SDO value and can be changed during operation. -/// Connecting SDO to GND results in slave address 1110110 (0x76); connecting it to V DDIO results in slave -/// address 1110111 (0x77), which is the same as BMP280’s I2C address. -/// -#[derive(Debug, Clone, Copy)] -pub enum I2CAddress { - /// Primary Slave Address 0x76 - Primary, - /// Secondary Slave Address 0x77 - Secondary, - /// Alternative address - Other(u8), -} - -impl I2CAddress { - pub fn addr(&self) -> u8 { - match &self { - I2CAddress::Primary => 0x76u8, - I2CAddress::Secondary => 0x77u8, - I2CAddress::Other(addr) => *addr, - } - } -} - -impl Default for I2CAddress { - fn default() -> I2CAddress { - I2CAddress::Primary - } -} - -/// Calibration data used during initalization +/// Calibration data used during initialization #[derive(Debug, Default, Copy)] #[repr(C)] -pub struct CalibData { +pub struct CalibrationData { pub par_h1: u16, pub par_h2: u16, pub par_h3: i8, @@ -294,7 +154,7 @@ pub struct CalibData { pub range_sw_err: u8, } -impl Clone for CalibData { +impl Clone for CalibrationData { fn clone(&self) -> Self { *self } @@ -310,7 +170,7 @@ pub struct FieldData { /// Index of heater profile used gas_index: u8, /// Measurement index - meas_index: u8, + measurement_index: u8, temperature: i16, pressure: u32, humidity: u32, @@ -370,156 +230,127 @@ pub enum FieldDataCondition { Unchanged, } -struct I2CUtil {} - -impl I2CUtil { - pub fn read_byte( - i2c: &mut I2C, - dev_id: u8, - reg_addr: u8, - ) -> Result::Error, ::Error> - where - I2C: Read + Write, - { - let mut buf = [0; 1]; - - i2c.write(dev_id, &[reg_addr]).map_err(Error::I2CWrite)?; - - match i2c.read(dev_id, &mut buf) { - Ok(()) => Ok(buf[0]), - Err(e) => Err(Error::I2CRead(e)), - } - } - - pub fn read_bytes( - i2c: &mut I2C, - dev_id: u8, - reg_addr: u8, - buf: &mut [u8], - ) -> Result<(), ::Error, ::Error> - where - I2C: Read + Write, - { - i2c.write(dev_id, &[reg_addr]).map_err(Error::I2CWrite)?; - - match i2c.read(dev_id, buf) { - Ok(()) => Ok(()), - Err(e) => Err(Error::I2CRead(e)), - } - } -} - /// Driver for the BME680 environmental sensor #[repr(C)] pub struct Bme680 { - i2c: I2C, + i2c_bus_handle: RefCell, delay: PhantomData, - dev_id: I2CAddress, - calib: CalibData, - // TODO remove ? as it may not reflect the state of the device - tph_sett: TphSett, - // TODO remove ? as it may not reflect the state of the device - gas_sett: GasSett, - // TODO remove ? as it may not reflect the state of the device + device_address: Address, + calibration_data: CalibrationData, + temperature_offset: f32, power_mode: PowerMode, } -fn boundary_check( +/// Checks if an u8 value is within the boundary and returns a Result containing an error +/// if the value is not within the boundary. +/// +/// * `value` - The value to check for +/// * `value_name` - The name of the value +/// * `min` - The minimum boundary. +/// * `max` - The maximum boundary. +fn boundary_check_u8( value: Option, value_name: &'static str, min: u8, max: u8, -) -> Result::Error, ::Error> -where - I2C: Read + Write, -{ - let value = value.ok_or(Error::BoundaryCheckFailure(value_name))?; +) -> Result { + let value = value.ok_or(anyhow!("Boundary check failed for {}", value_name))?; if value < min { const MIN: &str = "Boundary check failure, value exceeds maximum"; error!("{}, value name: {}", MIN, value_name); - return Err(Error::BoundaryCheckFailure(MIN)); + return Err(anyhow!( + "Failed MIN={} boundary check for {}", + MIN, + value_name + )); } if value > max { const MAX: &str = "Boundary check, value exceeds minimum"; error!("{}, value name: {}", MAX, value_name); - return Err(Error::BoundaryCheckFailure(MAX)); + return Err(anyhow!( + "Failed MAX={} boundary check for {}", + MAX, + value_name + )); } Ok(value) } -impl Bme680 +impl Bme680 where - D: DelayMs, - I2C: Read + Write, + D: DelayNs, + I2C: I2c, { + /// Sends the soft reset command to the chip. pub fn soft_reset( - i2c: &mut I2C, + i2c_handle: &mut I2C, delay: &mut D, - dev_id: I2CAddress, - ) -> Result<(), ::Error, ::Error> { + device_address: Address, + ) -> Result<(), anyhow::Error> { let tmp_buff: [u8; 2] = [BME680_SOFT_RESET_ADDR, BME680_SOFT_RESET_CMD]; - - i2c.write(dev_id.addr(), &tmp_buff) - .map_err(Error::I2CWrite)?; - - delay - .delay_ms(BME680_RESET_PERIOD) - .map_err(|_| Error::Delay)?; + I2CUtility::write_bytes(i2c_handle, device_address.addr(), &tmp_buff)?; + delay.delay_ms(BME680_RESET_PERIOD as u32); Ok(()) } + /// Initializes the BME680 sensor with an I2C handle. pub fn init( - mut i2c: I2C, + mut i2c_handle: I2C, delay: &mut D, - dev_id: I2CAddress, - ) -> Result, ::Error, ::Error> { - Bme680::soft_reset(&mut i2c, delay, dev_id)?; + device_address: Address, + ) -> Result, anyhow::Error> { + Bme680::soft_reset(&mut i2c_handle, delay, device_address)?; debug!("Reading chip id"); /* Soft reset to restore it to default values*/ - let chip_id = I2CUtil::read_byte::(&mut i2c, dev_id.addr(), BME680_CHIP_ID_ADDR)?; + let chip_id = I2CUtility::read_byte::( + &mut i2c_handle, + device_address.addr(), + BME680_CHIP_ID_ADDR, + )?; debug!("Chip id: {}", chip_id); if chip_id == BME680_CHIP_ID { - debug!("Reading calib data"); - let calib = Bme680::::get_calib_data::(&mut i2c, dev_id)?; - debug!("Calib data {:?}", calib); - let dev = Bme680 { - i2c, + debug!("Reading calibration data"); + let calibration_data = + Bme680::::get_calibration_data::(&mut i2c_handle, device_address)?; + debug!("Calibration data {:?}", calibration_data); + let device = Bme680 { + i2c_bus_handle: RefCell::new(i2c_handle), delay: PhantomData, - dev_id, - calib, + device_address, + calibration_data, + temperature_offset: 0.0, power_mode: PowerMode::ForcedMode, - tph_sett: Default::default(), - gas_sett: Default::default(), }; info!("Finished device init"); - Ok(dev) + Ok(device) } else { error!("Device does not match chip id {}", BME680_CHIP_ID); - Err(Error::DeviceNotFound) + Err(anyhow!("Device address not found")) } } - fn bme680_set_regs( - &mut self, - reg: &[(u8, u8)], - ) -> Result<(), ::Error, ::Error> { - if reg.is_empty() || reg.len() > (BME680_TMP_BUFFER_LENGTH / 2) as usize { - return Err(Error::InvalidLength); + /// Sets the sensor registers. + fn bme680_set_registers(&mut self, registers: &[(u8, u8)]) -> Result<(), anyhow::Error> { + if registers.is_empty() || registers.len() > (BME680_TMP_BUFFER_LENGTH / 2) { + return Err(anyhow!("Invalid register length!")); } - for (reg_addr, reg_data) in reg { - let tmp_buff: [u8; 2] = [*reg_addr, *reg_data]; + for (register_address, register_data) in registers { + let buffer: [u8; 2] = [*register_address, *register_data]; debug!( - "Setting register reg: {:?} tmp_buf: {:?}", - reg_addr, tmp_buff + "Setting register reg: {:?} buffer: {:?}", + register_address, buffer ); - self.i2c - .write(self.dev_id.addr(), &tmp_buff) - .map_err(Error::I2CWrite)?; + + I2CUtility::write_bytes( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), + &buffer, + )?; } Ok(()) @@ -529,18 +360,23 @@ where pub fn set_sensor_settings( &mut self, delay: &mut D, - settings: Settings, - ) -> Result<(), ::Error, ::Error> { + settings: &Settings, + ) -> Result<(), anyhow::Error> { let (sensor_settings, desired_settings) = settings; - let tph_sett = sensor_settings.tph_sett; - let gas_sett = sensor_settings.gas_sett; + let tph_sett = sensor_settings.temperature_settings; + let gas_sett = sensor_settings.gas_settings; + + self.temperature_offset = sensor_settings + .temperature_settings + .temperature_offset + .unwrap_or(0f32); let mut reg: [(u8, u8); BME680_REG_BUFFER_LENGTH] = [(0, 0); BME680_REG_BUFFER_LENGTH]; let intended_power_mode = self.power_mode; if desired_settings.contains(DesiredSensorSettings::GAS_MEAS_SEL) { debug!("GAS_MEAS_SEL: true"); - self.set_gas_config(gas_sett)?; + self.set_gas_settings(gas_sett)?; } let power_mode = self.power_mode; @@ -548,25 +384,28 @@ where let mut element_index = 0; // Selecting the filter - if desired_settings.contains(DesiredSensorSettings::FILTER_SEL) { - let mut data = - I2CUtil::read_byte(&mut self.i2c, self.dev_id.addr(), BME680_CONF_ODR_FILT_ADDR)?; + if desired_settings.contains(DesiredSensorSettings::FILTER_SIZE_SEL) { + let mut data = I2CUtility::read_byte( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), + BME680_CONF_ODR_FILT_ADDR, + )?; debug!("FILTER_SEL: true"); data = (data as i32 & !0x1ci32 - | (tph_sett.filter.unwrap_or(IIRFilterSize::Size0) as i32) << 2i32 & 0x1ci32) + | (tph_sett.filter_size.unwrap_or(IIRFilterSize::Size0) as i32) << 2i32 & 0x1ci32) as u8; reg[element_index] = (BME680_CONF_ODR_FILT_ADDR, data); element_index += 1; } - if desired_settings.contains(DesiredSensorSettings::HCNTRL_SEL) { + if desired_settings.contains(DesiredSensorSettings::HUMIDITY_CONTROL_SEL) { debug!("HCNTRL_SEL: true"); let gas_sett_heatr_ctrl = - boundary_check::(gas_sett.heatr_ctrl, "GasSett.heatr_ctrl", 0x0u8, 0x8u8)?; - let mut data = I2CUtil::read_byte( - &mut self.i2c, - self.dev_id.addr(), + boundary_check_u8(gas_sett.heater_control, "GasSett.heatr_ctrl", 0x0u8, 0x8u8)?; + let mut data = I2CUtility::read_byte( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), BME680_CONF_HEAT_CTRL_ADDR, )?; data = (data as i32 & !0x8i32 | gas_sett_heatr_ctrl as i32 & 0x8) as u8; @@ -578,13 +417,16 @@ where if desired_settings .contains(DesiredSensorSettings::OST_SEL | DesiredSensorSettings::OSP_SEL) { - let mut data = - I2CUtil::read_byte(&mut self.i2c, self.dev_id.addr(), BME680_CONF_T_P_MODE_ADDR)?; + let mut data = I2CUtility::read_byte( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), + BME680_CONF_T_P_MODE_ADDR, + )?; if desired_settings.contains(DesiredSensorSettings::OST_SEL) { debug!("OST_SEL: true"); - let tph_sett_os_temp = boundary_check::( - tph_sett.os_temp.map(|x| x as u8), + let tph_sett_os_temp = boundary_check_u8( + tph_sett.temperature_oversampling.map(|x| x as u8), "TphSett.os_temp", 0, 5, @@ -594,7 +436,9 @@ where if desired_settings.contains(DesiredSensorSettings::OSP_SEL) { debug!("OSP_SEL: true"); - let tph_sett_os_pres = tph_sett.os_temp.expect("OS TEMP"); + let tph_sett_os_pres = tph_sett + .temperature_oversampling + .unwrap_or(OversamplingSetting::OS1x); data = (data as i32 & !0x1ci32 | (tph_sett_os_pres as i32) << 2i32 & 0x1ci32) as u8; } reg[element_index] = (BME680_CONF_T_P_MODE_ADDR, data); @@ -604,10 +448,17 @@ where // Selecting humidity oversampling for the sensor if desired_settings.contains(DesiredSensorSettings::OSH_SEL) { debug!("OSH_SEL: true"); - let tph_sett_os_hum = - boundary_check::(tph_sett.os_hum.map(|x| x as u8), "TphSett.os_hum", 0, 5)?; - let mut data = - I2CUtil::read_byte(&mut self.i2c, self.dev_id.addr(), BME680_CONF_OS_H_ADDR)?; + let tph_sett_os_hum = boundary_check_u8( + tph_sett.humidity_oversampling.map(|x| x as u8), + "TphSett.os_hum", + 0, + 5, + )?; + let mut data = I2CUtility::read_byte( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), + BME680_CONF_OS_H_ADDR, + )?; data = (data as i32 & !0x7i32 | tph_sett_os_hum as i32 & 0x7i32) as u8; reg[element_index] = (BME680_CONF_OS_H_ADDR, data); element_index += 1; @@ -617,23 +468,23 @@ where if desired_settings .contains(DesiredSensorSettings::RUN_GAS_SEL | DesiredSensorSettings::NBCONV_SEL) { - let mut data = I2CUtil::read_byte( - &mut self.i2c, - self.dev_id.addr(), + let mut data = I2CUtility::read_byte( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), BME680_CONF_ODR_RUN_GAS_NBC_ADDR, )?; if desired_settings.contains(DesiredSensorSettings::RUN_GAS_SEL) { debug!("RUN_GAS_SEL: true"); data = (data as i32 & !0x10i32 - | (gas_sett.run_gas_measurement as i32) << 4i32 & 0x10i32) + | (gas_sett.enable_gas_measurement as i32) << 4i32 & 0x10i32) as u8; } if desired_settings.contains(DesiredSensorSettings::NBCONV_SEL) { debug!("NBCONV_SEL: true"); let gas_sett_nb_conv = - boundary_check::(Some(gas_sett.nb_conv), "GasSett.nb_conv", 0, 10)?; + boundary_check_u8(Some(gas_sett.nb_conv), "GasSett.nb_conv", 0, 10)?; data = (data as i32 & !0xfi32 | gas_sett_nb_conv as i32 & 0xfi32) as u8; } @@ -641,11 +492,10 @@ where element_index += 1; } - self.bme680_set_regs(®[0..element_index])?; + self.bme680_set_registers(®[0..element_index])?; // Restore previous intended power mode self.power_mode = intended_power_mode; - self.tph_sett = tph_sett; Ok(()) } @@ -657,20 +507,24 @@ where pub fn get_sensor_settings( &mut self, desired_settings: DesiredSensorSettings, - ) -> Result::Error, ::Error> { + ) -> Result { let reg_addr: u8 = 0x70u8; let mut data_array: [u8; BME680_REG_BUFFER_LENGTH] = [0; BME680_REG_BUFFER_LENGTH]; let mut sensor_settings: SensorSettings = Default::default(); - sensor_settings.tph_sett.temperature_offset = self.tph_sett.temperature_offset; - I2CUtil::read_bytes(&mut self.i2c, self.dev_id.addr(), reg_addr, &mut data_array)?; + I2CUtility::read_bytes( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), + reg_addr, + &mut data_array, + )?; if desired_settings.contains(DesiredSensorSettings::GAS_MEAS_SEL) { - sensor_settings.gas_sett = self.get_gas_config()?; + sensor_settings.gas_settings = self.get_gas_settings()?; } - if desired_settings.contains(DesiredSensorSettings::FILTER_SEL) { - sensor_settings.tph_sett.filter = Some(IIRFilterSize::from_u8( + if desired_settings.contains(DesiredSensorSettings::FILTER_SIZE_SEL) { + sensor_settings.temperature_settings.filter_size = Some(IIRFilterSize::from_u8( ((data_array[5usize] as i32 & 0x1ci32) >> 2i32) as u8, )); } @@ -680,24 +534,29 @@ where { let os_temp: u8 = ((data_array[4usize] as i32 & 0xe0i32) >> 5i32) as u8; let os_pres: u8 = ((data_array[4usize] as i32 & 0x1ci32) >> 2i32) as u8; - sensor_settings.tph_sett.os_temp = Some(OversamplingSetting::from_u8(os_temp)); - sensor_settings.tph_sett.os_pres = Some(OversamplingSetting::from_u8(os_pres)); + sensor_settings + .temperature_settings + .temperature_oversampling = Some(OversamplingSetting::from_u8(os_temp)); + sensor_settings.temperature_settings.pressure_oversampling = + Some(OversamplingSetting::from_u8(os_pres)); } if desired_settings.contains(DesiredSensorSettings::OSH_SEL) { let os_hum: u8 = (data_array[2usize] as i32 & 0x7i32) as u8; - sensor_settings.tph_sett.os_hum = Some(OversamplingSetting::from_u8(os_hum)); + sensor_settings.temperature_settings.humidity_oversampling = + Some(OversamplingSetting::from_u8(os_hum)); } - if desired_settings.contains(DesiredSensorSettings::HCNTRL_SEL) { - sensor_settings.gas_sett.heatr_ctrl = Some((data_array[0usize] as i32 & 0x8i32) as u8); + if desired_settings.contains(DesiredSensorSettings::HUMIDITY_CONTROL_SEL) { + sensor_settings.gas_settings.heater_control = + Some((data_array[0usize] as i32 & 0x8i32) as u8); } if desired_settings .contains(DesiredSensorSettings::RUN_GAS_SEL | DesiredSensorSettings::NBCONV_SEL) { - sensor_settings.gas_sett.nb_conv = (data_array[1usize] as i32 & 0xfi32) as u8; - sensor_settings.gas_sett.run_gas_measurement = + sensor_settings.gas_settings.nb_conv = (data_array[1usize] as i32 & 0xfi32) as u8; + sensor_settings.gas_settings.enable_gas_measurement = ((data_array[1usize] as i32 & 0x10i32) >> 4i32) == 0; } @@ -713,295 +572,305 @@ where &mut self, delay: &mut D, target_power_mode: PowerMode, - ) -> Result<(), ::Error, ::Error> { - let mut tmp_pow_mode: u8; - let mut current_power_mode: PowerMode; + ) -> Result<(), anyhow::Error> { + let mut power_mode_byte: u8; + let mut power_mode: PowerMode; // Call repeatedly until in sleep loop { - tmp_pow_mode = - I2CUtil::read_byte(&mut self.i2c, self.dev_id.addr(), BME680_CONF_T_P_MODE_ADDR)?; + power_mode_byte = I2CUtility::read_byte( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), + BME680_CONF_T_P_MODE_ADDR, + )?; // Put to sleep before changing mode - current_power_mode = PowerMode::from(tmp_pow_mode & BME680_MODE_MSK); + power_mode = PowerMode::from(power_mode_byte & BME680_MODE_MSK); - debug!("Current power mode: {:?}", current_power_mode); + debug!("Current power mode: {:?}", power_mode); - if current_power_mode != PowerMode::SleepMode { + if power_mode != PowerMode::SleepMode { // Set to sleep - tmp_pow_mode &= !BME680_MODE_MSK; - debug!("Setting to sleep tmp_pow_mode: {}", tmp_pow_mode); - self.bme680_set_regs(&[(BME680_CONF_T_P_MODE_ADDR, tmp_pow_mode)])?; - delay - .delay_ms(BME680_POLL_PERIOD_MS) - .map_err(|_| Error::Delay)?; + power_mode_byte &= !BME680_MODE_MSK; + debug!("Setting to sleep tmp_pow_mode: {}", power_mode_byte); + self.bme680_set_registers(&[(BME680_CONF_T_P_MODE_ADDR, power_mode_byte)])?; + delay.delay_ms(BME680_POLL_PERIOD_MS as u32); } else { - // TODO do while in Rust? break; } } // Already in sleep if target_power_mode != PowerMode::SleepMode { - tmp_pow_mode = tmp_pow_mode & !BME680_MODE_MSK | target_power_mode.value(); - debug!("Already in sleep Target power mode: {}", tmp_pow_mode); - self.bme680_set_regs(&[(BME680_CONF_T_P_MODE_ADDR, tmp_pow_mode)])?; + power_mode_byte = power_mode_byte & !BME680_MODE_MSK | target_power_mode.value(); + debug!("Already in sleep Target power mode: {}", power_mode_byte); + self.bme680_set_registers(&[(BME680_CONF_T_P_MODE_ADDR, power_mode_byte)])?; } Ok(()) } /// Retrieve current sensor power mode via registers - pub fn get_sensor_mode( - &mut self, - ) -> Result::Error, ::Error> { - let regs = - I2CUtil::read_byte(&mut self.i2c, self.dev_id.addr(), BME680_CONF_T_P_MODE_ADDR)?; - let mode = regs & BME680_MODE_MSK; + pub fn get_sensor_mode(&mut self) -> Result { + let registers = I2CUtility::read_byte( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), + BME680_CONF_T_P_MODE_ADDR, + )?; + let mode = registers & BME680_MODE_MSK; Ok(PowerMode::from(mode)) } - pub fn bme680_set_profile_dur(&mut self, tph_sett: TphSett, duration: Duration) { - let os_to_meas_cycles: [u8; 6] = [0u8, 1u8, 2u8, 4u8, 8u8, 16u8]; - // TODO check if the following unwrap_ors do not change behaviour - // TODO replace once https://github.com/rust-lang/rust/pull/50167 has been merged - const MILLIS_PER_SEC: u64 = 1_000; - const NANOS_PER_MILLI: u64 = 1_000_000; - let millis = (duration.as_secs() as u64 * MILLIS_PER_SEC) - + (duration.subsec_nanos() as u64 / NANOS_PER_MILLI); - - let mut meas_cycles = os_to_meas_cycles - [tph_sett.os_temp.unwrap_or(OversamplingSetting::OSNone) as usize] - as u64; - meas_cycles = meas_cycles.wrapping_add( - os_to_meas_cycles[tph_sett.os_pres.unwrap_or(OversamplingSetting::OSNone) as usize] - as u64, - ); - meas_cycles = meas_cycles.wrapping_add( - os_to_meas_cycles[tph_sett.os_hum.unwrap_or(OversamplingSetting::OSNone) as usize] - as u64, - ); - let mut tph_dur = meas_cycles.wrapping_mul(1963u64); - tph_dur = tph_dur.wrapping_add(477u64.wrapping_mul(4u64)); - tph_dur = tph_dur.wrapping_add(477u64.wrapping_mul(5u64)); - tph_dur = tph_dur.wrapping_add(500u64); - tph_dur = tph_dur.wrapping_div(1000u64); - tph_dur = tph_dur.wrapping_add(1u64); - self.gas_sett.heatr_dur = Some(Duration::from_millis(millis - tph_dur)); - } - - pub fn get_profile_dur( + pub fn get_profile_duration( &self, sensor_settings: &SensorSettings, - ) -> Result::Error, ::Error> { + ) -> Result { let os_to_meas_cycles: [u8; 6] = [0u8, 1u8, 2u8, 4u8, 8u8, 16u8]; - // TODO check if the following unwrap_ors do not change behaviour - let mut meas_cycles = os_to_meas_cycles[sensor_settings - .tph_sett - .os_temp + let mut measurement_cycles = os_to_meas_cycles[sensor_settings + .temperature_settings + .temperature_oversampling .unwrap_or(OversamplingSetting::OSNone) as usize] as u32; - meas_cycles = meas_cycles.wrapping_add( + measurement_cycles = measurement_cycles.wrapping_add( os_to_meas_cycles[sensor_settings - .tph_sett - .os_pres + .temperature_settings + .pressure_oversampling .unwrap_or(OversamplingSetting::OSNone) as usize] as u32, ); - meas_cycles = meas_cycles.wrapping_add( + measurement_cycles = measurement_cycles.wrapping_add( os_to_meas_cycles[sensor_settings - .tph_sett - .os_hum + .temperature_settings + .humidity_oversampling .unwrap_or(OversamplingSetting::OSNone) as usize] as u32, ); - let mut tph_dur = meas_cycles.wrapping_mul(1963u32); - tph_dur = tph_dur.wrapping_add(477u32.wrapping_mul(4u32)); - tph_dur = tph_dur.wrapping_add(477u32.wrapping_mul(5u32)); - tph_dur = tph_dur.wrapping_add(500u32); - tph_dur = tph_dur.wrapping_div(1000u32); - tph_dur = tph_dur.wrapping_add(1u32); - let mut duration = Duration::from_millis(tph_dur as u64); - if sensor_settings.gas_sett.run_gas_measurement { - duration += sensor_settings.gas_sett.heatr_dur.expect("Heatrdur"); + let mut temperature_duration = measurement_cycles.wrapping_mul(1963u32); + temperature_duration = temperature_duration.wrapping_add(477u32.wrapping_mul(4u32)); + temperature_duration = temperature_duration.wrapping_add(477u32.wrapping_mul(5u32)); + temperature_duration = temperature_duration.wrapping_add(500u32); + temperature_duration = temperature_duration.wrapping_div(1000u32); + temperature_duration = temperature_duration.wrapping_add(1u32); + let mut duration = Duration::from_millis(temperature_duration as u64); + if sensor_settings.gas_settings.enable_gas_measurement { + duration += sensor_settings + .gas_settings + .heater_duration + .unwrap_or_default(); } Ok(duration) } - fn get_calib_data( + fn get_calibration_data( i2c: &mut I2CX, - dev_id: I2CAddress, - ) -> Result::Error, ::Error> + device_address: Address, + ) -> Result where - I2CX: Read + Write, + I2CX: I2c, { - let mut calib: CalibData = Default::default(); + let mut calibration_data: CalibrationData = Default::default(); - let mut coeff_array: [u8; BME680_COEFF_ADDR1_LEN + BME680_COEFF_ADDR2_LEN] = + let mut coefficients_array: [u8; BME680_COEFF_ADDR1_LEN + BME680_COEFF_ADDR2_LEN] = [0; BME680_COEFF_ADDR1_LEN + BME680_COEFF_ADDR2_LEN]; - I2CUtil::read_bytes::( + I2CUtility::read_bytes::( i2c, - dev_id.addr(), + device_address.addr(), BME680_COEFF_ADDR1, - &mut coeff_array[0..(BME680_COEFF_ADDR1_LEN - 1)], - )?; - - I2CUtil::read_bytes::( + &mut coefficients_array[0..(BME680_COEFF_ADDR1_LEN - 1)], + ) + .map_err(|_e| { + anyhow!( + "Failed to get calibration data from device: {}", + device_address + ) + })?; + + I2CUtility::read_bytes::( i2c, - dev_id.addr(), + device_address.addr(), BME680_COEFF_ADDR2, - &mut coeff_array + &mut coefficients_array [BME680_COEFF_ADDR1_LEN..(BME680_COEFF_ADDR1_LEN + BME680_COEFF_ADDR2_LEN - 1)], - )?; + ) + .map_err(|_e| { + anyhow!( + "Failed to get calibration data from device: {}", + device_address + ) + })?; + + calibration_data.par_t1 = ((coefficients_array[34usize] as i32) << 8i32 + | coefficients_array[33usize] as i32) as u16; + calibration_data.par_t2 = ((coefficients_array[2usize] as i32) << 8i32 + | coefficients_array[1usize] as i32) as i16; + calibration_data.par_t3 = coefficients_array[3usize] as i8; + calibration_data.par_p1 = ((coefficients_array[6usize] as i32) << 8i32 + | coefficients_array[5usize] as i32) as u16; + calibration_data.par_p2 = ((coefficients_array[8usize] as i32) << 8i32 + | coefficients_array[7usize] as i32) as i16; + calibration_data.par_p3 = coefficients_array[9usize] as i8; + calibration_data.par_p4 = ((coefficients_array[12usize] as i32) << 8i32 + | coefficients_array[11usize] as i32) as i16; + calibration_data.par_p5 = ((coefficients_array[14usize] as i32) << 8i32 + | coefficients_array[13usize] as i32) as i16; + calibration_data.par_p6 = coefficients_array[16usize] as i8; + calibration_data.par_p7 = coefficients_array[15usize] as i8; + calibration_data.par_p8 = ((coefficients_array[20usize] as i32) << 8i32 + | coefficients_array[19usize] as i32) as i16; + calibration_data.par_p9 = ((coefficients_array[22usize] as i32) << 8i32 + | coefficients_array[21usize] as i32) as i16; + calibration_data.par_p10 = coefficients_array[23usize]; + calibration_data.par_h1 = ((coefficients_array[27usize] as i32) << 4i32 + | coefficients_array[26usize] as i32 & 0xfi32) as u16; + calibration_data.par_h2 = ((coefficients_array[25usize] as i32) << 4i32 + | coefficients_array[26usize] as i32 >> 4i32) as u16; + calibration_data.par_h3 = coefficients_array[28usize] as i8; + calibration_data.par_h4 = coefficients_array[29usize] as i8; + calibration_data.par_h5 = coefficients_array[30usize] as i8; + calibration_data.par_h6 = coefficients_array[31usize]; + calibration_data.par_h7 = coefficients_array[32usize] as i8; + calibration_data.par_gh1 = coefficients_array[37usize] as i8; + calibration_data.par_gh2 = ((coefficients_array[36usize] as i32) << 8i32 + | coefficients_array[35usize] as i32) as i16; + calibration_data.par_gh3 = coefficients_array[38usize] as i8; + + calibration_data.res_heat_range = (I2CUtility::read_byte::( + i2c, + device_address.addr(), + BME680_ADDR_RES_HEAT_RANGE_ADDR, + ) + .map_err(|_e| anyhow!("Failed to read from register BME680_ADDR_RES_HEAT_RANGE_ADDR"))? + & 0x30) + / 16; + + calibration_data.res_heat_val = I2CUtility::read_byte::( + i2c, + device_address.addr(), + BME680_ADDR_RES_HEAT_VAL_ADDR, + ) + .map_err(|_e| anyhow!("Failed to read from register BME680_ADDR_RES_HEAT_VAL_ADDR"))? + as i8; - calib.par_t1 = ((coeff_array[34usize] as i32) << 8i32 | coeff_array[33usize] as i32) as u16; - calib.par_t2 = ((coeff_array[2usize] as i32) << 8i32 | coeff_array[1usize] as i32) as i16; - calib.par_t3 = coeff_array[3usize] as i8; - calib.par_p1 = ((coeff_array[6usize] as i32) << 8i32 | coeff_array[5usize] as i32) as u16; - calib.par_p2 = ((coeff_array[8usize] as i32) << 8i32 | coeff_array[7usize] as i32) as i16; - calib.par_p3 = coeff_array[9usize] as i8; - calib.par_p4 = ((coeff_array[12usize] as i32) << 8i32 | coeff_array[11usize] as i32) as i16; - calib.par_p5 = ((coeff_array[14usize] as i32) << 8i32 | coeff_array[13usize] as i32) as i16; - calib.par_p6 = coeff_array[16usize] as i8; - calib.par_p7 = coeff_array[15usize] as i8; - calib.par_p8 = ((coeff_array[20usize] as i32) << 8i32 | coeff_array[19usize] as i32) as i16; - calib.par_p9 = ((coeff_array[22usize] as i32) << 8i32 | coeff_array[21usize] as i32) as i16; - calib.par_p10 = coeff_array[23usize]; - calib.par_h1 = - ((coeff_array[27usize] as i32) << 4i32 | coeff_array[26usize] as i32 & 0xfi32) as u16; - calib.par_h2 = - ((coeff_array[25usize] as i32) << 4i32 | coeff_array[26usize] as i32 >> 4i32) as u16; - calib.par_h3 = coeff_array[28usize] as i8; - calib.par_h4 = coeff_array[29usize] as i8; - calib.par_h5 = coeff_array[30usize] as i8; - calib.par_h6 = coeff_array[31usize]; - calib.par_h7 = coeff_array[32usize] as i8; - calib.par_gh1 = coeff_array[37usize] as i8; - calib.par_gh2 = - ((coeff_array[36usize] as i32) << 8i32 | coeff_array[35usize] as i32) as i16; - calib.par_gh3 = coeff_array[38usize] as i8; - - calib.res_heat_range = - (I2CUtil::read_byte::(i2c, dev_id.addr(), BME680_ADDR_RES_HEAT_RANGE_ADDR)? - & 0x30) - / 16; - - calib.res_heat_val = - I2CUtil::read_byte::(i2c, dev_id.addr(), BME680_ADDR_RES_HEAT_VAL_ADDR)? as i8; - - calib.range_sw_err = - (I2CUtil::read_byte::(i2c, dev_id.addr(), BME680_ADDR_RANGE_SW_ERR_ADDR)? - & BME680_RSERROR_MSK) - / 16; - - Ok(calib) + calibration_data.range_sw_err = (I2CUtility::read_byte::( + i2c, + device_address.addr(), + BME680_ADDR_RANGE_SW_ERR_ADDR, + ) + .map_err(|_e| anyhow!("Failed to read from register BME680_ADDR_RANGE_SW_ERR_ADDR"))? + & BME680_RSERROR_MSK) + / 16; + + Ok(calibration_data) } - fn set_gas_config( - &mut self, - gas_sett: GasSett, - ) -> Result<(), ::Error, ::Error> { + fn set_gas_settings(&mut self, gas_settings: GasSettings) -> Result<(), anyhow::Error> { if self.power_mode != PowerMode::ForcedMode { - return Err(Error::DefinePwrMode); + return Err(anyhow!("Current power mode is not forced")); } - // TODO check whether unwrap_or changes behaviour let reg: [(u8, u8); 2] = [ ( BME680_RES_HEAT0_ADDR, - Calc::calc_heater_res( - &self.calib, - gas_sett.ambient_temperature, - gas_sett.heatr_temp.unwrap_or(0), + Calculation::heater_resistance( + &self.calibration_data, + gas_settings.ambient_temperature, + gas_settings.heater_temperature.unwrap_or(0), ), ), ( BME680_GAS_WAIT0_ADDR, - Calc::calc_heater_dur(gas_sett.heatr_dur.unwrap_or_else(|| Duration::from_secs(0))), + Calculation::heater_duration( + gas_settings + .heater_duration + .unwrap_or_else(|| Duration::from_secs(0)), + ), ), ]; - self.gas_sett.nb_conv = 0; - self.bme680_set_regs(®) + self.bme680_set_registers(®) } - fn get_gas_config(&mut self) -> Result::Error, ::Error> { - let heatr_temp = Some(I2CUtil::read_byte( - &mut self.i2c, - self.dev_id.addr(), + fn get_gas_settings(&mut self) -> Result { + let heater_temperature = Some(I2CUtility::read_byte( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), BME680_ADDR_SENS_CONF_START, )? as u16); - let heatr_dur_ms = I2CUtil::read_byte( - &mut self.i2c, - self.dev_id.addr(), + let heater_duration_ms = I2CUtility::read_byte( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), BME680_ADDR_GAS_CONF_START, )? as u64; - let gas_sett = GasSett { - heatr_temp, - heatr_dur: Some(Duration::from_millis(heatr_dur_ms)), + let gas_sett = GasSettings { + heater_temperature, + heater_duration: Some(Duration::from_millis(heater_duration_ms)), ..Default::default() }; Ok(gas_sett) } - /// Retrieve the current sensor informations - pub fn get_sensor_data( + /// Retrieve the current sensor measurement. + pub fn get_measurement( &mut self, delay: &mut D, - ) -> Result<(FieldData, FieldDataCondition), ::Error, ::Error> { - let mut buff: [u8; BME680_FIELD_LENGTH] = [0; BME680_FIELD_LENGTH]; + ) -> Result<(FieldData, FieldDataCondition), anyhow::Error> { + let mut buffer: [u8; BME680_FIELD_LENGTH] = [0; BME680_FIELD_LENGTH]; - debug!("Buf {:?}, len: {}", buff, buff.len()); + debug!("Buf {:?}, len: {}", buffer, buffer.len()); let mut data: FieldData = Default::default(); const TRIES: u8 = 10; for _ in 0..TRIES { - I2CUtil::read_bytes( - &mut self.i2c, - self.dev_id.addr(), + I2CUtility::read_bytes( + self.i2c_bus_handle.borrow_mut().deref_mut(), + self.device_address.addr(), BME680_FIELD0_ADDR, - &mut buff, + &mut buffer, )?; - debug!("Field data read {:?}, len: {}", buff, buff.len()); + debug!("Field data read {:?}, len: {}", buffer, buffer.len()); - data.status = buff[0] & BME680_NEW_DATA_MSK; - data.gas_index = buff[0] & BME680_GAS_INDEX_MSK; - data.meas_index = buff[1]; + data.status = buffer[0] & BME680_NEW_DATA_MSK; + data.gas_index = buffer[0] & BME680_GAS_INDEX_MSK; + data.measurement_index = buffer[1]; - let adc_pres = (buff[2] as u32).wrapping_mul(4096) - | (buff[3] as u32).wrapping_mul(16) - | (buff[4] as u32).wrapping_div(16); - let adc_temp = (buff[5] as u32).wrapping_mul(4096) - | (buff[6] as u32).wrapping_mul(16) - | (buff[7] as u32).wrapping_div(16); - let adc_hum = ((buff[8] as u32).wrapping_mul(256) | buff[9] as u32) as u16; - let adc_gas_res = - ((buff[13] as u32).wrapping_mul(4) | (buff[14] as u32).wrapping_div(64)) as u16; - let gas_range = buff[14] & BME680_GAS_RANGE_MSK; + let adc_pressure = (buffer[2] as u32).wrapping_mul(4096) + | (buffer[3] as u32).wrapping_mul(16) + | (buffer[4] as u32).wrapping_div(16); + let adc_temperature = (buffer[5] as u32).wrapping_mul(4096) + | (buffer[6] as u32).wrapping_mul(16) + | (buffer[7] as u32).wrapping_div(16); + let adc_humidity = ((buffer[8] as u32).wrapping_mul(256) | buffer[9] as u32) as u16; + let adc_gas_resistance = + ((buffer[13] as u32).wrapping_mul(4) | (buffer[14] as u32).wrapping_div(64)) as u16; + let gas_range = buffer[14] & BME680_GAS_RANGE_MSK; - data.status |= buff[14] & BME680_GASM_VALID_MSK; - data.status |= buff[14] & BME680_HEAT_STAB_MSK; + data.status |= buffer[14] & BME680_GASM_VALID_MSK; + data.status |= buffer[14] & BME680_HEAT_STAB_MSK; if data.status & BME680_NEW_DATA_MSK != 0 { - let (temp, t_fine) = - Calc::calc_temperature(&self.calib, adc_temp, self.tph_sett.temperature_offset); + let (temp, t_fine) = Calculation::temperature( + &self.calibration_data, + adc_temperature, + Some(self.temperature_offset), + ); debug!( "adc_temp: {} adc_pres: {} adc_hum: {} adc_gas_res: {}, t_fine: {}", - adc_temp, adc_pres, adc_hum, adc_gas_res, t_fine + adc_temperature, adc_pressure, adc_humidity, adc_gas_resistance, t_fine ); data.temperature = temp; - data.pressure = Calc::calc_pressure(&self.calib, t_fine, adc_pres); - data.humidity = Calc::calc_humidity(&self.calib, t_fine, adc_hum); - data.gas_resistance = - Calc::calc_gas_resistance(&self.calib, adc_gas_res, gas_range); + data.pressure = Calculation::pressure(&self.calibration_data, t_fine, adc_pressure); + data.humidity = Calculation::humidity(&self.calibration_data, t_fine, adc_humidity); + data.gas_resistance = Calculation::gas_resistance( + &self.calibration_data, + adc_gas_resistance, + gas_range, + ); return Ok((data, FieldDataCondition::NewData)); } - delay - .delay_ms(BME680_POLL_PERIOD_MS) - .map_err(|_| Error::Delay)?; + delay.delay_ms(BME680_POLL_PERIOD_MS as u32); } Ok((data, FieldDataCondition::Unchanged)) } diff --git a/src/settings.rs b/src/settings.rs index f921c6a..0bfa31f 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -14,16 +14,15 @@ pub enum OversamplingSetting { } impl OversamplingSetting { - // TODO replace with TryFrom once stabilized - pub fn from_u8(os: u8) -> OversamplingSetting { - match os { + pub fn from_u8(value: u8) -> OversamplingSetting { + match value { 0 => OversamplingSetting::OSNone, 1 => OversamplingSetting::OS1x, 2 => OversamplingSetting::OS2x, 3 => OversamplingSetting::OS4x, 4 => OversamplingSetting::OS8x, 5 => OversamplingSetting::OS16x, - _ => panic!("Unknown oversampling setting: {}", os), + _ => panic!("Unknown oversampling setting: {}", value), } } } @@ -43,9 +42,8 @@ pub enum IIRFilterSize { } impl IIRFilterSize { - // TODO replace with TryFrom once stabilized - pub fn from_u8(os: u8) -> IIRFilterSize { - match os { + pub fn from_u8(value: u8) -> IIRFilterSize { + match value { 0 => IIRFilterSize::Size0, 1 => IIRFilterSize::Size1, 2 => IIRFilterSize::Size3, @@ -54,7 +52,7 @@ impl IIRFilterSize { 5 => IIRFilterSize::Size31, 6 => IIRFilterSize::Size63, 7 => IIRFilterSize::Size127, - _ => panic!("Unknown IIRFilterSize: {}", os), + _ => panic!("Unknown IIRFilterSize: {}", value), } } } @@ -62,20 +60,20 @@ impl IIRFilterSize { /// Temperature settings #[derive(Debug, Default, Copy)] #[repr(C)] -pub struct TphSett { +pub struct TemperatureSettings { /// Humidity oversampling - pub os_hum: Option, + pub humidity_oversampling: Option, /// Temperature oversampling - pub os_temp: Option, + pub temperature_oversampling: Option, /// Pressure oversampling - pub os_pres: Option, + pub pressure_oversampling: Option, /// Filter coefficient - pub filter: Option, + pub filter_size: Option, /// If set, the temperature t_fine will be increased by the given value in celsius. pub temperature_offset: Option, } -impl Clone for TphSett { +impl Clone for TemperatureSettings { fn clone(&self) -> Self { *self } @@ -84,20 +82,22 @@ impl Clone for TphSett { /// Gas measurement settings #[derive(Debug, Default, Copy)] #[repr(C)] -pub struct GasSett { +pub struct GasSettings { + /// nb_conv is used to select heater set-points of the sensor. pub nb_conv: u8, /// Heater control - pub heatr_ctrl: Option, + pub heater_control: Option, /// Enable measurement of gas, disabled by default - pub run_gas_measurement: bool, - /// Heater temperature - pub heatr_temp: Option, - /// Profile duration - pub heatr_dur: Option, + pub enable_gas_measurement: bool, + /// The heater temperature + pub heater_temperature: Option, + /// The Heating duration + pub heater_duration: Option, + /// The ambient temperature. pub ambient_temperature: i8, } -impl Clone for GasSett { +impl Clone for GasSettings { fn clone(&self) -> Self { *self } @@ -107,9 +107,9 @@ impl Clone for GasSett { #[derive(Debug, Default, Copy)] pub struct SensorSettings { /// Gas settings - pub gas_sett: GasSett, + pub gas_settings: GasSettings, /// Temperature settings - pub tph_sett: TphSett, + pub temperature_settings: TemperatureSettings, } impl Clone for SensorSettings { @@ -132,15 +132,15 @@ bitflags! { /// To set gas measurement setting. const GAS_MEAS_SEL = 8; /// To set filter setting. - const FILTER_SEL = 16; + const FILTER_SIZE_SEL = 16; /// To set humidity control setting. - const HCNTRL_SEL = 32; + const HUMIDITY_CONTROL_SEL = 32; /// To set run gas setting. const RUN_GAS_SEL = 64; /// To set NB conversion setting. const NBCONV_SEL = 128; /// To set all gas sensor related settings - const GAS_SENSOR_SEL = Self::GAS_MEAS_SEL.bits | Self::RUN_GAS_SEL.bits | Self::NBCONV_SEL.bits; + const GAS_SENSOR_SEL = Self::GAS_MEAS_SEL.bits() | Self::RUN_GAS_SEL.bits() | Self::NBCONV_SEL.bits(); } } @@ -171,74 +171,96 @@ pub struct SettingsBuilder { pub type Settings = (SensorSettings, DesiredSensorSettings); impl SettingsBuilder { + /// Constructs a new instance of the SettingsBuilder. pub fn new() -> SettingsBuilder { SettingsBuilder::default() } - pub fn with_temperature_filter(mut self, filter: IIRFilterSize) -> SettingsBuilder { - self.sensor_settings.tph_sett.filter = Some(filter); - self.desired_settings |= DesiredSensorSettings::FILTER_SEL; + /// With temperature filter. + pub fn with_temperature_filter(mut self, filter_size: IIRFilterSize) -> SettingsBuilder { + self.sensor_settings.temperature_settings.filter_size = Some(filter_size); + self.desired_settings |= DesiredSensorSettings::FILTER_SIZE_SEL; self } - pub fn with_humidity_control(mut self, heatr_control: u8) -> SettingsBuilder { - self.sensor_settings.gas_sett.heatr_ctrl = Some(heatr_control); - self.desired_settings |= DesiredSensorSettings::HCNTRL_SEL; + /// With gas heater control. + pub fn with_gas_heater_control(mut self, heater_control: u8) -> SettingsBuilder { + self.sensor_settings.gas_settings.heater_control = Some(heater_control); + self.desired_settings |= DesiredSensorSettings::HUMIDITY_CONTROL_SEL; self } + /// With temperature oversampling pub fn with_temperature_oversampling( mut self, - os_temp: OversamplingSetting, + temperature_oversampling: OversamplingSetting, ) -> SettingsBuilder { - self.sensor_settings.tph_sett.os_temp = Some(os_temp); + self.sensor_settings + .temperature_settings + .temperature_oversampling = Some(temperature_oversampling); self.desired_settings |= DesiredSensorSettings::OST_SEL; self } - pub fn with_pressure_oversampling(mut self, os_pres: OversamplingSetting) -> SettingsBuilder { - self.sensor_settings.tph_sett.os_pres = Some(os_pres); + /// With pressure oversampling. + pub fn with_pressure_oversampling( + mut self, + pressure_oversampling: OversamplingSetting, + ) -> SettingsBuilder { + self.sensor_settings + .temperature_settings + .pressure_oversampling = Some(pressure_oversampling); self.desired_settings |= DesiredSensorSettings::OSP_SEL; self } - pub fn with_humidity_oversampling(mut self, os_hum: OversamplingSetting) -> SettingsBuilder { - self.sensor_settings.tph_sett.os_hum = Some(os_hum); + /// With humidity oversampling. + pub fn with_humidity_oversampling( + mut self, + humidity_oversampling: OversamplingSetting, + ) -> SettingsBuilder { + self.sensor_settings + .temperature_settings + .humidity_oversampling = Some(humidity_oversampling); self.desired_settings |= DesiredSensorSettings::OSH_SEL; self } + /// With gas measurement. pub fn with_gas_measurement( mut self, - heatr_dur: Duration, - heatr_temp: u16, + heater_duration: Duration, + heater_temperature: u16, ambient_temperature: i8, ) -> SettingsBuilder { - self.sensor_settings.gas_sett.heatr_dur = Some(heatr_dur); - self.sensor_settings.gas_sett.heatr_temp = Some(heatr_temp); - self.sensor_settings.gas_sett.ambient_temperature = ambient_temperature; + self.sensor_settings.gas_settings.heater_duration = Some(heater_duration); + self.sensor_settings.gas_settings.heater_temperature = Some(heater_temperature); + self.sensor_settings.gas_settings.ambient_temperature = ambient_temperature; self.desired_settings |= DesiredSensorSettings::GAS_SENSOR_SEL; self } + /// With nb_conv. pub fn with_nb_conv(mut self, nb_conv: u8) -> SettingsBuilder { - self.sensor_settings.gas_sett.nb_conv = nb_conv; + self.sensor_settings.gas_settings.nb_conv = nb_conv; self.desired_settings |= DesiredSensorSettings::GAS_SENSOR_SEL; self } + /// With run gas. pub fn with_run_gas(mut self, run_gas: bool) -> SettingsBuilder { - self.sensor_settings.gas_sett.run_gas_measurement = run_gas; + self.sensor_settings.gas_settings.enable_gas_measurement = run_gas; self.desired_settings |= DesiredSensorSettings::GAS_SENSOR_SEL; self } - /// Temperature offset in Celsius, e.g. 4, -8, 1.25 + /// With temperature offset in Celsius, e.g. 4, -8, 1.25 pub fn with_temperature_offset(mut self, offset: f32) -> SettingsBuilder { - self.sensor_settings.tph_sett.temperature_offset = Some(offset); + self.sensor_settings.temperature_settings.temperature_offset = Some(offset); self } + /// Builds the settings object pub fn build(self) -> Settings { (self.sensor_settings, self.desired_settings) }