blob: 51d1c91ade0d347e4937b26a6a18dd4a4c02b55d [file] [log] [blame]
// 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_ = &notification;
}
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