/*
 * SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
 *
 * SPDX-License-Identifier: MIT
 */
/*!
  @file unit_BME688.hpp
  @brief BME688 Unit for M5UnitUnified
*/
#ifndef M5_UNIT_ENV_UNIT_BME688_HPP
#define M5_UNIT_ENV_UNIT_BME688_HPP

#include <M5UnitComponent.hpp>
#include <m5_utility/stl/extension.hpp>

#if defined(ARDUINO)
#include <bme68xLibrary.h>
#else
#include <bme68x/bme68x.h>
#endif

#if defined(CONFIG_IDF_TARGET_ESP32C6)
#pragma message "Not using bsec2"

#else
#pragma message "Using bsec2"
#define UNIT_BME688_USING_BSEC2

#if defined(ARDUINO)
#include <bsec2.h>
#else
#include <inc/bsec_datatypes.h>
#endif

#endif

#include <memory>
#include <limits>
#include <initializer_list>

namespace m5 {
namespace unit {

namespace bme688 {

/*!
  @enum Mode
  @brief Operation mode same as BME68X_xxx_MODE
 */
enum class Mode : uint8_t {
    Sleep,       //!<  No measurements are performed
    Forced,      //!< Single TPHG cycle is performed
    Parallel,    //!< Multiple TPHG cycles are performed
    Sequential,  //!< Sequential mode is similar as forced mode, but it provides
                 //!< temperature, pressure, humidity one by one
};

///@name Aliases for bme68x structures
///@{
/*!
  @typedef bme68xData
  @brief Raw data
 */
using bme68xData = struct bme68x_data;
/*!
  @typedef bme68xDev
  @brief bme68x device
 */
using bme68xDev = struct bme68x_dev;
/*!
  @typedef bme68xConf
  @brief Setting for temperature, pressure, humidity...
*/
using bme68xConf = struct bme68x_conf;
/*!
  @struct bme68xHeatrConf
  @brief Setting for gas heater
  @note Always valid pointers in derivation
*/
struct bme68xHeatrConf : bme68x_heatr_conf {
    uint16_t temp_prof[10]{};
    uint16_t dur_prof[10]{};
    bme68xHeatrConf() : bme68x_heatr_conf()
    {
        heatr_temp_prof = temp_prof;
        heatr_dur_prof  = dur_prof;
    }
};
/*!
  @typedef bme68xCalibration
  @brief Calibration parameters
 */
using bme68xCalibration = struct bme68x_calib_data;
///@}

/*!
  @enum Oversamping
  @brief Sampling setting
 */
enum class Oversampling : uint8_t {
    None,  //!< Skipped
    x1,    //!< x1
    x2,    //!< x2
    x4,    //!< x4
    x8,    //!< x8
    x16,   //!< x16
};

/*!
  @enum Filter
  @brief IIR Filter setting
 */
enum class Filter : uint8_t {
    None,
    Coeff_1,
    Coeff_3,
    Coeff_7,
    Coeff_15,
    Coeff_31,
    Coeff_63,
    Coeff_127,
};

/*!
  @enum ODR
  @brief bme68xConf::odr settings (standbytime Unit:ms)
 */
enum class ODR : uint8_t {
    MS_0_59,  //< 0.59 ms
    MS_62_5,  //< 62.5 ms
    MS_125,   //< 125 ms
    MS_250,   //!< 250 ms
    MS_500,   //!< 500 ms
    MS_1000,  //!< 1000 ms
    MS_10,    //!< 10 ms
    MS_20,    //!< 20 ms
    None,     //!< No standbytime
};

/*!
  @struct GasWait
  @brief GasSensor heater-on time
  @note Forced mode and Parallel mode are interpreted differently
 */
struct GasWait {
    GasWait() : value{0}
    {
    }
    /*!
      @enum Factor
      @brief Multiplier in Forced mode
     */
    enum class Factor { x1, x4, x16, x64 };
    ///@name Getter
    ///@{
    inline uint8_t step() const
    {
        return value & 0x3F;
    }
    inline Factor factor() const
    {
        return static_cast<Factor>((value >> 6) & 0x03);
    }
    ///@}

    ///@name Setter
    ///@{
    inline void step(const uint8_t s)
    {
        value = (value & ~0x3F) | (s & 0x3F);
    }
    inline void factor(const Factor f)
    {
        value = (value & ~(0x03 << 6)) | (m5::stl::to_underlying(f) << 6);
    }
    ///@}

    //! @brief Conversion from duration to register value for Force/Sequencial
    //! mode
    static uint8_t from(const uint16_t duration)
    {
        uint8_t f{};
        uint16_t d{duration};
        while (d > 0x3F) {
            d >>= 2;
            ++f;
        }
        return (f <= 0x03) ? ((uint8_t)d | (f << 6)) : 0xFF;
    }
    //! @brief Conversion from register value to duration for Force/Sequencial
    //! mode
    static uint16_t to(const uint8_t v)
    {
        constexpr uint16_t tbl[] = {1, 4, 16, 64};
        return (v & 0x3F) * tbl[(v >> 6) & 0x03];
    }

    uint8_t value{};  //!< Use the value as it is in parallel mode
};

#if defined(UNIT_BME688_USING_BSEC2)
namespace bsec2 {

/*!
  @enum SampleRate
  @brief Sample rate for BSEC2
 */
enum class SampleRate : uint8_t {
    Disabled,                          //!< Sample rate of a disabled sensor
    LowPower,                          //!< Sample rate in case of Low Power Mode (0.33Hz) interval 3 sec.
    UltraLowPower,                     //!< Sample rate in case of Ultra Low Power Mode (3.3 mHz) interval 300 sec.
    UltraLowPowerMeasurementOnDemand,  //!< Input value used to trigger an extra measurment (ULP plus) (0.33Hz
                                       //!< [T,p,h] 3.3mHz [IAQ])
    Scan,                              //!< Sample rate in case of scan mode (1/10.8 s)
    Continuous,                        //!< Sample rate in case of Continuous Mode (1Hz) interval 1 sec.
};

///@cond
template <typename T>
void is_bsec_virtual_sensor_t()
{
    static_assert(std::is_same<T, bsec_virtual_sensor_t>::value, "Argument must be of type bsec_virtual_sensor_t");
}
///@endcond

//! @brief  Conversion from BSEC2 subscription array to bits
inline uint32_t virtual_sensor_array_to_bits(const bsec_virtual_sensor_t* ss, const size_t len)
{
    uint32_t ret{};
    for (size_t i = 0; i < len; ++i) {
        ret |= ((uint32_t)1U) << ss[i];
    }
    return ret;
}

/*!
  @brief Make subscribe bits from bsec_virtual_sensor_t
 */
template <typename... Args>
uint32_t subscribe_to_bits(Args... args)
{
    // In a C++17 or later environment, it can be written like this...
    // static_assert(std::conjunction<std::is_same<Args, bsec_virtual_sensor_t>...>::value,
    //          "All arguments must be of type bsec_virtual_sensor_t");
    int discard[] = {(is_bsec_virtual_sensor_t<Args>(), 0)...};
    (void)discard;

    bsec_virtual_sensor_t tmp[] = {args...};
    constexpr size_t n          = sizeof...(args);
    return virtual_sensor_array_to_bits(tmp, n);
}

}  // namespace bsec2
#endif

/*!
  @struct Data
  @brief Measurement data group
 */
struct Data {
    bme688::bme68xData raw{};
#if defined(UNIT_BME688_USING_BSEC2)
    bsecOutputs raw_outputs{};

    float get(const bsec_virtual_sensor_t vs) const;
    inline float iaq() const
    {
        return get(BSEC_OUTPUT_IAQ);
    }
    inline float static_iaq() const
    {
        return get(BSEC_OUTPUT_STATIC_IAQ);
    }
    inline float co2() const
    {
        return get(BSEC_OUTPUT_CO2_EQUIVALENT);
    }
    inline float voc() const
    {
        return get(BSEC_OUTPUT_BREATH_VOC_EQUIVALENT);
    }
    inline float temperature() const
    {
        return get(BSEC_OUTPUT_RAW_TEMPERATURE);
    }
    inline float pressure() const
    {
        return get(BSEC_OUTPUT_RAW_PRESSURE);
    }
    inline float humidity() const
    {
        return get(BSEC_OUTPUT_RAW_HUMIDITY);
    }
    inline float gas() const
    {
        return get(BSEC_OUTPUT_RAW_GAS);
    }
    inline bool gas_stabilization() const
    {
        return get(BSEC_OUTPUT_STABILIZATION_STATUS) == 1.0f;
    }
    inline bool gas_run_in_status() const
    {
        return get(BSEC_OUTPUT_RUN_IN_STATUS) == 1.0f;
    }
    inline float heat_compensated_temperature() const
    {
        return get(BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE);
    }
    inline float heat_compensated_humidity() const
    {
        return get(BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY);
    }
    inline float gas_percentage() const
    {
        return get(BSEC_OUTPUT_GAS_PERCENTAGE);
    }
    inline float gas_estimate_1() const
    {
        return get(BSEC_OUTPUT_GAS_ESTIMATE_1);
    }
    inline float gas_estimate_2() const
    {
        return get(BSEC_OUTPUT_GAS_ESTIMATE_2);
    }
    inline float gas_estimate_3() const
    {
        return get(BSEC_OUTPUT_GAS_ESTIMATE_3);
    }
    inline float gas_estimate_4() const
    {
        return get(BSEC_OUTPUT_GAS_ESTIMATE_4);
    }
    inline uint32_t gas_index() const
    {
        return get(BSEC_OUTPUT_RAW_GAS_INDEX);
    }
    inline float regression_estimate_1() const
    {
        return get(BSEC_OUTPUT_REGRESSION_ESTIMATE_1);
    }
    inline float regression_estimate_2() const
    {
        return get(BSEC_OUTPUT_REGRESSION_ESTIMATE_2);
    }
    inline float regression_estimate_3() const
    {
        return get(BSEC_OUTPUT_REGRESSION_ESTIMATE_3);
    }
    inline float regression_estimate_4() const
    {
        return get(BSEC_OUTPUT_REGRESSION_ESTIMATE_4);
    }
#endif
    inline float raw_temperature() const
    {
        return raw.temperature;
    }
    inline float raw_pressure() const
    {
        return raw.pressure;
    }
    inline float raw_humidity() const
    {
        return raw.humidity;
    }
    inline float raw_gas() const
    {
        return raw.gas_resistance;
    }
};

}  // namespace bme688

/*!
  @class UnitBME688
  @brief BME688 unit
  @note Using config/bme688/bme688_sel_33v_3s_4d/bsec_selectivity.txt for default configuration
  @note If other settings are used, call bsec2SetConfig
 */
class UnitBME688 : public Component, public PeriodicMeasurementAdapter<UnitBME688, bme688::Data> {
    M5_UNIT_COMPONENT_HPP_BUILDER(UnitBME688, 0x77);

public:
    /*!
      @struct config_t
      @brief Settings for begin
     */
    struct config_t {
        //! Start periodic measurement on begin?
        bool start_periodic{true};
        //! ambient temperature
        int8_t ambient_temperature{25};
#if defined(UNIT_BME688_USING_BSEC2) || defined(DOXYGEN_PROCESS)
        ///@name  Exclude NanoC6
        ///@{
        /*! @brief Subscribe BSEC2 sensors bits if start on begin */
        uint32_t subscribe_bits{1U << BSEC_OUTPUT_IAQ | 1U << BSEC_OUTPUT_RAW_TEMPERATURE |
                                1U << BSEC_OUTPUT_RAW_PRESSURE | 1U << BSEC_OUTPUT_RAW_HUMIDITY |
                                1U << BSEC_OUTPUT_RAW_GAS | 1U << BSEC_OUTPUT_STABILIZATION_STATUS |
                                1U << BSEC_OUTPUT_RUN_IN_STATUS};
        /*!
          @brief Sampling rate for BSEC2 if start on begin
          @warning Depending on the value specified for the sample rate, appropriate configuration settings may be
          required in advance.
        */
        bme688::bsec2::SampleRate sample_rate{bme688::bsec2::SampleRate::LowPower};
#endif
#if !defined(UNIT_BME688_USING_BSEC2) || defined(DOXYGEN_PROCESS)
        ///@name Only Nano6
        ///@{
        /*! @brief Measurement mode if start on begin */
        bme688::Mode mode{bme688::Mode::Forced};
        //! Temperature oversampling if start on begin
        bme688::Oversampling oversampling_temperature{bme688::Oversampling::x2};
        //! Pressure oversampling if start on begin
        bme688::Oversampling oversampling_pressure{bme688::Oversampling::x1};
        //! Humidity oversampling if start on begin
        bme688::Oversampling oversampling_humidity{bme688::Oversampling::x16};
        //! Filter coefficient if start on begin
        bme688::Filter filter{bme688::Filter::None};
        //! Standby time between sequential mode measurement profiles if start on begin
        bme688::ODR odr{bme688::ODR::None};
        //! Enable gas measurement if start on begin
        bool heater_enable{true};
        //! The heater temperature for forced mode degree Celsius if start on begin
        uint16_t heater_temperature{300};
        //! The heating duration for forced mode in milliseconds if start on begin
        uint16_t heater_duration{100};
        ///@}
#endif
    };

    ///@name Configuration for begin
    ///@{
    /*! @brief Gets the configration */
    inline config_t config()
    {
        return _cfg;
    }
    //! @brief Set the configration
    inline void config(const config_t& cfg)
    {
        _cfg = cfg;
    }
    ///@}

    explicit UnitBME688(const uint8_t addr = DEFAULT_ADDRESS);
    virtual ~UnitBME688()
    {
    }

    virtual bool begin() override;
    virtual void update(const bool force = false) override;

    ///@name Properties
    ///@{
    //! @brief Current mode
    inline bme688::Mode mode() const
    {
        return _mode;
    }
    //!@brief Gets the Calibration
    inline const bme688::bme68xCalibration& calibration() const
    {
        return _dev.calib;
    }
    //! @brief Gets the TPH setting
    inline const bme688::bme68xConf& tphSetting() const
    {
        return _tphConf;
    }
    //! @brief Gets the heater setiing
    inline const bme688::bme68xHeatrConf& heaterSetting() const
    {
        return _heaterConf;
    }
    //! @brief Gets the ambient temperature
    inline int8_t ambientTemperature() const
    {
        return _dev.amb_temp;
    }
    ///@}

    ///@name Measurement data by periodic
    ///@{
#if defined(UNIT_BME688_USING_BSEC2)
    /*!
      @brief Oldest measured IAQ
      @warning Exclude NanoC6
    */
    inline float iaq() const
    {
        return !empty() ? oldest().iaq() : std::numeric_limits<float>::quiet_NaN();
    }
    //! @brief Oldest measured temperature (Celsius)
    inline float temperature() const
    {
        return !empty() ? oldest().temperature() : std::numeric_limits<float>::quiet_NaN();
    }
    //! @brief Oldest measured pressure (Pa)
    inline float pressure() const
    {
        return !empty() ? oldest().pressure() : std::numeric_limits<float>::quiet_NaN();
    }
    //! @brief Oldest measured humidity (%)
    inline float humidity() const
    {
        return !empty() ? oldest().humidity() : std::numeric_limits<float>::quiet_NaN();
    }
    //! @brief Oldest measured gas (Ohm)
    inline float gas() const
    {
        return !empty() ? oldest().gas() : std::numeric_limits<float>::quiet_NaN();
    }
#else
    //! @brief Oldest measured temperature (Celsius)
    inline float temperature() const
    {
        return !empty() ? oldest().raw_temperature() : std::numeric_limits<float>::quiet_NaN();
    }
    //! @brief Oldest measured pressure (Pa)
    inline float pressure() const
    {
        return !empty() ? oldest().raw_pressure() : std::numeric_limits<float>::quiet_NaN();
    }
    //! @brief Oldest measured humidity (%)
    inline float humidity() const
    {
        return !empty() ? oldest().raw_humidity() : std::numeric_limits<float>::quiet_NaN();
    }
    //! @brief Oldest measured gas (Ohm)
    inline float gas() const
    {
        return !empty() ? oldest().raw_gas() : std::numeric_limits<float>::quiet_NaN();
    }
#endif
    ///@}

#if 0
    /*!
      @brief Number of valid latest raw data
      @return between 0 and 3
      @note 0 or 1 in Forced mode
    */
    inline uint8_t numberOfRawData() const
    {
        return _num_of_data;
    }
    /*!
      @brief Obtaining the specified latest raw data pointer
      @param idx What number of the data
      @retval != nullptr Data pointer
      @retval == nullptr Failed
      @warning If it is not a valid range, nullptr is returned
     */
    inline const bme688::bme68xData* data(const uint8_t idx)
    {
        return (idx < _num_of_data) ? &_raw_data[idx] : nullptr;
    }
#endif

    //! @brief Sets the ambient temperature
    inline void setAambientTemperature(const int8_t temp)
    {
        _dev.amb_temp = temp;
    }
    /*!
      @brief Read calibration
      @param[out] c output value
      @return True if successful
     */
    bool readCalibration(bme688::bme68xCalibration& c);
    /*!
      @brief write calibration
      @param c Calibration parameter
      @return True if successful
     */
    bool writeCalibration(const bme688::bme68xCalibration& c);
    /*!
      @brief Calculation of measurement intervals without heater
      @return interval time (Unit: us)
    */
    uint32_t calculateMeasurementInterval(const bme688::Mode mode, const bme688::bme68xConf& s);
    /*!
      @brief Read unique ID
      @param[out] id output value
      @return True if successful
    */
    bool readUniqueID(uint32_t& id);
    /*!
      @brief Software reset
      @return True if successful
     */
    bool softReset();
    /*!
      @brief Self-test
      @return True if successful
    */
    bool selfTest();
    /*!
      @brief Write operation mode
      @param m Mode
      @return True if successful
     */
    bool writeMode(const bme688::Mode m);
    /*!
      @brief Read operation mode
      @param[out] m Mode
      @return True if successful
     */
    bool readMode(bme688::Mode& m);

    ///@name TPH(Temperature,Pressure,Humidity)
    ///@{
    /*!
      @brief Read TPH setting
      @param[out] s output value
      @return True if successful
    */
    bool readTPHSetting(bme688::bme68xConf& s);
    /*!
      @brief Read temperature oversampling
      @param[out] os output value
      @return True if successful
     */
    bool readOversamplingTemperature(bme688::Oversampling& os);
    /*!
      @brief Read pressure oversampling
      @param[out] os output value
      @return True if successful
     */
    bool readOversamplingPressure(bme688::Oversampling& os);
    /*!
      @brief Read IIRFilter
      @param[out] f output value
      @return True if successful
     */
    bool readIIRFilter(bme688::Filter& f);
    /*!
      @brief Read humidity oversampling
      @param[out] os output value
      @return True if successful
     */
    bool readOversamplingHumidity(bme688::Oversampling& os);
    /*!
      @brief Write TPH setting
      @param s Setting
      @return True if successful
    */
    bool writeTPHSetting(const bme688::bme68xConf& s);
    /*!
      @brief Wite oversamplings
      @param t oversampling for temperature
      @param p oversampling for pressure
      @param h oversampling for humidity
    */
    bool writeOversampling(const bme688::Oversampling t, const bme688::Oversampling p, const bme688::Oversampling h);
    /*!
      @brief Write temperature oversampling
      @param os enum value
      @return True if successful
     */
    bool writeOversamplingTemperature(const bme688::Oversampling os);
    /*!
      @brief Write pressure oversampling
      @param os enum value
      @return True if successful
     */
    bool writeOversamplingPressure(const bme688::Oversampling os);
    /*!
      @brief Write humidity oversampling
      @param os enum value
      @return True if successful
     */
    bool writeOversamplingHumidity(const bme688::Oversampling os);
    /*!
      @brief Write IIRFilter
      @param[out] f enum value
      @return True if successful
    */
    bool writeIIRFilter(const bme688::Filter f);
    ///@}

    ///@name Heater
    ///@{
    /*!
      @brief Read heater setting
      @param hs Setting
      @return True if successful
      @warning Only heatr_dur_prof and heatr_temp_prof can be obtained
     */
    bool readHeaterSetting(bme688::bme68xHeatrConf& hs);
    /*!
      @brief Write heater setting
      @param mode Expected operation mode of the sensor
      @param hs Setting
      @return True if successful
     */
    bool writeHeaterSetting(const bme688::Mode mode, const bme688::bme68xHeatrConf& hs);
    ///@}

    ///@name Periodic measurement
    ///@{
    /*!
      @brief Start periodic measurement without BSEC2
      @param m Mode for measurement
      @return True if successful
      @pre Calibration,TPH and heater must already be set up
      @warning Measurement intervals are not constant in Parallel mode
    */
    inline bool startPeriodicMeasurement(const bme688::Mode m)
    {
        return PeriodicMeasurementAdapter<UnitBME688, bme688::Data>::startPeriodicMeasurement(m);
    }
#if defined(UNIT_BME688_USING_BSEC2) || defined(DOXYGEN_PROCESS)
    /*!
      @brief Start periodic measurement using BSEC2
      @param subscribe_bits Measurement type bits
      @return True if successful
      @warning Not available for NanoC6
    */
    inline bool startPeriodicMeasurement(const uint32_t subscribe_bits,
                                         const bme688::bsec2::SampleRate sr = bme688::bsec2::SampleRate::LowPower)
    {
        return PeriodicMeasurementAdapter<UnitBME688, bme688::Data>::startPeriodicMeasurement(subscribe_bits, sr);
    }
    /*!
      @brief Start periodic measurement using BSEC2
      @param ss Array of requested virtual sensor (output) configurations for the library
      @param len Number of array elements
      @return True if successful
      @warning Not available for NanoC6
    */
    inline bool startPeriodicMeasurement(const bsec_virtual_sensor_t* ss, const size_t len,
                                         const bme688::bsec2::SampleRate sr = bme688::bsec2::SampleRate::LowPower)
    {
        return ss ? startPeriodicMeasurement(bme688::bsec2::virtual_sensor_array_to_bits(ss, len), sr) : false;
    }

#endif
    /*!
      @brief Stop periodic measurement
      @return True if successful
    */
    inline bool stopPeriodicMeasurement()
    {
        return PeriodicMeasurementAdapter<UnitBME688, bme688::Data>::stopPeriodicMeasurement();
    }
    ///@}

    ///@name Single shot measurement
    ///@{
    /*!
      @brief Take a single measurement
      @param[out] data output value
      @return True if successful
      @pre Calibration,TPH and heater must already be set up
      @note Measure once by Force mode
      @note Blocked until it can be measured.
    */
    bool measureSingleShot(bme688::bme68xData& data);
    ///@}

#if defined(UNIT_BME688_USING_BSEC2) || defined(DOXYGEN_PROCESS)
    ///@warning Not available for NanoC6
    ///@name Bosch BSEC2 wrapper
    ///@{
    /*!
      @brief Gets the temperature offset(Celsius)
     */
    float bsec2GetTemperatureOffset() const
    {
        return _temperatureOffset;
    }
    /*!
      @brief Set the temperature offset(Celsius)
      @param offset offset value
     */
    void bsec2SetTemperatureOffset(const float offset)
    {
        _temperatureOffset = offset;
    }
    /*!
      @brief Gets the BSEC2 library version
      @return reference of the version structure
      @warning Call after begin
    */
    const bsec_version_t& bsec2Version() const
    {
        return _bsec2_version;
    }
    /*!
      @brief Update algorithm configuration parameters
      Update bsec2 configuration settings
      @param cfg Settings serialized to a binary blob
      @param sz size of cfg
      @return True if successful
      @note See also BSEC2 src/config/bme688 for configuration data
    */
    bool bsec2SetConfig(const uint8_t* cfg, const size_t sz = BSEC_MAX_PROPERTY_BLOB_SIZE);
    /*!
      @brief Retrieve the current library configuration
      @param[out] cfg Buffer to hold the serialized config blob
      @param[out] actualSize Actual size of the returned serialized configuration blob
      @return True if successful
      @warning cfg size must be greater than or equal to BSEC_MAX_PROPERTY_BLOB_SIZE
    */
    bool bsec2GetConfig(uint8_t* cfg, uint32_t& actualSize);
    /*!
      @brief Restore the internal state
      @param state pointer of the state array
      @return True if successful
    */
    bool bsec2SetState(const uint8_t* state);
    /*!
      @brief Retrieve the current internal library state
      @param[out] state Buffer to hold the serialized state blob
      @param[out] actualSize Actual size of the returned serialized blob
      @return True if successful
      @warning cfg size must be greater than or equal to BSEC_MAX_STATE_BLOB_SIZE
    */
    bool bsec2GetState(uint8_t* state, uint32_t& actualSize);
    /*!
      @brief Subscribe to library virtual sensors outputs
      @param sensorBits Requested virtual sensor (output) configurations for the library
      @param sr Sample rate
      @return True if successful
      @warning Depending on the value specified for the sample rate, appropriate configuration settings may be
      required in advance.
    */
    bool bsec2UpdateSubscription(const uint32_t sensorBits, const bme688::bsec2::SampleRate sr);
    /*!
      @brief Subscribe to library virtual sensors outputs
      @param ss Array of requested virtual sensor (output) configurations for the library
      @param len Number of array elements
      @param sr Sample rate
      @return True if successful
      @warning Depending on the value specified for the sample rate, appropriate configuration settings may be
      required in advance.
    */
    inline bool bsec2UpdateSubscription(const bsec_virtual_sensor_t* ss, const size_t len,
                                        const bme688::bsec2::SampleRate sr)
    {
        return bsec2UpdateSubscription(bme688::bsec2::virtual_sensor_array_to_bits(ss, len), sr);
    }
    /*!
      @brief is virtual sensor Subscribed?
      @param id virtual sensor (output)
      @return True if subscribed
    */
    inline bool bsec2IsSubscribed(const bsec_virtual_sensor_t id)
    {
        return _bsec2_subscription & (1U << id);
    }
    /*!
      @brief Gets the subscription bits
      @return Subscribed sensor id bits
      @note If BSEC_OUTPUT_IAQ is subscribed, then bit 2 (means 1U << BSEC_OUTPUT_IAQ) is 1
     */
    uint32_t bsec2Subscription() const
    {
        return _bsec2_subscription;
    }
    /*!
      @brief Subscribe virtual sensor
      @param id virtual sensor (output)
      @return True if successful
      @note SampleRate uses the value that is currently set or has been set in the past
    */
    bool bsec2Subscribe(const bsec_virtual_sensor_t id);
    /*!
      @brief Unsubscribe virtual sensor
      @param id virtual sensor (output)
      @return True if successful
    */
    bool bsec2Unsubscribe(const bsec_virtual_sensor_t id);
    /*!
      @brief Unsubacribe currentt all sensors
      @return True if successful
     */
    bool bsec2UnsubscribeAll();
///@}
#endif

protected:
    static int8_t read_function(uint8_t reg_addr, uint8_t* reg_data, uint32_t length, void* intf_ptr);
    static int8_t write_function(uint8_t reg_addr, const uint8_t* reg_data, uint32_t length, void* intf_ptr);

    bool start_periodic_measurement(const bme688::Mode m);
    bool stop_periodic_measurement();
#if defined(UNIT_BME688_USING_BSEC2)
    bool start_periodic_measurement(const uint32_t subscribe_bits, const bme688::bsec2::SampleRate sr);
#endif

    bool write_mode_forced();
    bool write_mode_parallel();
    bool fetch_data();

    void update_bme688(const bool force);
    bool read_measurement();
#if defined(UNIT_BME688_USING_BSEC2)
    bool process_data(bsecOutputs& outouts, const int64_t ns, const bme688::bme68xData& data);
    void update_bsec2(const bool force);
#endif

    inline virtual bool in_periodic() const override
    {
        return _periodic || (_bsec2_subscription != 0);
    }

    M5_UNIT_COMPONENT_PERIODIC_MEASUREMENT_ADAPTER_HPP_BUILDER(UnitBME688, bme688::Data);

protected:
    bme688::Mode _mode{bme688::Mode::Sleep};

    // bme68x
    bme688::bme68xData _raw_data[3]{};  // latest data
    uint8_t _num_of_data{};
    bme688::bme68xDev _dev{};
    bme688::bme68xConf _tphConf{};
    bme688::bme68xHeatrConf _heaterConf{};

    // BSEC2
    uint32_t _bsec2_subscription{};  // Enabled virtual sensor bit

#if defined(UNIT_BME688_USING_BSEC2)
    bsec_version_t _bsec2_version{};
    std::unique_ptr<uint8_t> _bsec2_work{};
    bsec_bme_settings_t _bsec2_settings{};

    bme688::Mode _bsec2_mode{};
    bme688::bsec2::SampleRate _bsec2_sr{};

    bsecOutputs _outputs{};
    float _temperatureOffset{};
#endif

    std::unique_ptr<m5::container::CircularBuffer<bme688::Data>> _data{};

    bool _waiting{};
    types::elapsed_time_t _can_measure_time{};

    config_t _cfg{};
};

///@cond 0
namespace bme688 {
namespace command {
constexpr uint8_t CHIP_ID{0xD0};
constexpr uint8_t RESET{0xE0};
constexpr uint8_t VARIANT_ID{0xF0};

constexpr uint8_t IDAC_HEATER_0{0x50};  // ...9
constexpr uint8_t RES_HEAT_0{0x5A};     // ...9
constexpr uint8_t GAS_WAIT_0{0x64};     // ...9
constexpr uint8_t GAS_WAIT_SHARED{0x6E};

constexpr uint8_t CTRL_GAS_0{0x70};
constexpr uint8_t CTRL_GAS_1{0x71};
constexpr uint8_t CTRL_HUMIDITY{0x72};
constexpr uint8_t CTRL_MEASUREMENT{0x74};
constexpr uint8_t CONFIG{0x75};

constexpr uint8_t MEASUREMENT_STATUS_0{0x1D};
constexpr uint8_t MEASUREMENT_STATUS_1{0x2E};
constexpr uint8_t MEASUREMENT_STATUS_2{0x3F};

constexpr uint8_t MEASUREMENT_GROUP_INDEX_0{0x1F};
constexpr uint8_t MEASUREMENT_GROUP_INDEX_1{0x30};
constexpr uint8_t MEASUREMENT_GROUP_INDEX_2{0x41};

constexpr uint8_t UNIQUE_ID{0x83};

// calibration
constexpr uint8_t CALIBRATION_GROUP_0{0x8A};
constexpr uint8_t CALIBRATION_GROUP_1{0xE1};
constexpr uint8_t CALIBRATION_GROUP_2{0x00};
constexpr uint8_t CALIBRATION_TEMPERATURE_1_LOW{0xE9};
constexpr uint8_t CALIBRATION_TEMPERATURE_2_LOW{0x8A};
constexpr uint8_t CALIBRATION_TEMPERATURE_3{0x8C};
constexpr uint8_t CALIBRATION_PRESSURE_1_LOW{0x8E};
constexpr uint8_t CALIBRATION_PRESSURE_2_LOW{0x90};
constexpr uint8_t CALIBRATION_PRESSURE_3{0x92};
constexpr uint8_t CALIBRATION_PRESSURE_4_LOW{0x94};
constexpr uint8_t CALIBRATION_PRESSURE_5_LOW{0x96};
constexpr uint8_t CALIBRATION_PRESSURE_6{0x99};
constexpr uint8_t CALIBRATION_PRESSURE_7{0x98};
constexpr uint8_t CALIBRATION_PRESSURE_8_LOW{0x9C};
constexpr uint8_t CALIBRATION_PRESSURE_9_LOW{0x9E};
constexpr uint8_t CALIBRATION_PRESSURE_10{0xA0};
constexpr uint8_t CALIBRATION_HUMIDITY_12{0xE2};
constexpr uint8_t CALIBRATION_HUMIDITY_1_HIGH{0xE3};
constexpr uint8_t CALIBRATION_HUMIDITY_2_HIGH{0xE1};
constexpr uint8_t CALIBRATION_HUMIDITY_3{0xE4};
constexpr uint8_t CALIBRATION_HUMIDITY_4{0xE5};
constexpr uint8_t CALIBRATION_HUMIDITY_5{0xE6};
constexpr uint8_t CALIBRATION_HUMIDITY_6{0xE7};
constexpr uint8_t CALIBRATION_HUMIDITY_7{0xE8};
constexpr uint8_t CALIBRATION_GAS_1{0xED};
constexpr uint8_t CALIBRATION_GAS_2_LOW{0xEB};
constexpr uint8_t CALIBRATION_GAS_3{0xEE};
constexpr uint8_t CALIBRATION_RES_HEAT_RANGE{0x02};  // [5:4]
constexpr uint8_t CALIBRATION_RES_HEAT_VAL{0x00};

}  // namespace command
}  // namespace bme688
///@endcond

}  // namespace unit
}  // namespace m5
#endif
