diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..487b90b --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# BME280 module + +The BME280 is as combined digital humidity, pressure and temperature sensor based on proven sensing principles. +The sensor module is housed in an extremely compact metal-lid LGA package with a footprint of only 2.5 × 2.5 mm² with a height of 0.93 mm. +Its small dimensions and its low power consumption allow the implementation in battery driven devices such as handsets, GPS modules or watches. + +[Datasheet](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf) + +This module utilizes the [Viam Sensor Component](https://docs.viam.com/operate/reference/components/sensor/) API and acts as a wrapper around the [BME280 ESP-IDF Component](https://components.espressif.com/components/espressif/bme280). + +## Configuration + +This component utilizes the following configuration attributes: +- `i2c_bus`: The I2C bus initialized on the board (defaults to `0`) +- `scl_pin`: The `SCL` I2C clock pin (defaults to `22`) +- `sda_pin`: The `SDA` IC2 data pin (defaults to `21`) + +> CAUTION: This component's implementation initializes the I2C bus directly and assumes it is the sole device with control of the bus. Do not use in conjunction with the [Viam Board Component](https://docs.viam.com/operate/reference/components/board/) APIs for I2C or other devices on the same bus. + +```json +{ + "name": "my-bme280-sensor", + "api": "rdk:component:sensor", + "model": "bme280", + "attributes": { + "i2c_bus": 0, + "scl_pin": 21, + "sda_pin": 22, + } +} +``` diff --git a/src/espressif.rs b/src/espressif.rs deleted file mode 100644 index fa82c3b..0000000 --- a/src/espressif.rs +++ /dev/null @@ -1,130 +0,0 @@ -use micro_rdk::{ - common::{ - config::ConfigType, - registry::{ComponentRegistry, Dependency, RegistryError}, - sensor::{ - GenericReadingsResult, Readings, Sensor, SensorError, SensorResult, SensorT, - SensorType, TypedReadingsResult, - }, - status::{Status, StatusError}, - }, - esp32::esp_idf_svc::hal::sys::esp, - DoCommand, -}; -use std::{ - collections::HashMap, - sync::{Arc, Mutex}, -}; - -// idf component re-exported from esp-idf-svc build system -use micro_rdk::esp32::esp_idf_svc::sys::bme280::*; - -/// Global handle to I2C bus -static mut I2C_BUS: i2c_bus_handle_t = std::ptr::null_mut(); -/// Global handle to BME280 device -static mut BME280: bme280_handle_t = std::ptr::null_mut(); -const I2C_MODE_MASTER: u32 = 0x1; - -pub fn register_models(registry: &mut ComponentRegistry) -> Result<(), RegistryError> { - registry.register_sensor("bme280", &Bme280::from_config) -} - -#[derive(DoCommand)] -pub struct Bme280 {} - -impl Status for Bme280 { - fn get_status(&self) -> Result, StatusError> { - let fields = HashMap::new(); - Ok(Some(micro_rdk::google::protobuf::Struct { fields })) - } -} - -impl Sensor for Bme280 {} - -impl Readings for Bme280 { - fn get_generic_readings(&mut self) -> Result { - Ok(self - .get_readings()? - .into_iter() - .map(|v| (v.0, SensorResult:: { value: v.1 }.into())) - .collect()) - } -} - -impl SensorT for Bme280 { - fn get_readings(&self) -> Result, SensorError> { - let mut temperature: f32 = 0.0; - let mut pressure: f32 = 0.0; - let mut humidity: f32 = 0.0; - unsafe { - log::debug!("bme280 - reading temperature..."); - esp!(bme280_read_temperature( - BME280, - &mut temperature as &mut f32 - ))?; - log::debug!("bme280 - reading pressure..."); - esp!(bme280_read_pressure(BME280, &mut pressure as &mut f32))?; - log::debug!("bme280 - reading humidity..."); - esp!(bme280_read_humidity(BME280, &mut humidity as &mut f32))?; - } - log::debug!( - "temperature: {}, humidity: {}, pressure: {}", - temperature, - humidity, - pressure - ); - let mut x: HashMap = HashMap::new(); - x.insert("temperature_c".to_string(), temperature.into()); - x.insert("humidity".to_string(), humidity.into()); - x.insert("pressure_hpa".to_string(), pressure.into()); - Ok(x) - } -} - -impl Bme280 { - pub fn from_config(cfg: ConfigType, _deps: Vec) -> Result { - // DO NOT use the board i2c interface to initialize the i2c bus, the idf-component will handle it - let bus_no = cfg - .get_attribute::("i2c_bus") - .inspect_err(|_| { - log::warn!("`i2c_bus` attribute not found or invalid, defaulting to bus 0") - }) - .unwrap_or_default(); - - let sda_pin = cfg - .get_attribute::("sda_pin") - .inspect_err(|_| { - log::info!("`sda_pin` attribute not found or invalid, defaulting to pin 22") - }) - .unwrap_or(22); - let scl_pin = cfg - .get_attribute::("scl_pin") - .inspect_err(|_| { - log::info!("`scl_pin` attribute not found or invalid, defaulting to pin 21") - }) - .unwrap_or(21); - - let config = i2c_config_t { - mode: I2C_MODE_MASTER, - sda_io_num: sda_pin, - sda_pullup_en: true, - scl_io_num: scl_pin, - scl_pullup_en: true, - __bindgen_anon_1: i2c_config_t__bindgen_ty_1 { - master: i2c_config_t__bindgen_ty_1__bindgen_ty_1 { clk_speed: 100000 }, - }, - clk_flags: 0, - }; - - unsafe { - log::info!("initializing I2C_BUS: {:?}", I2C_BUS); - I2C_BUS = i2c_bus_create(bus_no, &config); - log::info!("initialized I2C_BUS to : {:?}", I2C_BUS); - log::info!("initializing BME280: {:?}", BME280); - BME280 = bme280_create(I2C_BUS, BME280_I2C_ADDRESS_DEFAULT.try_into().unwrap()); - log::info!("initialized BME280 to : {:?}", BME280); - esp!(bme280_default_init(BME280))?; - } - Ok(Arc::new(Mutex::new(Self {}))) - } -} diff --git a/src/lib.rs b/src/lib.rs index e4a03f4..58b0f09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,185 @@ -//pub mod viam; -mod espressif; -pub use espressif::*; +use micro_rdk::{ + common::{ + config::ConfigType, + registry::{ComponentRegistry, Dependency, RegistryError}, + sensor::{ + GenericReadingsResult, Readings, Sensor, SensorError, SensorResult, SensorT, + SensorType, TypedReadingsResult, + }, + status::{Status, StatusError}, + }, + esp32::esp_idf_svc::hal::sys::esp, + DoCommand, +}; +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +// idf component re-exported from esp-idf-svc build system +use micro_rdk::esp32::esp_idf_svc::sys::bme280::*; + +const I2C_MODE_MASTER: u32 = 0x1; + +pub fn register_models(registry: &mut ComponentRegistry) -> Result<(), RegistryError> { + registry.register_sensor("bme280", &Bme280::from_config) +} + +#[derive(DoCommand)] +pub struct Bme280 { + // handle to I2C bus + i2c_handle: i2c_bus_handle_t, + // handle to BME280 device + driver_handle: bme280_handle_t, // = std::ptr::null_mut() +} + +// Safety: The raw pointer handles are bound to the initialization and drop of this struct. +// Additionally, as the current implementation assumes ownership of the i2c bus, when used in isolation, +// there is no risk of another device manipulating the i2c and device handles. +unsafe impl Send for Bme280 {} + +impl Status for Bme280 { + fn get_status(&self) -> Result, StatusError> { + let fields = HashMap::new(); + Ok(Some(micro_rdk::google::protobuf::Struct { fields })) + } +} + +impl Sensor for Bme280 {} + +impl Readings for Bme280 { + fn get_generic_readings(&mut self) -> Result { + Ok(self + .get_readings()? + .into_iter() + .map(|v| (v.0, SensorResult:: { value: v.1 }.into())) + .collect()) + } +} + +impl SensorT for Bme280 { + fn get_readings(&self) -> Result, SensorError> { + let mut temperature: f32 = 0.0; + let mut pressure: f32 = 0.0; + let mut humidity: f32 = 0.0; + unsafe { + log::debug!("bme280 - reading temperature..."); + esp!(bme280_read_temperature( + self.driver_handle, + &mut temperature as &mut f32 + ))?; + log::debug!("bme280 - reading pressure..."); + esp!(bme280_read_pressure( + self.driver_handle, + &mut pressure as &mut f32 + ))?; + log::debug!("bme280 - reading humidity..."); + esp!(bme280_read_humidity( + self.driver_handle, + &mut humidity as &mut f32 + ))?; + } + log::debug!( + "temperature: {}, humidity: {}, pressure: {}", + temperature, + humidity, + pressure + ); + let mut x: HashMap = HashMap::new(); + x.insert("temperature_c".to_string(), temperature.into()); + x.insert("humidity".to_string(), humidity.into()); + x.insert("pressure_hpa".to_string(), pressure.into()); + Ok(x) + } +} + +impl Bme280 { + pub fn from_config(cfg: ConfigType, _deps: Vec) -> Result { + // DO NOT use the board i2c interface to initialize the i2c bus, the idf-component will handle it + let bus_no = cfg + .get_attribute::("i2c_bus") + .inspect_err(|_| { + log::warn!("`i2c_bus` attribute not found or invalid, defaulting to bus 0") + }) + .unwrap_or_default(); + + let sda_pin = cfg + .get_attribute::("sda_pin") + .inspect_err(|_| { + log::info!("`sda_pin` attribute not found or invalid, defaulting to pin 22") + }) + .unwrap_or(21); + let scl_pin = cfg + .get_attribute::("scl_pin") + .inspect_err(|_| { + log::info!("`scl_pin` attribute not found or invalid, defaulting to pin 21") + }) + .unwrap_or(22); + + let config = i2c_config_t { + mode: I2C_MODE_MASTER, + sda_io_num: sda_pin, + sda_pullup_en: true, + scl_io_num: scl_pin, + scl_pullup_en: true, + __bindgen_anon_1: i2c_config_t__bindgen_ty_1 { + master: i2c_config_t__bindgen_ty_1__bindgen_ty_1 { clk_speed: 100000 }, + }, + clk_flags: 0, + }; + + let mut sensor = Self { + i2c_handle: std::ptr::null_mut(), + driver_handle: std::ptr::null_mut(), + }; + + unsafe { + log::info!("creating I2C_BUS..."); + + sensor.i2c_handle = i2c_bus_create(bus_no, &config); + if sensor.i2c_handle.is_null() { + log::error!("failed to create i2c bus `{}`", bus_no); + return Err(SensorError::ConfigError("failed to create i2c bus")); + } + log::info!("created I2C_BUS at : {:?}", sensor.i2c_handle); + + log::info!("creating BME280 device..."); + sensor.driver_handle = bme280_create( + sensor.i2c_handle, + BME280_I2C_ADDRESS_DEFAULT.try_into().unwrap(), + ); + + if sensor.driver_handle.is_null() { + return Err(SensorError::ConfigError("failed to create BME280 device")); + } + log::info!("created BME280 at : {:?}", sensor.driver_handle); + + log::info!("initializing BME280..."); + if esp!(bme280_default_init(sensor.driver_handle)).is_err() { + log::error!("failed to initialize BME280"); + return Err(SensorError::ConfigError( + "failed to initialize BME280 device", + )); + } + log::info!("BME280 initialized successfully"); + } + Ok(Arc::new(Mutex::new(sensor))) + } +} + +impl Drop for Bme280 { + fn drop(&mut self) { + unsafe { + if !self.driver_handle.is_null() { + log::debug!("deleting bme280"); + bme280_delete(&mut self.driver_handle as *mut _); + log::debug!("device_handle value deleting: {:?}", self.driver_handle); + } + if !self.i2c_handle.is_null() { + log::debug!("deleting I2C bus"); + i2c_bus_delete(&mut self.i2c_handle as *mut _); + log::debug!("i2c_bus handle value deleting: {:?}", self.i2c_handle); + } + } + } +} diff --git a/src/viam.rs b/src/viam.rs deleted file mode 100644 index 40adf13..0000000 --- a/src/viam.rs +++ /dev/null @@ -1,526 +0,0 @@ -//! bmp280 module implements the sensor interface for a Bosch BMP280 Digital Pressure Sensor. -//! (datasheet)[https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf] -//! The chip can communicate via I2C or SPI, determined by the chip select bus (CSB) bin. -//! - CSB Pulled High: IC2 -//! - CSB Pulled Low: SPI -//! -//! The chip has two possible I2C addresses, which can be selected by wiring the SDO pin to GND or Vddio. -//! GND: 1110110 (0x76) -//! Vddio: 1110111 (0x77), same as BMP180 and BME280 i2c addresses. -//! The SDO pin cannot be left floating; if left floating, the i2c address will be undefined. -//! -//! Ic2 interface uses the following pins: -//! - SCK: serial clock (SCL) -//! - SDI: data (SDA) -//! - SDO: slave address -//! -//! ## I2C Write -//! Sending the slave address in write mode (RW=0) followed by pairs of register addresses and register data. -//! Start(1bit)->SlaveAddress(7bit)->RW(1bit)->ACKS -//! RegisterAddress(8bit)->ACKS -//! Register Data(8bit)->ACKS -//! ... -//! Stop(1bit) -//! -//! ## I2C Read -//! Sending the slave address in write mode (RW=0) followed by the address of the register to be read. -//! Sending the slave address in read mode (RW=1) is then followed by the slave sending back auto-incremented register data, -//! terminated by a NOACKM and Stop. -//! -//! ## Data Readout -//! To read out data after a conversion, use burst read and not individual registers. -//! To read data, start a burst read from 0xF7(press_msb) to 0xFC(temp_xlsb). -//! Data er read out in an unsigned 20-bit format for both temperature and pressure. -//! -//! -//! Attributions: -//! - initial config snippets from (bmp280 driver)[https://github.com/copterust/bmp280] by coterust (MIT) - -use micro_rdk::common::board::Board; -use micro_rdk::common::config::ConfigType; -use micro_rdk::common::generic::DoCommand; -use micro_rdk::common::i2c::{I2CHandle, I2cHandleType}; -use micro_rdk::common::registry::{ComponentRegistry, Dependency, RegistryError}; -use micro_rdk::common::status::{Status, StatusError}; -use micro_rdk::DoCommand; -use std::cell::RefCell; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; - -use micro_rdk::common::sensor::SensorResult; - -use micro_rdk::common::sensor::{ - GenericReadingsResult, Readings, Sensor, SensorError, SensorT, SensorType, -}; - -pub fn register_models(registry: &mut ComponentRegistry) -> Result<(), RegistryError> { - registry.register_sensor("bme280", &Bmp280::from_config) -} - -enum Register { - HumLsb = 0xFE, - HumMsb = 0xFD, - TempXlsb = 0xFC, - TempLsb = 0xFB, - TempMsb = 0xFA, - PressXlsb = 0xF9, - PressLsb = 0xF8, - PressMsb = 0xF7, - /// the `config` register sets the rate, filter, and interface options of the device. - /// Writes to the `config` register might be ignored when in normal mode (cyclic reading). - /// In sleep mode, writes are not ignored. - Config = 0xF5, - /// the `ctrl_meas` register sets the data acquisition options of the device; ie oversampling settings and device power mode. - CtrlMeas = 0xF4, - /// the `status` register contains two bits regarding the status of the device. - /// Bit3 read as 1 whenever a conversion is running, read as 0 when results are transfered to data register . - /// Bit0 read as 1 when NVM data being copied to image registers. Read as 0 when copying is done. Data is copied - /// at power-on-reset and before every conversion - Status = 0xF3, - /// the `reset` register, if the value 0xB6 is written to this register, the device is reset using the complete power-on-reset procesdure. - Reset = 0xE0, - Id = 0xD0, - Calib = 0x88, -} - -enum BmpAddr { - Low = 0x76, - High = 0x77, -} - -#[derive(DoCommand)] -pub struct Bmp280 { - i2c_handle: I2cHandleType, - i2c_address: u8, - calib: RefCell, -} - -impl Status for Bmp280 { - fn get_status(&self) -> Result, StatusError> { - /* - let status = self.status(); - let mut fields = HashMap::new(); - fields.insert( - "measuring".to_string(), - micro_rdk::google::protobuf::Value { - kind: Some(micro_rdk::google::protobuf::value::Kind::BoolValue( - status.measuring, - )), - }, - ); - fields.insert( - "im_update".to_string(), - micro_rdk::google::protobuf::Value { - kind: Some(micro_rdk::google::protobuf::value::Kind::BoolValue( - status.im_update, - )), - }, - ); - */ - Ok(Some(micro_rdk::google::protobuf::Struct { - fields: HashMap::new(), - })) - } -} - -impl Sensor for Bmp280 {} - -impl SensorT for Bmp280 { - fn get_readings( - &self, - ) -> Result, SensorError> { - let register_write: [u8; 1] = [Register::PressMsb as u8]; - let mut result: [u8; 24] = [0; 24]; - let temp = self.temp_one_shot(); - let pressure = self.pressure_one_shot(); - self.i2c_handle - .write_read_i2c(self.i2c_address, ®ister_write, &mut result)?; - let mut x = HashMap::new(); - x.insert("temp".to_string(), temp); - x.insert("pressure".to_string(), pressure); - - Ok(x) - } -} - -impl Readings for Bmp280 { - fn get_generic_readings( - &mut self, - ) -> Result { - Ok(self - .get_readings()? - .into_iter() - .map(|v| (v.0, SensorResult:: { value: v.1 }.into())) - .collect()) - } -} - -impl Bmp280 { - pub fn from_config(cfg: ConfigType, deps: Vec) -> Result { - let board = micro_rdk::common::registry::get_board_from_dependencies(deps); - if board.is_none() { - return Err(SensorError::ConfigError("BMP280 missing board attribute")); - } - let board = board.unwrap(); - let i2c_handle: I2cHandleType; - if let Ok(i2c_name) = cfg.get_attribute::("i2c_bus") { - i2c_handle = board.get_i2c_by_name(i2c_name)?; - } else { - return Err(SensorError::ConfigError("BMP280 missing i2c_bus attribute")); - } - if let Ok(sdo_setting) = cfg.get_attribute::("sdo_setting") { - let i2c_address: u8; - match sdo_setting.as_str() { - "high" => { - i2c_address = BmpAddr::High as u8; - } - "low" => { - i2c_address = BmpAddr::Low as u8; - } - _ => { - //log::error!("sdo_setting must be either `high` or `low`"); - return Err(SensorError::ConfigError("invalid sdo_setting")); - } - } - - let mut driver = Self { - i2c_handle, - i2c_address, - calib: Default::default(), - }; - if driver.id() == 0x58 { - driver.get_calibration(); - } - - return Ok(Arc::new(Mutex::new(driver))); - } else { - //log::error!("sdo_setting attribute must be either `high` or `low`"); - return Err(SensorError::ConfigError( - "BMP280 missing sdo_setting attribute", - )); - } - } - - fn get_calibration(&mut self) { - let mut data: [u8; 24] = [0; 24]; - let _ = - self.i2c_handle - .write_read_i2c(self.i2c_address, &[Register::Calib as u8], &mut data); - - let mut calib = self.calib.borrow_mut(); - calib.dig_t1 = ((data[1] as u16) << 8) | (data[0] as u16); - calib.dig_t2 = ((data[3] as i16) << 8) | (data[2] as i16); - calib.dig_t3 = ((data[5] as i16) << 8) | (data[4] as i16); - calib.dig_p1 = ((data[7] as u16) << 8) | (data[6] as u16); - calib.dig_p2 = ((data[9] as i16) << 8) | (data[8] as i16); - calib.dig_p3 = ((data[11] as i16) << 8) | (data[10] as i16); - calib.dig_p4 = ((data[13] as i16) << 8) | (data[12] as i16); - calib.dig_p5 = ((data[15] as i16) << 8) | (data[14] as i16); - calib.dig_p6 = ((data[17] as i16) << 8) | (data[16] as i16); - calib.dig_p7 = ((data[19] as i16) << 8) | (data[18] as i16); - calib.dig_p8 = ((data[21] as i16) << 8) | (data[20] as i16); - calib.dig_p9 = ((data[23] as i16) << 8) | (data[22] as i16); - } - - /// Reads and returns pressure - pub fn pressure(&mut self) -> f64 { - let mut data: [u8; 6] = [0, 0, 0, 0, 0, 0]; - let _ = self.i2c_handle.write_read_i2c( - self.i2c_address, - &[Register::PressMsb as u8], - &mut data, - ); - let press = (data[0] as u32) << 12 | (data[1] as u32) << 4 | (data[2] as u32) >> 4; - - let mut calib = self.calib.borrow_mut(); - let mut var1 = ((calib.t_fine as f64) / 2.0) - 64000.0; - let mut var2 = var1 * var1 * (calib.dig_p6 as f64) / 32768.0; - var2 += var1 * (calib.dig_p5 as f64) * 2.0; - var2 = (var2 / 4.0) + ((calib.dig_p4 as f64) * 65536.0); - var1 = ((calib.dig_p3 as f64) * var1 * var1 / 524288.0 + (calib.dig_p2 as f64) * var1) - / 524288.0; - var1 = (1.0 + var1 / 32768.0) * (calib.dig_p1 as f64); - let mut pressure = 1048576.0 - (press as f64); - if var1 != 0.0 { - pressure = (pressure - (var2 / 4096.0)) * 6250.0 / var1; - var1 = (calib.dig_p9 as f64) * pressure * pressure / 2147483648.0; - var2 = pressure * (calib.dig_p8 as f64) / 32768.0; - pressure += (var1 + var2 + (calib.dig_p7 as f64)) / 16.0; - } - pressure - } - - /// Reads and returns pressure and resets control - fn pressure_one_shot(&mut self) -> f64 { - let pressure = self.pressure(); - self.set_control(Control { - osrs_t: Oversampling::x2, - osrs_p: Oversampling::x16, - mode: PowerMode::Forced, - }); - - pressure - } - - /// Reads and returns temperature - fn temp(&mut self) -> f64 { - let mut data: [u8; 6] = [0, 0, 0, 0, 0, 0]; - let _ = self.i2c_handle.write_read_i2c( - self.i2c_address, - &[Register::PressMsb as u8], - &mut data, - ); - let _pres = (data[0] as u32) << 12 | (data[1] as u32) << 4 | (data[2] as u32) >> 4; - let temp = (data[3] as u32) << 12 | (data[4] as u32) << 4 | (data[5] as u32) >> 4; - let mut calib = self.calib.borrow_mut(); - - let v1 = ((temp as f64) / 16384.0 - (calib.dig_t1 as f64) / 1024.0) * (calib.dig_t2 as f64); - let v2 = (((temp as f64) / 131072.0 - (calib.dig_t1 as f64) / 8192.0) - * ((temp as f64) / 131072.0 - (calib.dig_t1 as f64) / 8192.0)) - * (calib.dig_t3 as f64); - calib.t_fine = (v1 + v2) as i32; - - (v1 + v2) / 5120.0 - } - - /// Reads and returns temperature and resets control - fn temp_one_shot(&mut self) -> f64 { - let temp = self.temp(); - self.set_control(Control { - osrs_t: Oversampling::x2, - osrs_p: Oversampling::x16, - mode: PowerMode::Forced, - }); - - temp - } - - /// Returns current config - fn config(&mut self) -> Config { - let config = self.read_byte(Register::Config); - let t_sb = match (config & (0b111 << 5)) >> 5 { - x if x == Standby::ms0_5 as u8 => Standby::ms0_5, - x if x == Standby::ms62_5 as u8 => Standby::ms62_5, - x if x == Standby::ms125 as u8 => Standby::ms125, - x if x == Standby::ms250 as u8 => Standby::ms250, - x if x == Standby::ms500 as u8 => Standby::ms500, - x if x == Standby::ms1000 as u8 => Standby::ms1000, - x if x == Standby::ms2000 as u8 => Standby::ms2000, - x if x == Standby::ms4000 as u8 => Standby::ms4000, - _ => Standby::unknown, - }; - let filter = match (config & (0b111 << 2)) >> 2 { - x if x == Filter::off as u8 => Filter::off, - x if x == Filter::c2 as u8 => Filter::c2, - x if x == Filter::c4 as u8 => Filter::c4, - x if x == Filter::c8 as u8 => Filter::c8, - x if x == Filter::c16 as u8 => Filter::c16, - _ => Filter::unknown, - }; - Config { t_sb, filter } - } - - /// Sets configuration - fn set_config(&mut self, new: Config) { - let config: u8 = 0x00; - let t_sb = (new.t_sb as u8) << 5; - let filter = (new.filter as u8) << 2; - self.write_byte(Register::Config, config | t_sb | filter); - } - - /// Sets control - fn set_control(&mut self, new: Control) { - let osrs_t: u8 = (new.osrs_t as u8) << 5; - let osrs_p: u8 = (new.osrs_p as u8) << 2; - let control: u8 = osrs_t | osrs_p | (new.mode as u8); - self.write_byte(Register::CtrlMeas, control); - } - - /// Returns control - fn control(&mut self) -> Control { - let config = self.read_byte(Register::CtrlMeas); - let osrs_t = match (config & (0b111 << 5)) >> 5 { - x if x == Oversampling::skipped as u8 => Oversampling::skipped, - x if x == Oversampling::x1 as u8 => Oversampling::x1, - x if x == Oversampling::x2 as u8 => Oversampling::x2, - x if x == Oversampling::x4 as u8 => Oversampling::x4, - x if x == Oversampling::x8 as u8 => Oversampling::x8, - _ => Oversampling::x16, - }; - let osrs_p = match (config & (0b111 << 2)) >> 2 { - x if x == Oversampling::skipped as u8 => Oversampling::skipped, - x if x == Oversampling::x1 as u8 => Oversampling::x1, - x if x == Oversampling::x2 as u8 => Oversampling::x2, - x if x == Oversampling::x4 as u8 => Oversampling::x4, - x if x == Oversampling::x8 as u8 => Oversampling::x8, - _ => Oversampling::x16, - }; - let mode = match config & 0b11 { - x if x == PowerMode::Sleep as u8 => PowerMode::Sleep, - x if x == PowerMode::Forced as u8 => PowerMode::Forced, - x if x == PowerMode::Normal as u8 => PowerMode::Normal, - _ => PowerMode::Forced, - }; - - Control { - osrs_t, - osrs_p, - mode, - } - } - - /// Returns device status - fn status(&mut self) -> SensorStatus { - let status = self.read_byte(Register::Status); - SensorStatus { - measuring: 0 != (status & 0b00001000), - im_update: 0 != (status & 0b00000001), - } - } - - /// Returns device id - fn id(&mut self) -> u8 { - self.read_byte(Register::Id) - } - - /// Software reset, emulates POR - fn reset(&mut self) { - self.write_byte(Register::Reset, 0xB6); // Magic from documentation - } - - fn write_byte(&mut self, reg: Register, byte: u8) { - let mut buffer = [0]; - let _ = self - .i2c_handle - .write_read_i2c(self.i2c_address, &[reg as u8, byte], &mut buffer); - } - - fn read_byte(&mut self, reg: Register) -> u8 { - let mut data: [u8; 1] = [0]; - let _ = self - .i2c_handle - .write_read_i2c(self.i2c_address, &[reg as u8], &mut data); - data[0] - } -} - -/// Calibration manufacturer settings used to calculate temperature and pressure readings -#[derive(Default)] -struct Calibration { - dig_t1: u16, - dig_t2: i16, - dig_t3: i16, - t_fine: i32, - dig_p1: u16, - dig_p2: i16, - dig_p3: i16, - dig_p4: i16, - dig_p5: i16, - dig_p6: i16, - dig_p7: i16, - dig_p8: i16, - dig_p9: i16, -} - -#[derive(Debug, Copy, Clone)] -/// Control -pub struct Control { - /// Temperature oversampling - pub osrs_t: Oversampling, - /// Pressure oversampling - pub osrs_p: Oversampling, - /// Powermode - pub mode: PowerMode, -} - -#[derive(Debug, Copy, Clone)] -#[allow(non_camel_case_types)] -/// Standby time in ms -pub enum Standby { - /// ms0_5 - ms0_5 = 0b000, - /// ms62_5 - ms62_5 = 0b001, - /// ms125_5 - ms125 = 0b010, - /// ms250 - ms250 = 0b011, - /// ms500 - ms500 = 0b100, - /// ms1000 - ms1000 = 0b101, - /// ms2000 - ms2000 = 0b110, - /// ms4000 - ms4000 = 0b111, - /// unknown - unknown, -} - -#[derive(Debug, Copy, Clone)] -#[allow(non_camel_case_types)] -/// The time constant of IIR filter -pub enum Filter { - /// off - off = 0x00, - /// c2 - c2 = 0x01, - /// c4 - c4 = 0x02, - /// c8 - c8 = 0x03, - /// c16 - c16 = 0x04, - /// unknown - unknown, -} - -/// Configuration register, sets the rate, filter and interface options -/// of the device. Note that writing to this register while device in normal -/// mode may be ignored. Writes in sleep mode are not ignored. -/// -/// spi3w_en is intentionally left out of this implementation. -#[derive(Debug, Copy, Clone)] -pub struct Config { - /// Controls inactive duration in normal mode - pub t_sb: Standby, - /// Controls the time constant of IIR filter - pub filter: Filter, -} - -#[derive(Debug, Copy, Clone)] -pub struct SensorStatus { - /// measuring - measuring: bool, - /// im update - im_update: bool, -} - -#[derive(Debug, Copy, Clone)] -#[allow(non_camel_case_types)] -/// Oversampling -pub enum Oversampling { - /// skipped - skipped = 0b000, - /// x1 - x1 = 0b001, - /// x2 - x2 = 0b010, - /// x4 - x4 = 0b011, - /// x8 - x8 = 0b100, - /// x16 - x16 = 0b101, -} - -#[derive(Debug, Copy, Clone)] -/// PowerMode -pub enum PowerMode { - /// Sleep - Sleep = 0b00, - /// Forced - Forced = 0b01, - /// Normal - Normal = 0b11, -}