| // Copyright 2024 The Pigweed Authors |
| // |
| // 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 |
| // |
| // https://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. |
| #define PW_LOG_MODULE_NAME "BME688" |
| #define PW_LOG_LEVEL PW_LOG_LEVEL_WARN |
| |
| #include "device/bme688.h" |
| |
| #include <chrono> |
| #include <cstdint> |
| |
| #include "bme68x.h" |
| #include "pw_assert/check.h" |
| #include "pw_bytes/endian.h" |
| #include "pw_bytes/span.h" |
| #include "pw_function/function.h" |
| #include "pw_log/log.h" |
| #include "pw_span/span.h" |
| #include "pw_status/try.h" |
| #include "pw_thread/sleep.h" |
| |
| namespace sense { |
| |
| static constexpr pw::i2c::Address kAddress = |
| pw::i2c::Address::SevenBit<BME68X_I2C_ADDR_HIGH>(); |
| static constexpr uint16_t kHeaterTemperature = 300; |
| static constexpr uint16_t kHeaterDuration = 100; |
| static constexpr auto kTimeout = |
| pw::chrono::SystemClock::for_at_least(std::chrono::seconds(1)); |
| |
| static int8_t Write(uint8_t reg_address, |
| const uint8_t* data, |
| uint32_t length, |
| void* context) { |
| PW_LOG_INFO("Write(reg_address=0x%02x, data=%p, length=%u, context=%p)", |
| reg_address, |
| static_cast<const void*>(data), |
| length, |
| context); |
| auto i2c_device = static_cast<pw::i2c::RegisterDevice*>(context); |
| |
| std::array<std::byte, 16> write_buffer; |
| pw::span<const uint8_t> bytes(data, length); |
| auto status = |
| i2c_device->WriteRegisters8(reg_address, bytes, write_buffer, kTimeout); |
| PW_LOG_INFO("WriteRegisters8 returned %s", pw_StatusString(status)); |
| return status.ok() ? 0 : 1; |
| } |
| |
| static int8_t Read(uint8_t reg_address, |
| uint8_t* data, |
| uint32_t length, |
| void* context) { |
| PW_LOG_INFO("Read(reg_address=0x%02x, data=%p, length=%u, context=%p)", |
| reg_address, |
| static_cast<const void*>(data), |
| length, |
| context); |
| auto i2c_device = static_cast<pw::i2c::RegisterDevice*>(context); |
| |
| pw::span<uint8_t> read_buffer(data, length); |
| auto status = i2c_device->ReadRegisters8(reg_address, read_buffer, kTimeout); |
| PW_LOG_INFO("ReadRegisters8 returned %s", pw_StatusString(status)); |
| return status.ok() ? 0 : 1; |
| } |
| |
| static void Delay(uint32_t interval_us, void* context) { |
| PW_LOG_INFO("Delay(interval_us=%u, context=%p)", interval_us, context); |
| auto interval = pw::chrono::SystemClock::for_at_least( |
| std::chrono::microseconds(interval_us)); |
| pw::this_thread::sleep_for(interval); |
| } |
| |
| Bme688::Bme688(pw::i2c::Initiator& initiator, Worker& worker) |
| : AirSensor(), |
| worker_(worker), |
| i2c_device_(initiator, |
| kAddress, |
| pw::endian::native, |
| pw::i2c::RegisterAddressSize::k1Byte), |
| get_data_(pw::bind_member<&Bme688::GetDataCallback>(this)) {} |
| |
| pw::Status Bme688::DoInit() { |
| bme688_.intf_ptr = &i2c_device_; |
| bme688_.intf = bme68x_intf::BME68X_I2C_INTF; |
| bme688_.read = Read; |
| bme688_.write = Write; |
| bme688_.delay_us = Delay; |
| bme688_.amb_temp = 21; // Celsius, approximately 70 degrees Fahrenheit. |
| |
| PW_LOG_INFO("bme68x_init"); |
| auto init_status = Check(bme68x_init(&bme688_)); |
| if (!init_status.ok()) { |
| return init_status; |
| } |
| |
| PW_LOG_INFO("bme68x_get_conf"); |
| auto get_conf_status = Check(bme68x_get_conf(&config_, &bme688_)); |
| if (!get_conf_status.ok()) { |
| return get_conf_status; |
| } |
| |
| config_.filter = BME68X_FILTER_OFF; |
| config_.odr = BME68X_ODR_NONE; |
| config_.os_hum = BME68X_OS_16X; |
| config_.os_pres = BME68X_OS_1X; |
| config_.os_temp = BME68X_OS_2X; |
| |
| PW_LOG_INFO("bme68x_set_conf"); |
| auto set_conf_status = Check(bme68x_set_conf(&config_, &bme688_)); |
| if (!set_conf_status.ok()) { |
| return set_conf_status; |
| } |
| |
| return pw::OkStatus(); |
| } |
| |
| pw::Status Bme688::DoMeasure(pw::sync::ThreadNotification& notification) { |
| get_data_.Cancel(); |
| { |
| std::lock_guard lock(lock_); |
| if (notification_ != nullptr) { |
| notification_->release(); |
| } |
| notification_ = ¬ification; |
| } |
| |
| heater_.enable = BME68X_ENABLE; |
| heater_.heatr_temp = kHeaterTemperature; |
| heater_.heatr_dur = kHeaterDuration; |
| PW_TRY(Check(bme68x_set_heatr_conf(BME68X_FORCED_MODE, &heater_, &bme688_))); |
| PW_TRY(Check(bme68x_set_op_mode(BME68X_FORCED_MODE, &bme688_))); |
| |
| worker_.RunOnce([this]() { |
| uint32_t delay_us = |
| bme68x_get_meas_dur(BME68X_FORCED_MODE, &config_, &bme688_); |
| delay_us += (heater_.heatr_dur * 1000); |
| auto delay = pw::chrono::SystemClock::for_at_least( |
| std::chrono::microseconds(delay_us)); |
| get_data_.InvokeAfter(delay); |
| }); |
| return pw::OkStatus(); |
| } |
| |
| void Bme688::GetDataCallback(pw::chrono::SystemClock::time_point) { |
| bme68x_data data; |
| uint8_t n; |
| if (Check(bme68x_get_data(BME68X_FORCED_MODE, &data, &n, &bme688_)).ok() && |
| n != 0) { |
| Update(data.temperature, data.pressure, data.humidity, data.gas_resistance); |
| } |
| std::lock_guard lock(lock_); |
| notification_->release(); |
| notification_ = nullptr; |
| } |
| |
| pw::Status Bme688::Check(int8_t result) { |
| switch (result) { |
| case BME68X_OK: |
| return pw::OkStatus(); |
| |
| case BME68X_E_NULL_PTR: |
| PW_LOG_ERROR("Null pointer"); |
| return pw::Status::InvalidArgument(); |
| |
| case BME68X_E_COM_FAIL: |
| PW_LOG_ERROR("Communication failure"); |
| return pw::Status::Unavailable(); |
| |
| case BME68X_E_INVALID_LENGTH: |
| PW_LOG_ERROR("Incorrect length parameter"); |
| return pw::Status::OutOfRange(); |
| |
| case BME68X_E_DEV_NOT_FOUND: |
| PW_LOG_ERROR("Device not found"); |
| return pw::Status::NotFound(); |
| |
| case BME68X_E_SELF_TEST: |
| PW_LOG_ERROR("Self test error"); |
| return pw::Status::FailedPrecondition(); |
| |
| case BME68X_W_NO_NEW_DATA: |
| PW_LOG_WARN("No new data found"); |
| return pw::OkStatus(); |
| |
| default: |
| PW_LOG_ERROR("Unknown error code: %d", result); |
| return pw::Status::Unknown(); |
| } |
| } |
| |
| } // namespace sense |