first commit

This commit is contained in:
stuce-bot 2025-06-30 20:47:33 +02:00
commit 5893b00dd2
1669 changed files with 1982740 additions and 0 deletions

View file

@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
main for UnitTest on native
*/
#include <gtest/gtest.h>
// C++ version
#if __cplusplus >= 202002L
#pragma message "C++20 or later"
#elif __cplusplus >= 201703L
#pragma message "C++17 or later"
#elif __cplusplus >= 201402L
#pragma message "C++14 or later"
#elif __cplusplus >= 201103L
#pragma message "C++11 or later"
#else
#error "Need C++11 or later"
#endif
// Compiler
#if defined(__clang__)
#pragma message "Clang"
#elif defined(_MSC_VER)
#pragma message "MSVC"
#elif defined(__BORLANDC__)
#pragma message "BORLANDC"
#elif defined(__MINGW32__) || defined(__MINGW64__)
#pragma message "MINGW"
#elif defined(__INTEL_COMPILER)
#pragma message "ICC"
#elif defined(__GNUG__)
#pragma message "GCC"
#else
#pragma message "Unknown compiler"
#endif
/*
For native test, this main() is used.
If the Arduino framework is used, the framework library main is used.
*/
#if !defined(ARDUINO)
int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
#ifdef GTEST_FILTER
::testing::GTEST_FLAG(filter) = GTEST_FILTER;
#endif
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result"
RUN_ALL_TESTS();
#pragma GCC diagnostic pop
// Always return zero-code and allow PlatformIO to parse results
return 0;
}
#endif

View file

@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
main for UnitTest on embedded
*/
#include <gtest/gtest.h>
#include <M5Unified.h>
#include <esp_system.h>
#pragma message "Embedded setup/loop"
#if __has_include(<esp_idf_version.h>)
#include <esp_idf_version.h>
#else // esp_idf_version.h has been introduced in Arduino 1.0.5 (ESP-IDF3.3)
#define ESP_IDF_VERSION_VAL(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(3, 2, 0)
#endif
namespace {
auto& lcd = M5.Display;
} // namespace
void test()
{
lcd.fillRect(0, 0, lcd.width() >> 1, lcd.height(), RUN_ALL_TESTS() ? TFT_RED : TFT_GREEN);
}
void setup()
{
delay(1500);
M5.begin();
M5_LOGI("CPP %ld", __cplusplus);
M5_LOGI("ESP-IDF Version %d.%d.%d", (ESP_IDF_VERSION >> 16) & 0xFF, (ESP_IDF_VERSION >> 8) & 0xFF,
ESP_IDF_VERSION & 0xFF);
M5_LOGI("BOARD:%X", M5.getBoard());
M5_LOGI("Heap: %u", esp_get_free_heap_size());
lcd.clear(TFT_DARKGRAY);
::testing::InitGoogleTest();
#ifdef GTEST_FILTER
::testing::GTEST_FLAG(filter) = GTEST_FILTER;
#endif
}
void loop()
{
test();
#if 0
delay(1000);
esp_restart();
#endif
while (true) {
delay(10000);
}
}

View file

@ -0,0 +1,347 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
Common parts of SCD40/41 test
*/
namespace {
// float t uu int16 (temperature) same as library
constexpr uint16_t float_to_uint16(const float f)
{
return f * 65536 / 175;
}
constexpr Mode mode_table[] = {Mode::Normal, Mode::LowPower};
constexpr uint32_t interval_table[] = {
5 * 1000,
30 * 1000,
};
template <class U>
elapsed_time_t test_periodic(U* unit, const uint32_t times, const uint32_t measure_duration = 0)
{
auto tm = unit->interval();
auto timeout_at = m5::utility::millis() + 10 * 1000;
do {
unit->update();
if (unit->updated()) {
break;
}
std::this_thread::yield();
} while (!unit->updated() && m5::utility::millis() <= timeout_at);
// timeout
if (!unit->updated()) {
return 0;
}
//
uint32_t measured{};
auto start_at = m5::utility::millis();
timeout_at = start_at + (times * (tm + measure_duration) * 2);
do {
unit->update();
measured += unit->updated() ? 1 : 0;
if (measured >= times) {
break;
}
m5::utility::delay(1);
} while (measured < times && m5::utility::millis() <= timeout_at);
return (measured == times) ? m5::utility::millis() - start_at : 0;
}
} // namespace
TEST_P(TestSCD4x, BasicCommand)
{
SCOPED_TRACE(ustr);
EXPECT_FALSE(unit->inPeriodic());
for (auto&& m : mode_table) {
auto s = m5::utility::formatString("Mode:%u", m);
SCOPED_TRACE(s);
// Return False if already stopped
EXPECT_FALSE(unit->stopPeriodicMeasurement());
EXPECT_TRUE(unit->startPeriodicMeasurement(m));
// Return False if already started
EXPECT_FALSE(unit->startPeriodicMeasurement(m));
EXPECT_FALSE(unit->startLowPowerPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
// These APIs result in an error during periodic detection
{
EXPECT_FALSE(unit->writeTemperatureOffset(0));
float offset{};
EXPECT_FALSE(unit->readTemperatureOffset(offset));
EXPECT_FALSE(unit->writeSensorAltitude(0));
uint16_t altitude{};
EXPECT_FALSE(unit->readSensorAltitude(altitude));
int16_t correction{};
EXPECT_FALSE(unit->performForcedRecalibration(0, correction));
EXPECT_FALSE(unit->writeAutomaticSelfCalibrationEnabled(true));
bool enabled{};
EXPECT_FALSE(unit->readAutomaticSelfCalibrationEnabled(enabled));
EXPECT_FALSE(unit->writeAutomaticSelfCalibrationTarget(0));
uint16_t ppm{};
EXPECT_FALSE(unit->readAutomaticSelfCalibrationTarget(ppm));
EXPECT_FALSE(unit->writePersistSettings());
uint64_t sno{};
EXPECT_FALSE(unit->readSerialNumber(sno));
bool malfunction{};
EXPECT_FALSE(unit->performSelfTest(malfunction));
EXPECT_FALSE(unit->performFactoryReset());
EXPECT_FALSE(unit->reInit());
}
EXPECT_TRUE(unit->writeAmbientPressure(1013));
uint16_t pressure{};
EXPECT_TRUE(unit->readAmbientPressure(pressure));
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
}
}
TEST_P(TestSCD4x, OnChipOutputSignalCompensation)
{
SCOPED_TRACE(ustr);
{
constexpr float OFFSET{5.4f};
EXPECT_TRUE(unit->writeTemperatureOffset(OFFSET));
float offset{};
EXPECT_TRUE(unit->readTemperatureOffset(offset));
EXPECT_EQ(float_to_uint16(offset), float_to_uint16(OFFSET)) << "offset:" << offset << " OFFSET:" << OFFSET;
}
{
constexpr uint16_t ALTITUDE{3776};
EXPECT_TRUE(unit->writeSensorAltitude(ALTITUDE));
uint16_t altitude{};
EXPECT_TRUE(unit->readSensorAltitude(altitude));
EXPECT_EQ(altitude, ALTITUDE);
}
{
constexpr uint16_t PRESSURE{1111};
EXPECT_TRUE(unit->writeAmbientPressure(PRESSURE));
uint16_t pressure{};
EXPECT_TRUE(unit->readAmbientPressure(pressure));
EXPECT_EQ(pressure, PRESSURE);
EXPECT_TRUE(unit->writeAmbientPressure(700));
EXPECT_TRUE(unit->readAmbientPressure(pressure));
EXPECT_EQ(pressure, 700);
EXPECT_TRUE(unit->writeAmbientPressure(1200));
EXPECT_TRUE(unit->readAmbientPressure(pressure));
EXPECT_EQ(pressure, 1200);
EXPECT_FALSE(unit->writeAmbientPressure(699));
EXPECT_FALSE(unit->writeAmbientPressure(1201));
}
}
TEST_P(TestSCD4x, FieldCalibration)
{
SCOPED_TRACE(ustr);
{
int16_t correction{};
EXPECT_TRUE(unit->performForcedRecalibration(1234, correction));
}
{
EXPECT_TRUE(unit->writeAutomaticSelfCalibrationEnabled(false));
bool enabled{};
EXPECT_TRUE(unit->readAutomaticSelfCalibrationEnabled(enabled));
EXPECT_FALSE(enabled);
EXPECT_TRUE(unit->writeAutomaticSelfCalibrationEnabled(true));
EXPECT_TRUE(unit->readAutomaticSelfCalibrationEnabled(enabled));
EXPECT_TRUE(enabled);
}
{
constexpr uint16_t PPM{12345};
EXPECT_TRUE(unit->writeAutomaticSelfCalibrationTarget(PPM));
uint16_t ppm{};
EXPECT_TRUE(unit->readAutomaticSelfCalibrationTarget(ppm));
EXPECT_EQ(ppm, PPM);
}
}
TEST_P(TestSCD4x, AdvancedFeatures)
{
SCOPED_TRACE(ustr);
{
// Read direct [MSB] SNB_3, SNB_2, CRC, SNB_1, SNB_0, CRC [LSB]
std::array<uint8_t, 9> rbuf{};
EXPECT_TRUE(unit->readRegister(m5::unit::scd4x::command::GET_SERIAL_NUMBER, rbuf.data(), rbuf.size(), 1));
// M5_LOGI("%02x%02x%02x%02x%02x%02x", rbuf[0], rbuf[1], rbuf[3],
// rbuf[4],
// rbuf[6], rbuf[7]);
m5::types::big_uint16_t w0(rbuf[0], rbuf[1]);
m5::types::big_uint16_t w1(rbuf[3], rbuf[4]);
m5::types::big_uint16_t w2(rbuf[6], rbuf[7]);
uint64_t d_sno = (((uint64_t)w0.get()) << 32) | (((uint64_t)w1.get()) << 16) | ((uint64_t)w2.get());
// M5_LOGI("d_sno[%llX]", d_sno);
//
uint64_t sno{};
char ssno[13]{};
EXPECT_TRUE(unit->readSerialNumber(sno));
EXPECT_TRUE(unit->readSerialNumber(ssno));
// M5_LOGI("s:[%s] uint64:[%x]", ssno, sno);
EXPECT_EQ(sno, d_sno);
std::stringstream stream;
stream << std::uppercase << std::setw(12) << std::hex << std::setfill('0') << sno;
std::string s(stream.str());
EXPECT_STREQ(s.c_str(), ssno);
}
// Set
constexpr float OFFSET{1.234f};
EXPECT_TRUE(unit->writeTemperatureOffset(OFFSET));
constexpr uint16_t ALTITUDE{3776};
EXPECT_TRUE(unit->writeSensorAltitude(ALTITUDE));
EXPECT_TRUE(unit->writeAutomaticSelfCalibrationEnabled(false));
constexpr uint16_t PPM{12345};
EXPECT_TRUE(unit->writeAutomaticSelfCalibrationTarget(PPM));
EXPECT_TRUE(unit->writePersistSettings()); // Save EEPROM
// Overwrite settings
EXPECT_TRUE(unit->writeTemperatureOffset(OFFSET * 2));
EXPECT_TRUE(unit->writeSensorAltitude(ALTITUDE * 2));
EXPECT_TRUE(unit->writeAutomaticSelfCalibrationEnabled(true));
EXPECT_TRUE(unit->writeAutomaticSelfCalibrationTarget(PPM * 2));
float off{};
uint16_t alt{}, ppm{};
bool enabled{};
EXPECT_TRUE(unit->readTemperatureOffset(off));
EXPECT_TRUE(unit->readSensorAltitude(alt));
EXPECT_TRUE(unit->readAutomaticSelfCalibrationEnabled(enabled));
EXPECT_TRUE(unit->readAutomaticSelfCalibrationTarget(ppm));
EXPECT_EQ(float_to_uint16(off), float_to_uint16(OFFSET * 2));
EXPECT_EQ(alt, ALTITUDE * 2);
EXPECT_EQ(ppm, PPM * 2);
EXPECT_TRUE(enabled);
EXPECT_TRUE(unit->reInit()); // Load EEPROM
// Check saved settings
EXPECT_TRUE(unit->readTemperatureOffset(off));
EXPECT_TRUE(unit->readSensorAltitude(alt));
EXPECT_TRUE(unit->readAutomaticSelfCalibrationEnabled(enabled));
EXPECT_TRUE(unit->readAutomaticSelfCalibrationTarget(ppm));
EXPECT_EQ(float_to_uint16(off), float_to_uint16(OFFSET));
EXPECT_EQ(alt, ALTITUDE);
EXPECT_EQ(ppm, PPM);
EXPECT_FALSE(enabled);
bool malfunction{};
EXPECT_TRUE(unit->performSelfTest(malfunction));
EXPECT_TRUE(unit->performFactoryReset()); // Reset EEPROM
EXPECT_TRUE(unit->readTemperatureOffset(off));
EXPECT_TRUE(unit->readSensorAltitude(alt));
EXPECT_TRUE(unit->readAutomaticSelfCalibrationEnabled(enabled));
EXPECT_TRUE(unit->readAutomaticSelfCalibrationTarget(ppm));
EXPECT_NE(float_to_uint16(off), float_to_uint16(OFFSET));
EXPECT_NE(alt, ALTITUDE);
EXPECT_NE(ppm, PPM);
EXPECT_TRUE(enabled);
}
TEST_P(TestSCD4x, Periodic)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->performFactoryReset()); // Reset EEPROM
uint32_t idx{};
for (auto&& m : mode_table) {
auto s = m5::utility::formatString("Mode:%u", m);
SCOPED_TRACE(s);
EXPECT_FALSE(unit->inPeriodic());
EXPECT_TRUE(unit->startPeriodicMeasurement(m));
EXPECT_TRUE(unit->inPeriodic());
EXPECT_EQ(unit->updatedMillis(), 0);
auto it = interval_table[idx];
auto elapsed = test_periodic(unit.get(), STORED_SIZE, it);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
EXPECT_NE(elapsed, 0);
EXPECT_GE(elapsed, STORED_SIZE * it);
//
EXPECT_EQ(unit->available(), STORED_SIZE);
EXPECT_FALSE(unit->empty());
EXPECT_TRUE(unit->full());
uint32_t cnt{STORED_SIZE / 2};
while (cnt-- && unit->available()) {
EXPECT_NE(unit->co2(), 0);
EXPECT_TRUE(std::isfinite(unit->celsius()));
EXPECT_TRUE(std::isfinite(unit->fahrenheit()));
EXPECT_TRUE(std::isfinite(unit->humidity()));
EXPECT_EQ(unit->co2(), unit->oldest().co2());
EXPECT_FLOAT_EQ(unit->celsius(), unit->oldest().celsius());
EXPECT_FLOAT_EQ(unit->fahrenheit(), unit->oldest().fahrenheit());
EXPECT_FLOAT_EQ(unit->humidity(), unit->oldest().humidity());
EXPECT_FALSE(unit->empty());
unit->discard();
}
EXPECT_EQ(unit->available(), STORED_SIZE / 2);
EXPECT_FALSE(unit->empty());
EXPECT_FALSE(unit->full());
unit->flush();
EXPECT_EQ(unit->available(), 0);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
EXPECT_EQ(unit->co2(), 0);
EXPECT_FALSE(std::isfinite(unit->celsius()));
EXPECT_FALSE(std::isfinite(unit->fahrenheit()));
EXPECT_FALSE(std::isfinite(unit->humidity()));
}
}

View file

@ -0,0 +1,638 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for UnitBME688
*/
#include <gtest/gtest.h>
#include <Wire.h>
#include <M5Unified.h>
#include <M5UnitUnified.hpp>
#include <googletest/test_helper.hpp>
#include <googletest/test_template.hpp>
#include <unit/unit_BME688.hpp>
#include <chrono>
#include <random>
#include <set>
using namespace m5::unit::googletest;
using namespace m5::unit;
using namespace m5::unit::bme688;
#if defined(UNIT_BME688_USING_BSEC2)
using namespace m5::unit::bme688::bsec2;
#endif
const ::testing::Environment* global_fixture = ::testing::AddGlobalTestEnvironment(new GlobalFixture<400000U>());
class TestBME688 : public ComponentTestBase<UnitBME688, bool> {
protected:
virtual UnitBME688* get_instance() override
{
auto ptr = new m5::unit::UnitBME688();
auto ccfg = ptr->component_config();
ccfg.stored_size = 8;
ptr->component_config(ccfg);
return ptr;
}
virtual bool is_using_hal() const override
{
return GetParam();
};
};
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestBME688,
// ::testing::Values(false, true));
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestBME688, ::testing::Values(true));
INSTANTIATE_TEST_SUITE_P(ParamValues, TestBME688, ::testing::Values(false));
namespace {
constexpr Oversampling os_table[] = {
Oversampling::None, Oversampling::x1, Oversampling::x1, Oversampling::x2,
Oversampling::x4, Oversampling::x8, Oversampling::x16,
};
constexpr Filter filter_table[] = {
Filter::None, Filter::Coeff_1, Filter::Coeff_3, Filter::Coeff_7,
Filter::Coeff_15, Filter::Coeff_31, Filter::Coeff_63, Filter::Coeff_127,
};
#if defined(UNIT_BME688_USING_BSEC2)
// All outputs
constexpr bsec_virtual_sensor_t vs_table[] = {
BSEC_OUTPUT_IAQ,
BSEC_OUTPUT_STATIC_IAQ,
BSEC_OUTPUT_CO2_EQUIVALENT,
BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
BSEC_OUTPUT_RAW_TEMPERATURE,
BSEC_OUTPUT_RAW_PRESSURE,
BSEC_OUTPUT_RAW_HUMIDITY,
BSEC_OUTPUT_RAW_GAS,
BSEC_OUTPUT_STABILIZATION_STATUS,
BSEC_OUTPUT_RUN_IN_STATUS,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
BSEC_OUTPUT_GAS_PERCENTAGE,
BSEC_OUTPUT_GAS_ESTIMATE_1,
BSEC_OUTPUT_GAS_ESTIMATE_2,
BSEC_OUTPUT_GAS_ESTIMATE_3,
BSEC_OUTPUT_GAS_ESTIMATE_4,
BSEC_OUTPUT_RAW_GAS_INDEX,
BSEC_OUTPUT_REGRESSION_ESTIMATE_1,
BSEC_OUTPUT_REGRESSION_ESTIMATE_2,
BSEC_OUTPUT_REGRESSION_ESTIMATE_3,
BSEC_OUTPUT_REGRESSION_ESTIMATE_4,
};
// Using BSEC2 library configuration files
constexpr uint8_t bsec_config[] = {
#include <config/bme688/bme688_sel_33v_300s_4d/bsec_selectivity.txt>
};
#endif
auto rng = std::default_random_engine{};
void check_measurement_values(UnitBME688* u)
{
auto latest = u->latest();
// for raw
EXPECT_TRUE(std::isfinite(latest.raw_temperature()));
EXPECT_TRUE(std::isfinite(latest.raw_pressure()));
EXPECT_TRUE(std::isfinite(latest.raw_humidity()));
EXPECT_TRUE(std::isfinite(latest.raw_gas()));
// M5_LOGI("%f/%f/%f/%f", latest.raw_temperature(), latest.raw_pressure(), latest.raw_humidity(), latest.raw_gas());
}
} // namespace
TEST_P(TestBME688, Misc)
{
#if defined(UNIT_BME688_USING_BSEC2)
for (auto&& v : vs_table) {
EXPECT_EQ(subscribe_to_bits(v), 1U << v);
}
auto bits = subscribe_to_bits(
BSEC_OUTPUT_IAQ, BSEC_OUTPUT_STATIC_IAQ, BSEC_OUTPUT_CO2_EQUIVALENT, BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
BSEC_OUTPUT_RAW_TEMPERATURE, BSEC_OUTPUT_RAW_PRESSURE, BSEC_OUTPUT_RAW_HUMIDITY, BSEC_OUTPUT_RAW_GAS,
BSEC_OUTPUT_STABILIZATION_STATUS, BSEC_OUTPUT_RUN_IN_STATUS, BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY, BSEC_OUTPUT_GAS_PERCENTAGE, BSEC_OUTPUT_GAS_ESTIMATE_1,
BSEC_OUTPUT_GAS_ESTIMATE_2, BSEC_OUTPUT_GAS_ESTIMATE_3, BSEC_OUTPUT_GAS_ESTIMATE_4, BSEC_OUTPUT_RAW_GAS_INDEX,
BSEC_OUTPUT_REGRESSION_ESTIMATE_1, BSEC_OUTPUT_REGRESSION_ESTIMATE_2, BSEC_OUTPUT_REGRESSION_ESTIMATE_3,
BSEC_OUTPUT_REGRESSION_ESTIMATE_4);
constexpr uint32_t val{
1U << BSEC_OUTPUT_IAQ | 1U << BSEC_OUTPUT_STATIC_IAQ | 1U << BSEC_OUTPUT_CO2_EQUIVALENT |
1U << BSEC_OUTPUT_BREATH_VOC_EQUIVALENT | 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 | 1U << BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE |
1U << BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY | 1U << BSEC_OUTPUT_GAS_PERCENTAGE |
1U << BSEC_OUTPUT_GAS_ESTIMATE_1 | 1U << BSEC_OUTPUT_GAS_ESTIMATE_2 | 1U << BSEC_OUTPUT_GAS_ESTIMATE_3 |
1U << BSEC_OUTPUT_GAS_ESTIMATE_4 | 1U << BSEC_OUTPUT_RAW_GAS_INDEX | 1U << BSEC_OUTPUT_REGRESSION_ESTIMATE_1 |
1U << BSEC_OUTPUT_REGRESSION_ESTIMATE_2 | 1U << BSEC_OUTPUT_REGRESSION_ESTIMATE_3 |
1U << BSEC_OUTPUT_REGRESSION_ESTIMATE_4};
EXPECT_EQ(bits, val);
#endif
}
TEST_P(TestBME688, Settings)
{
SCOPED_TRACE(ustr);
Oversampling os{};
Filter f{};
uint32_t serial{};
EXPECT_TRUE(unit->readUniqueID(serial));
EXPECT_NE(serial, 0U);
// TPH
for (auto&& e : os_table) {
EXPECT_TRUE(unit->writeOversamplingTemperature(e));
EXPECT_EQ(unit->tphSetting().os_temp, m5::stl::to_underlying(e));
EXPECT_TRUE(unit->readOversamplingTemperature(os));
EXPECT_EQ(os, e);
}
for (auto&& e : os_table) {
EXPECT_TRUE(unit->writeOversamplingPressure(e));
EXPECT_EQ(unit->tphSetting().os_pres, m5::stl::to_underlying(e));
EXPECT_TRUE(unit->readOversamplingPressure(os));
EXPECT_EQ(os, e);
}
for (auto&& e : os_table) {
EXPECT_TRUE(unit->writeOversamplingHumidity(e));
EXPECT_EQ(unit->tphSetting().os_hum, m5::stl::to_underlying(e));
EXPECT_TRUE(unit->readOversamplingHumidity(os));
EXPECT_EQ(os, e);
}
for (auto&& e : filter_table) {
EXPECT_TRUE(unit->writeIIRFilter(e));
EXPECT_EQ(unit->tphSetting().filter, m5::stl::to_underlying(e));
EXPECT_TRUE(unit->readIIRFilter(f));
EXPECT_EQ(f, e);
}
uint32_t cnt{10};
while (cnt--) {
bme68xConf tph = unit->tphSetting();
tph.os_temp = rng() % 0x06;
tph.os_pres = rng() % 0x06;
tph.os_hum = rng() % 0x06;
tph.filter = rng() % 0x07;
// M5_LOGW("%u/%u/%u/%u", tph.os_temp, tph.os_pres, tph.os_hum,
// tph.filter);
EXPECT_TRUE(unit->writeTPHSetting(tph));
EXPECT_EQ(unit->tphSetting().os_temp, tph.os_temp);
EXPECT_EQ(unit->tphSetting().os_pres, tph.os_pres);
EXPECT_EQ(unit->tphSetting().os_hum, tph.os_hum);
bme68xConf after{};
EXPECT_TRUE(unit->readTPHSetting(after));
EXPECT_TRUE(memcmp(&tph, &after, sizeof(tph)) == 0)
<< tph.os_temp << "/" << tph.os_pres << "/" << tph.os_hum << "/" << tph.filter;
EXPECT_TRUE(
unit->writeOversampling((Oversampling)tph.os_temp, (Oversampling)tph.os_pres, (Oversampling)tph.os_hum));
EXPECT_EQ(unit->tphSetting().os_temp, tph.os_temp);
EXPECT_EQ(unit->tphSetting().os_pres, tph.os_pres);
EXPECT_EQ(unit->tphSetting().os_hum, tph.os_hum);
EXPECT_TRUE(unit->readTPHSetting(after));
EXPECT_TRUE(memcmp(&tph, &after, sizeof(tph)) == 0)
<< tph.os_temp << "/" << tph.os_pres << "/" << tph.os_hum << "/" << tph.filter;
}
// Calibration
bme68xCalibration c0{}, c1{};
EXPECT_TRUE(unit->readCalibration(c0));
EXPECT_TRUE(unit->writeCalibration(c0));
EXPECT_TRUE(unit->readCalibration(c1));
EXPECT_TRUE(memcmp(&c0, &c1, sizeof(c1)) == 0);
// softReset rewinds settings
EXPECT_TRUE(unit->softReset());
//
EXPECT_TRUE(unit->readOversamplingTemperature(os));
EXPECT_EQ(os, Oversampling::None);
EXPECT_TRUE(unit->readOversamplingPressure(os));
EXPECT_EQ(os, Oversampling::None);
EXPECT_TRUE(unit->readOversamplingHumidity(os));
EXPECT_EQ(os, Oversampling::None);
EXPECT_TRUE(unit->readIIRFilter(f));
EXPECT_EQ(f, Filter::None);
}
#if defined(UNIT_BME688_USING_BSEC2)
TEST_P(TestBME688, BSEC2)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->inPeriodic());
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
uint8_t cfg[BSEC_MAX_PROPERTY_BLOB_SIZE]{};
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE]{};
uint8_t state2[BSEC_MAX_STATE_BLOB_SIZE]{};
uint32_t actual{};
EXPECT_TRUE(unit->bsec2GetState(state, actual)); // get current
// Version
auto& ver = unit->bsec2Version();
EXPECT_NE(ver.major, 0);
EXPECT_NE(ver.minor, 0);
// M5_LOGI("bsec2 ver:%u.%u.%u.%u", ver.major, ver.minor, ver.major_bugfix, ver.minor_bugfix);
// Subscribe
constexpr bsec_virtual_sensor_t sensorList[] = {
BSEC_OUTPUT_IAQ, BSEC_OUTPUT_RAW_TEMPERATURE, BSEC_OUTPUT_RAW_PRESSURE, BSEC_OUTPUT_RAW_HUMIDITY,
BSEC_OUTPUT_RAW_GAS, BSEC_OUTPUT_STABILIZATION_STATUS, BSEC_OUTPUT_RUN_IN_STATUS};
std::vector<bsec_virtual_sensor_t> nosubscribed(vs_table, vs_table + m5::stl::size(vs_table));
auto it = std::remove_if(nosubscribed.begin(), nosubscribed.end(), [&sensorList](bsec_virtual_sensor_t vs) {
auto e = std::begin(sensorList) + m5::stl::size(sensorList);
return std::find(std::begin(sensorList), e, vs) != e;
});
nosubscribed.erase(it, nosubscribed.end());
EXPECT_TRUE(unit->bsec2UpdateSubscription(sensorList, m5::stl::size(sensorList), bsec2::SampleRate::LowPower));
for (auto&& e : sensorList) {
EXPECT_TRUE(unit->bsec2IsSubscribed(e)) << e;
}
for (auto&& e : nosubscribed) {
EXPECT_FALSE(unit->bsec2IsSubscribed(e)) << e;
}
for (auto&& e : sensorList) {
EXPECT_TRUE(unit->bsec2Unsubscribe(e)) << e;
EXPECT_FALSE(unit->bsec2IsSubscribed(e)) << e;
}
for (auto&& e : nosubscribed) {
EXPECT_FALSE(unit->bsec2IsSubscribed(e)) << e;
}
for (auto&& e : sensorList) {
EXPECT_TRUE(unit->bsec2Subscribe(e)) << e;
EXPECT_TRUE(unit->bsec2IsSubscribed(e)) << e;
}
for (auto&& e : nosubscribed) {
EXPECT_FALSE(unit->bsec2IsSubscribed(e)) << e;
}
EXPECT_TRUE(unit->bsec2UnsubscribeAll()); // Same as stop
for (auto&& e : vs_table) {
EXPECT_FALSE(unit->bsec2IsSubscribed(e)) << e;
}
// Measurement
EXPECT_TRUE(unit->startPeriodicMeasurement(sensorList, m5::stl::size(sensorList), bsec2::SampleRate::LowPower));
auto bits = virtual_sensor_array_to_bits(sensorList, m5::stl::size(sensorList));
EXPECT_EQ(unit->bsec2Subscription(), bits);
#if 1
test_periodic_measurement(unit.get(), 8, 8, (unit->interval() * 2) * 8, check_measurement_values, false);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
EXPECT_EQ(unit->mode(), Mode::Sleep);
EXPECT_EQ(unit->available(), 8U);
EXPECT_FALSE(unit->empty());
EXPECT_TRUE(unit->full());
uint32_t cnt{4};
while (unit->available() && cnt--) {
EXPECT_TRUE(std::isfinite(unit->iaq()));
EXPECT_TRUE(std::isfinite(unit->temperature()));
EXPECT_TRUE(std::isfinite(unit->pressure()));
EXPECT_TRUE(std::isfinite(unit->humidity()));
EXPECT_TRUE(std::isfinite(unit->gas()));
EXPECT_FLOAT_EQ(unit->iaq(), unit->oldest().iaq());
EXPECT_FLOAT_EQ(unit->temperature(), unit->oldest().temperature());
EXPECT_FLOAT_EQ(unit->pressure(), unit->oldest().pressure());
EXPECT_FLOAT_EQ(unit->humidity(), unit->oldest().humidity());
EXPECT_FLOAT_EQ(unit->gas(), unit->oldest().gas());
EXPECT_FALSE(unit->empty());
unit->discard();
}
#else
uint32_t cnt{8};
while (cnt--) {
auto now{m5::utility::millis()};
auto timeout_at = now + 3 * 1000; // LowPower
do {
unit->update();
now = m5::utility::millis();
if (unit->updated()) {
break;
}
m5::utility::delay(1);
} while (now <= timeout_at);
// M5_LOGW("now:%ld timeout_at:%ld", now, timeout_at);
EXPECT_TRUE(unit->updated());
if (cnt < 2) {
EXPECT_LE(now, timeout_at);
}
auto d = unit->latest();
for (auto&& vs : sensorList) {
float f = d.get(vs);
// M5_LOGI("[%u]:%.2f", vs, f);
EXPECT_TRUE(std::isfinite(f)) << vs;
}
for (auto&& vs : nosubscribed) {
float f = d.get(vs);
EXPECT_FALSE(std::isfinite(f)) << vs;
}
}
EXPECT_TRUE(unit->inPeriodic());
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
EXPECT_EQ(unit->available(), 8U);
EXPECT_FALSE(unit->empty());
EXPECT_TRUE(unit->full());
cnt = 4;
while (unit->available() && cnt--) {
EXPECT_TRUE(std::isfinite(unit->iaq()));
EXPECT_TRUE(std::isfinite(unit->temperature()));
EXPECT_TRUE(std::isfinite(unit->pressure()));
EXPECT_TRUE(std::isfinite(unit->humidity()));
EXPECT_TRUE(std::isfinite(unit->gas()));
EXPECT_FLOAT_EQ(unit->iaq(), unit->oldest().iaq());
EXPECT_FLOAT_EQ(unit->temperature(), unit->oldest().temperature());
EXPECT_FLOAT_EQ(unit->pressure(), unit->oldest().pressure());
EXPECT_FLOAT_EQ(unit->humidity(), unit->oldest().humidity());
EXPECT_FLOAT_EQ(unit->gas(), unit->oldest().gas());
EXPECT_FALSE(unit->empty());
unit->discard();
}
#endif
EXPECT_EQ(unit->available(), 4U);
EXPECT_FALSE(unit->empty());
EXPECT_FALSE(unit->full());
unit->flush();
EXPECT_EQ(unit->available(), 0U);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
// Config
EXPECT_EQ(sizeof(bsec_config), BSEC_MAX_PROPERTY_BLOB_SIZE);
EXPECT_TRUE(unit->bsec2GetConfig(cfg, actual)); // get current
EXPECT_NE(memcmp(cfg, bsec_config, actual), 0);
EXPECT_TRUE(unit->bsec2SetConfig(bsec_config)); // overwrite
EXPECT_TRUE(unit->bsec2GetConfig(cfg, actual));
auto cmp = memcmp(cfg, bsec_config, actual) == 0;
EXPECT_TRUE(cmp);
if (!cmp) {
M5_DUMPI(cfg, actual);
M5_DUMPI(bsec_config, actual);
}
// State
EXPECT_TRUE(unit->bsec2GetState(state2, actual)); // get current
cmp = memcmp(state2, state, actual) == 0;
EXPECT_FALSE(cmp);
EXPECT_TRUE(unit->bsec2SetState(state)); // rewrite
EXPECT_TRUE(unit->bsec2GetState(state2, actual)); // get
cmp = memcmp(state2, state, actual) == 0;
if (!cmp) {
M5_DUMPI(state, actual);
M5_DUMPI(state2, actual);
}
}
#endif
TEST_P(TestBME688, SingleShot)
{
SCOPED_TRACE(ustr);
bme68xConf tph{};
tph.os_temp = m5::stl::to_underlying(Oversampling::x2);
tph.os_pres = m5::stl::to_underlying(Oversampling::x1);
tph.os_hum = m5::stl::to_underlying(Oversampling::x16);
tph.filter = m5::stl::to_underlying(Filter::None);
tph.odr = m5::stl::to_underlying(ODR::None);
EXPECT_TRUE(unit->writeTPHSetting(tph));
m5::unit::bme688::bme68xHeatrConf hs{};
hs.enable = true;
hs.heatr_temp = 300;
hs.heatr_dur = 100;
EXPECT_TRUE(unit->writeHeaterSetting(Mode::Forced, hs));
bme68xData data{};
EXPECT_TRUE(unit->inPeriodic());
EXPECT_FALSE(unit->measureSingleShot(data));
EXPECT_TRUE(unit->stopPeriodicMeasurement());
Mode m{};
EXPECT_TRUE(unit->readMode(m));
EXPECT_EQ(m, Mode::Sleep);
EXPECT_FALSE(unit->inPeriodic());
EXPECT_TRUE(unit->measureSingleShot(data));
#if 0
M5_LOGI(
"Status:%u\n"
"Gidx:%u Midx:%u\n"
"ResHeate:%u IDAC:%u GasWait:%u\n"
"T:%f P:%f H:%f R:%f",
data.status, data.gas_index, data.meas_index, data.res_heat, data.idac, data.gas_wait, data.temperature,
data.pressure, data.humidity, data.gas_resistance);
#endif
}
TEST_P(TestBME688, PeriodicForced)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->inPeriodic());
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
bme68xConf tph{};
tph.os_temp = m5::stl::to_underlying(Oversampling::x2);
tph.os_pres = m5::stl::to_underlying(Oversampling::x1);
tph.os_hum = m5::stl::to_underlying(Oversampling::x16);
tph.filter = m5::stl::to_underlying(Filter::None);
tph.odr = m5::stl::to_underlying(ODR::None);
EXPECT_TRUE(unit->writeTPHSetting(tph));
m5::unit::bme688::bme68xHeatrConf hs{};
hs.enable = true;
hs.heatr_temp = 300;
hs.heatr_dur = 100;
EXPECT_TRUE(unit->writeHeaterSetting(Mode::Forced, hs));
//
EXPECT_FALSE(unit->inPeriodic());
EXPECT_TRUE(unit->startPeriodicMeasurement(Mode::Forced));
EXPECT_TRUE(unit->inPeriodic());
EXPECT_EQ(unit->mode(), Mode::Forced);
// Always wait for an interval to obtain the correct value for the first measurement
// EXPECT_EQ(unit->updatedMillis(), 0); //
EXPECT_EQ(unit->available(), 0U);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
test_periodic_measurement(unit.get(), 8, 8, (unit->interval() * 2) * 8, check_measurement_values, false);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
EXPECT_EQ(unit->mode(), Mode::Sleep);
EXPECT_EQ(unit->available(), 8U);
EXPECT_FALSE(unit->empty());
EXPECT_TRUE(unit->full());
uint32_t cnt{4};
while (unit->available() && cnt--) {
EXPECT_TRUE(std::isfinite(unit->oldest().raw_temperature()));
EXPECT_TRUE(std::isfinite(unit->oldest().raw_pressure()));
EXPECT_TRUE(std::isfinite(unit->oldest().raw_humidity()));
EXPECT_TRUE(std::isfinite(unit->oldest().raw_gas()));
EXPECT_FALSE(unit->empty());
unit->discard();
}
EXPECT_EQ(unit->available(), 4U);
EXPECT_FALSE(unit->empty());
EXPECT_FALSE(unit->full());
unit->flush();
EXPECT_EQ(unit->available(), 0U);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
}
TEST_P(TestBME688, PeriodicParallel)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->inPeriodic());
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
uint16_t temp_prof[10] = {320, 100, 100, 100, 200, 200, 200, 320, 320, 320};
/* Multiplier to the shared heater duration */
uint16_t mul_prof[10] = {5, 2, 10, 30, 5, 5, 5, 5, 5, 5};
bme68xConf tph{};
tph.os_temp = m5::stl::to_underlying(Oversampling::x2);
tph.os_pres = m5::stl::to_underlying(Oversampling::x1);
tph.os_hum = m5::stl::to_underlying(Oversampling::x16);
tph.filter = m5::stl::to_underlying(Filter::None);
tph.odr = m5::stl::to_underlying(ODR::None);
EXPECT_TRUE(unit->writeTPHSetting(tph));
m5::unit::bme688::bme68xHeatrConf hs{};
hs.enable = true;
memcpy(hs.temp_prof, temp_prof, sizeof(temp_prof));
memcpy(hs.dur_prof, mul_prof, sizeof(mul_prof));
hs.shared_heatr_dur = (uint16_t)(140 - (unit->calculateMeasurementInterval(Mode::Parallel, tph) / 1000));
hs.profile_len = 10;
EXPECT_TRUE(unit->writeHeaterSetting(Mode::Parallel, hs));
//
EXPECT_FALSE(unit->inPeriodic());
EXPECT_TRUE(unit->startPeriodicMeasurement(Mode::Parallel));
EXPECT_TRUE(unit->inPeriodic());
EXPECT_EQ(unit->mode(), Mode::Parallel);
EXPECT_EQ(unit->available(), 0U);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
// TODO : What are the measurement intervals in the parallel mode datasheet?
test_periodic_measurement(unit.get(), 8, 1, (unit->interval() * 10) * 10, check_measurement_values, false);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
EXPECT_EQ(unit->mode(), Mode::Sleep);
EXPECT_EQ(unit->available(), 8U);
EXPECT_FALSE(unit->empty());
EXPECT_TRUE(unit->full());
uint32_t cnt{4};
while (unit->available() && cnt--) {
EXPECT_TRUE(std::isfinite(unit->oldest().raw_temperature()));
EXPECT_TRUE(std::isfinite(unit->oldest().raw_pressure()));
EXPECT_TRUE(std::isfinite(unit->oldest().raw_humidity()));
EXPECT_TRUE(std::isfinite(unit->oldest().raw_gas()));
EXPECT_FALSE(unit->empty());
unit->discard();
}
EXPECT_EQ(unit->available(), 4U);
EXPECT_FALSE(unit->empty());
EXPECT_FALSE(unit->full());
unit->flush();
EXPECT_EQ(unit->available(), 0U);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
}
TEST_P(TestBME688, PeriodiSequential)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->inPeriodic());
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
uint16_t temp_prof[10] = {200, 240, 280, 320, 360, 360, 320, 280, 240, 200};
/* Heating duration in milliseconds */
uint16_t dur_prof[10] = {100, 100, 100, 100, 100, 100, 100, 100, 100, 100};
bme68xConf tph{};
tph.os_temp = m5::stl::to_underlying(Oversampling::x2);
tph.os_pres = m5::stl::to_underlying(Oversampling::x1);
tph.os_hum = m5::stl::to_underlying(Oversampling::x16);
tph.filter = m5::stl::to_underlying(Filter::None);
tph.odr = m5::stl::to_underlying(ODR::None);
EXPECT_TRUE(unit->writeTPHSetting(tph));
m5::unit::bme688::bme68xHeatrConf hs{};
hs.enable = true;
memcpy(hs.temp_prof, temp_prof, sizeof(temp_prof));
memcpy(hs.dur_prof, dur_prof, sizeof(dur_prof));
hs.profile_len = 10;
EXPECT_TRUE(unit->writeHeaterSetting(Mode::Sequential, hs));
//
EXPECT_FALSE(unit->inPeriodic());
EXPECT_TRUE(unit->startPeriodicMeasurement(Mode::Sequential));
EXPECT_TRUE(unit->inPeriodic());
EXPECT_EQ(unit->mode(), Mode::Sequential);
test_periodic_measurement(unit.get(), 8, 1, (unit->interval() * 2) * 8, check_measurement_values, false);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
EXPECT_EQ(unit->mode(), Mode::Sleep);
}
TEST_P(TestBME688, SelfTest)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->selfTest());
}

View file

@ -0,0 +1,526 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for UnitBMP280
*/
#include <gtest/gtest.h>
#include <Wire.h>
#include <M5Unified.h>
#include <M5UnitUnified.hpp>
#include <googletest/test_template.hpp>
#include <googletest/test_helper.hpp>
#include <unit/unit_BMP280.hpp>
#include <chrono>
#include <cmath>
#include <random>
using namespace m5::unit::googletest;
using namespace m5::unit;
using namespace m5::unit::bmp280;
using namespace m5::unit::bmp280::command;
using m5::unit::types::elapsed_time_t;
constexpr uint32_t STORED_SIZE{8};
const ::testing::Environment* global_fixture = ::testing::AddGlobalTestEnvironment(new GlobalFixture<400000U>());
class TestBMP280 : public ComponentTestBase<UnitBMP280, bool> {
protected:
virtual UnitBMP280* get_instance() override
{
auto ptr = new m5::unit::UnitBMP280();
auto ccfg = ptr->component_config();
ccfg.stored_size = STORED_SIZE;
ptr->component_config(ccfg);
return ptr;
}
virtual bool is_using_hal() const override
{
return GetParam();
};
void print_ctrl_measurement(const char* msg = "")
{
uint8_t v{};
unit->readRegister8(CONTROL_MEASUREMENT, v, 0);
M5_LOGI("%s CM:%02X", msg, v);
}
void print_status(const char* msg = "")
{
uint8_t v{};
unit->readRegister8(GET_STATUS, v, 0);
M5_LOGI("%s S:%02X", msg, v);
}
};
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestBMP280,
// ::testing::Values(false, true));
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestBMP280, ::testing::Values(true));
INSTANTIATE_TEST_SUITE_P(ParamValues, TestBMP280, ::testing::Values(false));
namespace {
constexpr Oversampling os_table[] = {
Oversampling::Skipped, Oversampling::X1, Oversampling::X2, Oversampling::X4, Oversampling::X8, Oversampling::X16,
};
constexpr OversamplingSetting oss_table[] = {
OversamplingSetting::UltraLowPower, OversamplingSetting::LowPower,
OversamplingSetting::StandardResolution, OversamplingSetting::HighResolution,
OversamplingSetting::UltraHighResolution,
};
constexpr Oversampling osrss_table[][2] = {
// Pressure, Temperature
{Oversampling::X1, Oversampling::X1}, {Oversampling::X2, Oversampling::X1}, {Oversampling::X4, Oversampling::X1},
{Oversampling::X8, Oversampling::X1}, {Oversampling::X16, Oversampling::X2},
};
constexpr Filter filter_table[] = {
Filter::Off, Filter::Coeff2, Filter::Coeff4, Filter::Coeff8, Filter::Coeff16,
};
constexpr Standby standby_table[] = {
Standby::Time0_5ms, Standby::Time62_5ms, Standby::Time125ms, Standby::Time250ms,
Standby::Time500ms, Standby::Time1sec, Standby::Time2sec, Standby::Time4sec,
};
constexpr uint32_t standby_time_table[] = {1, 63, 125, 250, 500, 1000, 2000, 4000};
constexpr PowerMode pw_table[] = {
PowerMode::Sleep,
PowerMode::Forced,
PowerMode::Normal,
};
constexpr UseCase uc_table[] = {
UseCase::LowPower, UseCase::Dynamic, UseCase::Weather, UseCase::Elevator, UseCase::Drop, UseCase::Indoor,
};
struct UseCaseSetting {
OversamplingSetting osrss;
Filter filter;
Standby st;
};
constexpr UseCaseSetting uc_val_table[] = {
{OversamplingSetting::UltraHighResolution, Filter::Coeff4, Standby::Time62_5ms},
{OversamplingSetting::StandardResolution, Filter::Coeff16, Standby::Time0_5ms},
{OversamplingSetting::UltraLowPower, Filter::Off, Standby::Time4sec},
{OversamplingSetting::StandardResolution, Filter::Coeff4, Standby::Time125ms},
{OversamplingSetting::LowPower, Filter::Off, Standby::Time0_5ms},
{OversamplingSetting::UltraHighResolution, Filter::Coeff16, Standby::Time0_5ms},
};
template <class U>
elapsed_time_t test_periodic(U* unit, const uint32_t times, const uint32_t measure_duration = 0)
{
auto tm = unit->interval();
auto timeout_at = m5::utility::millis() + 10 * 1000;
do {
unit->update();
if (unit->updated()) {
break;
}
std::this_thread::yield();
} while (!unit->updated() && m5::utility::millis() <= timeout_at);
// timeout
if (!unit->updated()) {
return 0;
}
//
uint32_t measured{};
auto start_at = m5::utility::millis();
timeout_at = start_at + (times * (tm + measure_duration) * 2);
do {
unit->update();
measured += unit->updated() ? 1 : 0;
if (measured >= times) {
break;
}
std::this_thread::yield();
// m5::utility::delay(1);
} while (measured < times && m5::utility::millis() <= timeout_at);
return (measured == times) ? m5::utility::millis() - start_at : 0;
// return (measured == times) ? unit->updatedMillis() - start_at : 0;
}
uint32_t calculate_measure_time(const Oversampling osrsP, const Oversampling osrsT, const Filter f)
{
uint32_t px = ((1U << m5::stl::to_underlying(osrsP) >> 1));
uint32_t tx = ((1U << m5::stl::to_underlying(osrsT) >> 1));
// uint32_t fx = (1U << m5::stl::to_underlying(f));
// if (fx == 1) {
// fx = 0;
// }
// M5_LOGI("%u,%u,%u => %u,%u,%u", osrsP, osrsT, f,
// px,tx,fx);
float pt = 2.3f * px;
float tt = 2.3f * tx;
// float ft = 0.5f * fx;
return pt + tt + 0.5f;
}
} // namespace
TEST_P(TestBMP280, Settings)
{
SCOPED_TRACE(ustr);
// Oversampling
if (1) {
// This process fails during periodic measurements.
EXPECT_TRUE(unit->inPeriodic());
for (auto&& po : os_table) {
for (auto&& to : os_table) {
auto s = m5::utility::formatString("OSRS:%u/%u", po, to);
SCOPED_TRACE(s);
EXPECT_FALSE(unit->writeOversampling(po, to));
EXPECT_FALSE(unit->writeOversamplingPressure(po));
EXPECT_FALSE(unit->writeOversamplingTemperature(to));
}
}
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& po : os_table) {
for (auto&& to : os_table) {
Oversampling p;
Oversampling t;
auto s = m5::utility::formatString("OSRS:%u/%u", po, to);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writeOversampling(po, to));
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_EQ(p, po);
EXPECT_EQ(t, to);
// Write reverse settings and check
EXPECT_TRUE(unit->writeOversamplingPressure(to));
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_EQ(p, to);
EXPECT_EQ(t, to);
EXPECT_TRUE(unit->writeOversamplingTemperature(po));
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_EQ(p, to);
EXPECT_EQ(t, po);
}
}
}
// OversamplingSettings
if (1) {
// This process fails during periodic measurements.
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
for (auto& oss : oss_table) {
auto s = m5::utility::formatString("OSS:%u", oss);
SCOPED_TRACE(s);
EXPECT_FALSE(unit->writeOversampling(oss));
}
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
uint32_t idx{};
for (auto& oss : oss_table) {
auto s = m5::utility::formatString("OSS:%u", oss);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writeOversampling(oss));
Oversampling p;
Oversampling t;
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_EQ(p, osrss_table[idx][0]);
EXPECT_EQ(t, osrss_table[idx][1]);
++idx;
}
}
// Filter
if (1) {
// This process fails during periodic measurements.
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
for (auto&& e : filter_table) {
auto s = m5::utility::formatString("F:%u", e);
SCOPED_TRACE(s);
EXPECT_FALSE(unit->writeFilter(e));
}
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& e : filter_table) {
auto s = m5::utility::formatString("F:%u", e);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writeFilter(e));
Filter f;
EXPECT_TRUE(unit->readFilter(f));
EXPECT_EQ(f, e);
}
}
// Standby
if (1) {
// This process fails during periodic measurements.
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
for (auto&& e : standby_table) {
auto s = m5::utility::formatString("ST:%u", e);
SCOPED_TRACE(s);
EXPECT_FALSE(unit->writeStandbyTime(e));
}
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& e : standby_table) {
auto s = m5::utility::formatString("ST:%u", e);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writeStandbyTime(e));
Standby st;
EXPECT_TRUE(unit->readStandbyTime(st));
EXPECT_EQ(st, e);
}
}
// PowerMode
if (1) {
for (auto&& pw : pw_table) {
auto s = m5::utility::formatString("PM:%u", pw);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writePowerMode(pw));
PowerMode p{};
EXPECT_TRUE(unit->readPowerMode(p));
EXPECT_EQ(p, pw);
}
}
}
TEST_P(TestBMP280, UseCase)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->inPeriodic());
// This process fails during periodic measurements.
for (auto&& uc : uc_table) {
auto s = m5::utility::formatString("UC:%u", uc);
SCOPED_TRACE(s);
EXPECT_FALSE(unit->writeUseCaseSetting(uc));
}
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& uc : uc_table) {
auto s = m5::utility::formatString("UC:%u", uc);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writeUseCaseSetting(uc));
Oversampling p{};
Oversampling t{};
Filter f{};
Standby st{};
const auto& val = uc_val_table[m5::stl::to_underlying(uc)];
const auto& osrrs = osrss_table[m5::stl::to_underlying(val.osrss)];
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_TRUE(unit->readFilter(f));
EXPECT_TRUE(unit->readStandbyTime(st));
EXPECT_EQ(p, osrrs[0]);
EXPECT_EQ(t, osrrs[1]);
EXPECT_EQ(f, val.filter);
EXPECT_EQ(st, val.st);
}
}
TEST_P(TestBMP280, Reset)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->inPeriodic());
Oversampling p{}, t{};
Filter f{};
Standby s{};
PowerMode pm{};
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_TRUE(unit->readFilter(f));
EXPECT_TRUE(unit->readStandbyTime(s));
EXPECT_TRUE(unit->readPowerMode(pm));
EXPECT_NE(p, Oversampling::Skipped);
EXPECT_NE(t, Oversampling::Skipped);
EXPECT_NE(f, Filter::Off);
EXPECT_NE(s, Standby::Time0_5ms);
EXPECT_EQ(pm, PowerMode::Normal);
EXPECT_TRUE(unit->softReset());
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_TRUE(unit->readFilter(f));
EXPECT_TRUE(unit->readStandbyTime(s));
EXPECT_TRUE(unit->readPowerMode(pm));
EXPECT_EQ(p, Oversampling::Skipped);
EXPECT_EQ(t, Oversampling::Skipped);
EXPECT_EQ(f, Filter::Off);
EXPECT_EQ(s, Standby::Time0_5ms);
EXPECT_EQ(pm, PowerMode::Sleep);
}
TEST_P(TestBMP280, SingleShot)
{
SCOPED_TRACE(ustr);
bmp280::Data discard{};
// This process fails during periodic measurements.
EXPECT_TRUE(unit->inPeriodic());
EXPECT_FALSE(unit->measureSingleshot(discard));
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
// Standby time for periodic measurement, does not affect single shots
EXPECT_TRUE(unit->writeStandbyTime(Standby::Time4sec));
for (auto&& po : os_table) {
for (auto&& to : os_table) {
for (auto&& coeff : filter_table) {
auto s = m5::utility::formatString("Singleshot OS:%u/%u F:%u", po, to, coeff);
SCOPED_TRACE(s);
// Specify settings and measure
bmp280::Data d{};
bool can_not_measure = to == Oversampling::Skipped;
bool only_temperature = to != Oversampling::Skipped && po == Oversampling::Skipped;
// M5_LOGI("%s:<%u><%u>", s.c_str(), can_not_measure, only_temperature);
if (can_not_measure) {
EXPECT_FALSE(unit->measureSingleshot(d, po, to, coeff));
} else if (only_temperature) {
EXPECT_TRUE(unit->measureSingleshot(d, po, to, coeff));
// M5_LOGI("%f/%f", d.celsius(), d.pressure());
EXPECT_TRUE(std::isfinite(d.celsius()));
EXPECT_TRUE(std::isfinite(d.fahrenheit()));
EXPECT_FALSE(std::isfinite(d.pressure()));
} else {
EXPECT_TRUE(unit->measureSingleshot(d, po, to, coeff));
// M5_LOGI("%f/%f", d.celsius(), d.pressure());
EXPECT_TRUE(std::isfinite(d.celsius()));
EXPECT_TRUE(std::isfinite(d.fahrenheit()));
EXPECT_TRUE(std::isfinite(d.pressure()));
}
if (!can_not_measure) {
Oversampling t;
Oversampling p;
Filter f;
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_TRUE(unit->readFilter(f));
EXPECT_EQ(p, po);
EXPECT_EQ(t, to);
EXPECT_EQ(f, coeff);
}
}
}
}
}
TEST_P(TestBMP280, Periodic)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& uc : uc_table) {
auto s = m5::utility::formatString("UC:%u", uc);
SCOPED_TRACE(s);
const auto& val = uc_val_table[m5::stl::to_underlying(uc)];
const auto& osrrs = osrss_table[m5::stl::to_underlying(val.osrss)];
elapsed_time_t tm{};
if (val.st == Standby::Time0_5ms) {
tm = calculate_measure_time(osrrs[0], osrrs[1], val.filter);
} else {
tm = standby_time_table[m5::stl::to_underlying(val.st)];
}
EXPECT_TRUE(unit->writeUseCaseSetting(uc));
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
auto elapsed = test_periodic(unit.get(), STORED_SIZE, tm);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
// M5_LOGW("E:%ld tm:%ld/%ld", elapsed, tm, tm * STORED_SIZE);
EXPECT_NE(elapsed, 0);
EXPECT_LE(elapsed, STORED_SIZE * tm);
//
EXPECT_EQ(unit->available(), STORED_SIZE);
EXPECT_FALSE(unit->empty());
EXPECT_TRUE(unit->full());
uint32_t cnt{STORED_SIZE / 2};
while (cnt-- && unit->available()) {
EXPECT_TRUE(std::isfinite(unit->temperature()));
EXPECT_TRUE(std::isfinite(unit->fahrenheit()));
EXPECT_TRUE(std::isfinite(unit->pressure()));
EXPECT_FLOAT_EQ(unit->temperature(), unit->oldest().temperature());
EXPECT_FLOAT_EQ(unit->pressure(), unit->oldest().pressure());
EXPECT_FALSE(unit->empty());
unit->discard();
}
EXPECT_EQ(unit->available(), STORED_SIZE / 2);
EXPECT_FALSE(unit->empty());
EXPECT_FALSE(unit->full());
unit->flush();
EXPECT_EQ(unit->available(), 0);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
EXPECT_FALSE(std::isfinite(unit->temperature()));
EXPECT_FALSE(std::isfinite(unit->pressure()));
}
}

View file

@ -0,0 +1,499 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for UnitQMP6988
*/
#include <gtest/gtest.h>
#include <Wire.h>
#include <M5Unified.h>
#include <M5UnitUnified.hpp>
#include <googletest/test_template.hpp>
#include <googletest/test_helper.hpp>
#include <unit/unit_QMP6988.hpp>
#include <chrono>
#include <cmath>
#include <random>
using namespace m5::unit::googletest;
using namespace m5::unit;
using namespace m5::unit::qmp6988;
using namespace m5::unit::qmp6988::command;
using m5::unit::types::elapsed_time_t;
const ::testing::Environment* global_fixture = ::testing::AddGlobalTestEnvironment(new GlobalFixture<400000U>());
constexpr uint32_t STORED_SIZE{8};
class TestQMP6988 : public ComponentTestBase<UnitQMP6988, bool> {
protected:
virtual UnitQMP6988* get_instance() override
{
auto ptr = new m5::unit::UnitQMP6988();
auto ccfg = ptr->component_config();
ccfg.stored_size = STORED_SIZE;
ptr->component_config(ccfg);
return ptr;
}
virtual bool is_using_hal() const override
{
return GetParam();
};
};
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestQMP6988,
// ::testing::Values(false, true));
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestQMP6988, ::testing::Values(true));
INSTANTIATE_TEST_SUITE_P(ParamValues, TestQMP6988, ::testing::Values(false));
namespace {
constexpr Oversampling os_table[] = {
Oversampling::Skipped, Oversampling::X1, Oversampling::X2, Oversampling::X4,
Oversampling::X8, Oversampling::X16, Oversampling::X32, Oversampling::X64,
};
constexpr OversamplingSetting oss_table[] = {
OversamplingSetting::HighSpeed, OversamplingSetting::LowPower, OversamplingSetting::Standard,
OversamplingSetting::HighAccuracy, OversamplingSetting::UltraHightAccuracy,
};
constexpr Oversampling osrss_table[][2] = {
// Pressure, Temperature
{Oversampling::X2, Oversampling::X1}, {Oversampling::X4, Oversampling::X1}, {Oversampling::X8, Oversampling::X1},
{Oversampling::X16, Oversampling::X2}, {Oversampling::X32, Oversampling::X4},
};
constexpr Filter filter_table[] = {
Filter::Off, Filter::Coeff2, Filter::Coeff4, Filter::Coeff8, Filter::Coeff16, Filter::Coeff32,
};
constexpr Standby standby_table[] = {
Standby::Time1ms, Standby::Time5ms, Standby::Time50ms, Standby::Time250ms,
Standby::Time500ms, Standby::Time1sec, Standby::Time2sec, Standby::Time4sec,
};
constexpr PowerMode pw_table[] = {
PowerMode::Sleep,
PowerMode::Forced,
PowerMode::Normal,
};
constexpr UseCase uc_table[] = {
UseCase::Weather, UseCase::Drop, UseCase::Elevator, UseCase::Stair, UseCase::Indoor,
};
struct UseCaseSetting {
OversamplingSetting osrss;
Filter filter;
};
constexpr UseCaseSetting uc_val_table[] = {
{OversamplingSetting::HighSpeed, Filter::Off},
{OversamplingSetting::LowPower, Filter::Off},
{OversamplingSetting::Standard, Filter::Coeff4},
{OversamplingSetting::HighAccuracy, Filter::Coeff8},
{OversamplingSetting::UltraHightAccuracy, Filter::Coeff32},
};
template <class U>
elapsed_time_t test_periodic(U* unit, const uint32_t times, const uint32_t measure_duration = 0)
{
auto tm = unit->interval();
auto timeout_at = m5::utility::millis() + 8 * 1000;
// First measured
do {
unit->update();
if (unit->updated()) {
break;
}
std::this_thread::yield();
} while (!unit->updated() && m5::utility::millis() <= timeout_at);
// timeout
if (!unit->updated()) {
return 0;
}
//
uint32_t measured{};
auto start_at = m5::utility::millis();
timeout_at = start_at + (times * (tm + measure_duration) * 2);
do {
unit->update();
measured += unit->updated() ? 1 : 0;
if (measured >= times) {
break;
}
std::this_thread::yield();
// m5::utility::delay(1);
} while (measured < times && m5::utility::millis() <= timeout_at);
if (measured == times) {
return m5::utility::millis() - start_at;
}
M5_LOGE("measured:%u", measured);
return -1;
}
} // namespace
TEST_P(TestQMP6988, Settings)
{
SCOPED_TRACE(ustr);
// Oversampling
if (1) {
// This process fails during periodic measurements.
EXPECT_TRUE(unit->inPeriodic());
for (auto&& po : os_table) {
for (auto&& to : os_table) {
auto s = m5::utility::formatString("OSRS:%u/%u", po, to);
SCOPED_TRACE(s);
EXPECT_FALSE(unit->writeOversampling(po, to));
EXPECT_FALSE(unit->writeOversamplingPressure(po));
EXPECT_FALSE(unit->writeOversamplingTemperature(to));
}
}
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& po : os_table) {
for (auto&& to : os_table) {
Oversampling p;
Oversampling t;
auto s = m5::utility::formatString("OSRS:%u/%u", po, to);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writeOversampling(po, to));
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_EQ(p, po);
EXPECT_EQ(t, to);
// Write reverse settings and check
EXPECT_TRUE(unit->writeOversamplingPressure(to));
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_EQ(p, to);
EXPECT_EQ(t, to);
EXPECT_TRUE(unit->writeOversamplingTemperature(po));
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_EQ(p, to);
EXPECT_EQ(t, po);
}
}
}
// OversamplingSettings
if (1) {
// This process fails during periodic measurements.
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
for (auto& oss : oss_table) {
auto s = m5::utility::formatString("OSS:%u", oss);
SCOPED_TRACE(s);
EXPECT_FALSE(unit->writeOversampling(oss));
}
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
uint32_t idx{};
for (auto& oss : oss_table) {
auto s = m5::utility::formatString("OSS:%u", oss);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writeOversampling(oss));
Oversampling p;
Oversampling t;
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_EQ(p, osrss_table[idx][0]);
EXPECT_EQ(t, osrss_table[idx][1]);
++idx;
}
}
// Filter
if (1) {
// This process fails during periodic measurements.
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
for (auto&& e : filter_table) {
auto s = m5::utility::formatString("F:%u", e);
SCOPED_TRACE(s);
EXPECT_FALSE(unit->writeFilter(e));
}
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& e : filter_table) {
auto s = m5::utility::formatString("F:%u", e);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writeFilter(e));
Filter f;
EXPECT_TRUE(unit->readFilter(f));
EXPECT_EQ(f, e);
}
}
// Standby
if (1) {
// This process fails during periodic measurements.
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
for (auto&& e : standby_table) {
auto s = m5::utility::formatString("ST:%u", e);
SCOPED_TRACE(s);
EXPECT_FALSE(unit->writeStandbyTime(e));
}
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& e : standby_table) {
auto s = m5::utility::formatString("ST:%u", e);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writeStandbyTime(e));
Standby st;
EXPECT_TRUE(unit->readStandbyTime(st));
EXPECT_EQ(st, e);
}
}
// PowerMode
if (1) {
for (auto&& pw : pw_table) {
auto s = m5::utility::formatString("PM:%u", pw);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writePowerMode(pw));
PowerMode p{};
EXPECT_TRUE(unit->readPowerMode(p));
EXPECT_EQ(p, pw);
}
}
}
TEST_P(TestQMP6988, UseCase)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->inPeriodic());
// This process fails during periodic measurements.
for (auto&& uc : uc_table) {
auto s = m5::utility::formatString("UC:%u", uc);
SCOPED_TRACE(s);
EXPECT_FALSE(unit->writeUseCaseSetting(uc));
}
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& uc : uc_table) {
auto s = m5::utility::formatString("UC:%u", uc);
SCOPED_TRACE(s);
EXPECT_TRUE(unit->writeUseCaseSetting(uc));
Oversampling p{};
Oversampling t{};
Filter f{};
const auto& val = uc_val_table[m5::stl::to_underlying(uc)];
const auto& osrrs = osrss_table[m5::stl::to_underlying(val.osrss)];
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_TRUE(unit->readFilter(f));
EXPECT_EQ(p, osrrs[0]);
EXPECT_EQ(t, osrrs[1]);
EXPECT_EQ(f, val.filter);
}
}
TEST_P(TestQMP6988, Reset)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->inPeriodic());
Oversampling p{}, t{};
Filter f{};
Standby s{};
PowerMode pm{};
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_TRUE(unit->readFilter(f));
EXPECT_TRUE(unit->readStandbyTime(s));
EXPECT_TRUE(unit->readPowerMode(pm));
EXPECT_NE(p, Oversampling::Skipped);
EXPECT_NE(t, Oversampling::Skipped);
EXPECT_NE(f, Filter::Off);
EXPECT_NE(s, Standby::Time1ms);
EXPECT_EQ(pm, PowerMode::Normal);
EXPECT_TRUE(unit->softReset());
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_TRUE(unit->readFilter(f));
EXPECT_TRUE(unit->readStandbyTime(s));
EXPECT_TRUE(unit->readPowerMode(pm));
EXPECT_EQ(p, Oversampling::Skipped);
EXPECT_EQ(t, Oversampling::Skipped);
EXPECT_EQ(f, Filter::Off);
EXPECT_EQ(s, Standby::Time1ms);
EXPECT_EQ(pm, PowerMode::Sleep);
}
TEST_P(TestQMP6988, SingleShot)
{
SCOPED_TRACE(ustr);
// This process fails during periodic measurements.
EXPECT_TRUE(unit->inPeriodic());
qmp6988::Data discard{};
EXPECT_FALSE(unit->measureSingleshot(discard));
// Success if not in periodic measurement
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
// Standby time for periodic measurement, does not affect single shots
EXPECT_TRUE(unit->writeStandbyTime(Standby::Time4sec));
for (auto&& po : os_table) {
for (auto&& to : os_table) {
for (auto&& coeff : filter_table) {
auto s = m5::utility::formatString("Singleshot OS:%u/%u F:%u", po, to, coeff);
SCOPED_TRACE(s);
// Specify settings and measure
qmp6988::Data d{};
bool can_not_measure = to == Oversampling::Skipped;
bool only_temperature = to != Oversampling::Skipped && po == Oversampling::Skipped;
// M5_LOGI("%s:<%u><%u>", s.c_str(), can_not_measure, only_temperature);
if (can_not_measure) {
EXPECT_FALSE(unit->measureSingleshot(d, po, to, coeff));
} else if (only_temperature) {
EXPECT_TRUE(unit->measureSingleshot(d, po, to, coeff));
// M5_LOGI("%f/%f/%f", d.celsius(), d.fahrenheit(), d.pressure());
EXPECT_TRUE(std::isfinite(d.celsius()));
EXPECT_TRUE(std::isfinite(d.fahrenheit()));
EXPECT_FALSE(std::isfinite(d.pressure()));
} else {
EXPECT_TRUE(unit->measureSingleshot(d, po, to, coeff));
// M5_LOGI("%f/%f", d.celsius(), d.pressure());
EXPECT_TRUE(std::isfinite(d.celsius()));
EXPECT_TRUE(std::isfinite(d.fahrenheit()));
EXPECT_TRUE(std::isfinite(d.pressure()));
}
if (!can_not_measure) {
Oversampling t;
Oversampling p;
Filter f;
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_TRUE(unit->readFilter(f));
EXPECT_EQ(p, po);
EXPECT_EQ(t, to);
EXPECT_EQ(f, coeff);
}
}
}
}
}
TEST_P(TestQMP6988, Periodic)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& uc : uc_table) {
for (auto&& st : standby_table) {
auto s = m5::utility::formatString("UC:%u ST:%u", uc, st);
SCOPED_TRACE(s);
// M5_LOGW("%s", s.c_str());
const auto& val = uc_val_table[m5::stl::to_underlying(uc)];
const auto& osrrs = osrss_table[m5::stl::to_underlying(val.osrss)];
EXPECT_TRUE(unit->writeUseCaseSetting(uc));
EXPECT_TRUE(unit->writeStandbyTime(st));
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
auto tm = unit->interval();
auto elapsed = test_periodic(unit.get(), STORED_SIZE, (int)st == 0 ? ((uint32_t)uc + 1) * 2 : 0);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
// M5_LOGW("E:(%u) %ld", tm == 1 ? (uint32_t)uc * 2 : 0, elapsed);
EXPECT_NE(elapsed, 0);
EXPECT_NE(elapsed, -1);
EXPECT_GE(elapsed, STORED_SIZE * tm - 1);
Oversampling t;
Oversampling p;
Filter f;
EXPECT_TRUE(unit->readOversampling(p, t));
EXPECT_TRUE(unit->readFilter(f));
EXPECT_EQ(p, osrrs[0]);
EXPECT_EQ(t, osrrs[1]);
EXPECT_EQ(f, val.filter);
//
EXPECT_EQ(unit->available(), STORED_SIZE);
EXPECT_FALSE(unit->empty());
EXPECT_TRUE(unit->full());
uint32_t cnt{STORED_SIZE / 2};
while (cnt-- && unit->available()) {
// M5_LOGI("%f/%f/%f", unit->celsius(), unit->fahrenheit(), unit->pressure());
EXPECT_TRUE(std::isfinite(unit->temperature()));
EXPECT_TRUE(std::isfinite(unit->fahrenheit()));
EXPECT_TRUE(std::isfinite(unit->pressure()));
EXPECT_FLOAT_EQ(unit->temperature(), unit->oldest().temperature());
EXPECT_FLOAT_EQ(unit->pressure(), unit->oldest().pressure());
EXPECT_FALSE(unit->empty());
unit->discard();
}
EXPECT_EQ(unit->available(), STORED_SIZE / 2);
EXPECT_FALSE(unit->empty());
EXPECT_FALSE(unit->full());
unit->flush();
EXPECT_EQ(unit->available(), 0);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
EXPECT_FALSE(std::isfinite(unit->temperature()));
EXPECT_FALSE(std::isfinite(unit->pressure()));
}
}
}

View file

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for UnitSCD40
*/
#include <gtest/gtest.h>
#include <Wire.h>
#include <M5Unified.h>
#include <M5UnitUnified.hpp>
#include <googletest/test_template.hpp>
#include <googletest/test_helper.hpp>
#include <unit/unit_SCD40.hpp>
#include <chrono>
#include <iostream>
using namespace m5::unit::googletest;
using namespace m5::unit;
using namespace m5::unit::scd4x;
using m5::unit::types::elapsed_time_t;
const ::testing::Environment* global_fixture = ::testing::AddGlobalTestEnvironment(new GlobalFixture<400000U>());
constexpr uint32_t STORED_SIZE{4};
class TestSCD4x : public ComponentTestBase<UnitSCD40, bool> {
protected:
virtual UnitSCD40* get_instance() override
{
auto ptr = new m5::unit::UnitSCD40();
auto ccfg = ptr->component_config();
ccfg.stored_size = STORED_SIZE;
ptr->component_config(ccfg);
auto cfg = ptr->config();
cfg.start_periodic = false;
ptr->config(cfg);
return ptr;
}
virtual bool is_using_hal() const override
{
return GetParam();
};
};
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestSCD4x, ::testing::Values(false, true));
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestSCD4x, ::testing::Values(true));
INSTANTIATE_TEST_SUITE_P(ParamValues, TestSCD4x, ::testing::Values(false));
namespace {
} // namespace
#include "../scd4x_test.inl"

View file

@ -0,0 +1,142 @@
/*
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for UnitSCD41
*/
#include <gtest/gtest.h>
#include <Wire.h>
#include <M5Unified.h>
#include <M5UnitUnified.hpp>
#include <googletest/test_template.hpp>
#include <unit/unit_SCD41.hpp>
#include <chrono>
#include <iostream>
using namespace m5::unit::googletest;
using namespace m5::unit;
using namespace m5::unit::scd4x;
using m5::unit::types::elapsed_time_t;
const ::testing::Environment* global_fixture = ::testing::AddGlobalTestEnvironment(new GlobalFixture<400000U>());
constexpr uint32_t STORED_SIZE{4};
class TestSCD4x : public ComponentTestBase<UnitSCD41, bool> {
protected:
virtual UnitSCD41* get_instance() override
{
auto ptr = new m5::unit::UnitSCD41();
auto ccfg = ptr->component_config();
ccfg.stored_size = STORED_SIZE;
ptr->component_config(ccfg);
auto cfg = ptr->config();
cfg.start_periodic = false;
ptr->config(cfg);
return ptr;
}
virtual bool is_using_hal() const override
{
return GetParam();
};
};
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestSCD4x, ::testing::Values(false, true));
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestSCD4x, ::testing::Values(true));
INSTANTIATE_TEST_SUITE_P(ParamValues, TestSCD4x, ::testing::Values(false));
namespace {
} // namespace
#include "../scd4x_test.inl"
TEST_P(TestSCD4x, Singleshot)
{
SCOPED_TRACE(ustr);
{
Data d{};
EXPECT_FALSE(unit->inPeriodic());
EXPECT_TRUE(unit->measureSingleshot(d));
EXPECT_NE(d.co2(), 0);
EXPECT_TRUE(std::isfinite(d.temperature()));
EXPECT_TRUE(std::isfinite(d.humidity()));
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
EXPECT_FALSE(unit->measureSingleshot(d));
EXPECT_EQ(d.co2(), 0);
EXPECT_FLOAT_EQ(d.temperature(), -45.f);
EXPECT_FLOAT_EQ(d.humidity(), 0.0f);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
}
{
Data d{};
EXPECT_FALSE(unit->inPeriodic());
EXPECT_TRUE(unit->measureSingleshotRHT(d));
EXPECT_EQ(d.co2(), 0);
EXPECT_TRUE(std::isfinite(d.temperature()));
EXPECT_TRUE(std::isfinite(d.humidity()));
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
EXPECT_FALSE(unit->measureSingleshotRHT(d));
EXPECT_EQ(d.co2(), 0);
EXPECT_FLOAT_EQ(d.temperature(), -45.f);
EXPECT_FLOAT_EQ(d.humidity(), 0.0f);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
}
}
TEST_P(TestSCD4x, PowerMode)
{
SCOPED_TRACE(ustr);
EXPECT_FALSE(unit->inPeriodic());
uint32_t count{8};
while (count--) {
EXPECT_TRUE(unit->powerDown()) << count;
EXPECT_TRUE(unit->wakeup()) << count;
}
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
EXPECT_FALSE(unit->powerDown());
EXPECT_FALSE(unit->wakeup());
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_TRUE(unit->reInit());
}
TEST_P(TestSCD4x, ASC)
{
SCOPED_TRACE(ustr);
constexpr uint16_t hours_table[] = {0, 32768, 65535};
for (auto&& h : hours_table) {
EXPECT_TRUE(unit->writeAutomaticSelfCalibrationInitialPeriod(h));
EXPECT_TRUE(unit->writeAutomaticSelfCalibrationStandardPeriod(h));
uint16_t ih{}, sh{};
EXPECT_TRUE(unit->readAutomaticSelfCalibrationInitialPeriod(ih));
EXPECT_TRUE(unit->readAutomaticSelfCalibrationStandardPeriod(sh));
EXPECT_EQ(ih, (h >> 2) << 2);
EXPECT_EQ(sh, (h >> 2) << 2);
}
EXPECT_TRUE(unit->startPeriodicMeasurement());
EXPECT_TRUE(unit->inPeriodic());
for (auto&& h : hours_table) {
EXPECT_FALSE(unit->writeAutomaticSelfCalibrationInitialPeriod(h));
EXPECT_FALSE(unit->writeAutomaticSelfCalibrationStandardPeriod(h));
}
}

View file

@ -0,0 +1,208 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for UnitSGP30
*/
#include <gtest/gtest.h>
#include <Wire.h>
#include <M5Unified.h>
#include <M5UnitUnified.hpp>
#include <googletest/test_template.hpp>
#include <googletest/test_helper.hpp>
#include <unit/unit_SGP30.hpp>
using namespace m5::unit::googletest;
using namespace m5::unit;
using namespace m5::unit::sgp30;
const ::testing::Environment* global_fixture = ::testing::AddGlobalTestEnvironment(new GlobalFixture<400000U>());
class TestSGP30 : public ComponentTestBase<UnitSGP30, bool> {
protected:
virtual UnitSGP30* get_instance() override
{
auto* ptr = new m5::unit::UnitSGP30();
if (ptr) {
auto ccfg = ptr->component_config();
ccfg.stored_size = 4;
ptr->component_config(ccfg);
auto cfg = ptr->config();
cfg.start_periodic = false;
ptr->config(cfg);
}
return ptr;
}
virtual bool is_using_hal() const override
{
return GetParam();
};
};
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestSGP30, ::testing::Values(false, true));
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestSGP30, ::testing::Values(true));
INSTANTIATE_TEST_SUITE_P(ParamValues, TestSGP30, ::testing::Values(false));
namespace {
void check_measurement_values(UnitSGP30* u)
{
EXPECT_NE(u->co2eq(), 0xFFFFU);
EXPECT_NE(u->tvoc(), 0XFFFFU);
// readRaw test
uint16_t h{}, e{};
EXPECT_TRUE(u->readRaw(h, e));
EXPECT_NE(h, 0U);
EXPECT_NE(e, 0U);
}
void wait15sec()
{
auto to = m5::utility::millis() + 15 * 1000;
do {
m5::utility::delay(1);
} while (m5::utility::millis() < to);
}
} // namespace
TEST_P(TestSGP30, FeatureSet)
{
SCOPED_TRACE(ustr);
EXPECT_NE(unit->productVersion(), 0U);
M5_LOGI("productVersion:%x", unit->productVersion());
Feature f{};
EXPECT_TRUE(unit->readFeatureSet(f));
EXPECT_EQ(f.productType(), 0U);
EXPECT_NE(f.productVersion(), 0U);
EXPECT_EQ(unit->productVersion(), f.productVersion());
}
TEST_P(TestSGP30, selfTest)
{
SCOPED_TRACE(ustr);
uint16_t result{};
EXPECT_TRUE(unit->measureTest(result));
EXPECT_EQ(result, 0xD400);
}
TEST_P(TestSGP30, serialNumber)
{
SCOPED_TRACE(ustr);
// Read direct [MSB] SNB_3, SNB_2, CRC, SNB_1, SNB_0, CRC [LSB]
std::array<uint8_t, 9> rbuf{};
EXPECT_TRUE(unit->readRegister(command::GET_SERIAL_ID, rbuf.data(), rbuf.size(), 1));
// M5_LOGI("%02x%02x%02x%02x%02x%02x", rbuf[0], rbuf[1], rbuf[3],
// rbuf[4],
// rbuf[6], rbuf[7]);
m5::types::big_uint16_t w0(rbuf[0], rbuf[1]);
m5::types::big_uint16_t w1(rbuf[3], rbuf[4]);
m5::types::big_uint16_t w2(rbuf[6], rbuf[7]);
uint64_t d_sno = (((uint64_t)w0.get()) << 32) | (((uint64_t)w1.get()) << 16) | ((uint64_t)w2.get());
// M5_LOGI("d_sno[%llX]", d_sno);
//
uint64_t sno{};
char ssno[13]{};
EXPECT_TRUE(unit->readSerialNumber(sno));
EXPECT_TRUE(unit->readSerialNumber(ssno));
// M5_LOGI("s:[%s] uint64:[%x]", ssno, sno);
EXPECT_EQ(sno, d_sno);
std::stringstream stream;
stream << std::uppercase << std::setw(12) << std::hex << std::setfill('0') << sno;
std::string s(stream.str());
EXPECT_STREQ(s.c_str(), ssno);
}
TEST_P(TestSGP30, generalReset)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->startPeriodicMeasurement(0x1234, 0x5678, 0x9ABC));
M5_LOGW("SGP30 measurement starts 15 seconds after begin");
EXPECT_TRUE(unit->inPeriodic());
wait15sec();
uint16_t co2eq{}, tvoc{}, inceptive_tvoc{};
EXPECT_TRUE(unit->readIaqBaseline(co2eq, tvoc));
// EXPECT_TRUE(unit->readTvocInceptiveBaseline(inceptive_tvoc));
// M5_LOGW("%x/%x/%x", co2eq, tvoc, inceptive_tvoc);
EXPECT_EQ(co2eq, 0x1234);
EXPECT_EQ(tvoc, 0x5678);
// EXPECT_EQ(inceptive_tvoc, 0x1122);
EXPECT_TRUE(unit->generalReset());
EXPECT_TRUE(unit->readIaqBaseline(co2eq, tvoc));
// EXPECT_TRUE(unit->readTvocInceptiveBaseline(inceptive_tvoc));
// M5_LOGW("%x/%x/%x", co2eq, tvoc, inceptive_tvoc);
EXPECT_EQ(co2eq, 0x0000);
EXPECT_EQ(tvoc, 0x0000);
// EXPECT_EQ(inceptive_tvoc, 0x0000);
}
TEST_P(TestSGP30, Periodic)
{
SCOPED_TRACE(ustr);
EXPECT_FALSE(unit->inPeriodic());
{
uint32_t cnt{10};
uint16_t h2{}, et{};
while (cnt--) {
EXPECT_TRUE(unit->readRaw(h2, et));
}
}
EXPECT_TRUE(unit->startPeriodicMeasurement(0, 0, 0));
M5_LOGW("SGP30 measurement starts 15 seconds after begin");
EXPECT_TRUE(unit->inPeriodic());
wait15sec();
test_periodic_measurement(unit.get(), 4, check_measurement_values);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
EXPECT_EQ(unit->available(), 4);
EXPECT_TRUE(unit->full());
EXPECT_FALSE(unit->empty());
uint32_t cnt{2};
while (unit->available() && cnt--) {
EXPECT_FALSE(unit->empty());
// M5_LOGW("C:%u T:%u", unit->co2eq(), unit->tvoc());
EXPECT_EQ(unit->co2eq(), unit->oldest().co2eq());
EXPECT_EQ(unit->tvoc(), unit->oldest().tvoc());
unit->discard();
}
EXPECT_EQ(unit->available(), 2);
EXPECT_FALSE(unit->full());
EXPECT_FALSE(unit->empty());
unit->flush();
EXPECT_EQ(unit->co2eq(), 0XFFFF);
EXPECT_EQ(unit->tvoc(), 0XFFFF);
EXPECT_EQ(unit->available(), 0);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
}

View file

@ -0,0 +1,355 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for UnitSHT30
*/
#include <gtest/gtest.h>
#include <Wire.h>
#include <M5Unified.h>
#include <M5UnitUnified.hpp>
#include <googletest/test_template.hpp>
#include <googletest/test_helper.hpp>
#include <unit/unit_SHT30.hpp>
#include <chrono>
#include <iostream>
#include <iomanip>
#include <bitset>
using namespace m5::unit::googletest;
using namespace m5::unit;
using namespace m5::unit::sht30;
using namespace m5::unit::sht30::command;
constexpr size_t STORED_SIZE{4};
const ::testing::Environment* global_fixture = ::testing::AddGlobalTestEnvironment(new GlobalFixture<400000U>());
class TestSHT30 : public ComponentTestBase<UnitSHT30, bool> {
protected:
virtual UnitSHT30* get_instance() override
{
auto ptr = new m5::unit::UnitSHT30();
auto ccfg = ptr->component_config();
ccfg.stored_size = STORED_SIZE;
ptr->component_config(ccfg);
return ptr;
}
virtual bool is_using_hal() const override
{
return GetParam();
};
};
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestSHT30,
// ::testing::Values(false, true));
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestSHT30, ::testing::Values(true));
INSTANTIATE_TEST_SUITE_P(ParamValues, TestSHT30, ::testing::Values(false));
namespace {
// flot t uu int16 (temperature)
constexpr uint16_t float_to_uint16(const float f)
{
return f * 65536 / 175;
}
std::tuple<const char*, Repeatability, bool> ss_table[] = {
{"HighTrue", Repeatability::High, true}, {"MediumTrue", Repeatability::Medium, true},
{"LowTrue", Repeatability::Low, true}, {"HighFalse", Repeatability::High, false},
{"MediumFalse", Repeatability::Medium, false}, {"LowFalse", Repeatability::Low, false},
};
void check_measurement_values(UnitSHT30* u)
{
EXPECT_TRUE(std::isfinite(u->latest().celsius()));
EXPECT_TRUE(std::isfinite(u->latest().fahrenheit()));
EXPECT_TRUE(std::isfinite(u->latest().humidity()));
}
} // namespace
TEST_P(TestSHT30, SingleShot)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
for (auto&& e : ss_table) {
const char* s{};
Repeatability rep;
bool stretch{};
std::tie(s, rep, stretch) = e;
SCOPED_TRACE(s);
int cnt{10}; // repeat 10 times
while (cnt--) {
sht30::Data d{};
EXPECT_TRUE(unit->measureSingleshot(d, rep, stretch)) << (int)rep << " : " << stretch;
EXPECT_TRUE(std::isfinite(d.temperature()));
EXPECT_TRUE(std::isfinite(d.humidity()));
}
}
}
TEST_P(TestSHT30, Periodic)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
constexpr std::tuple<const char*, MPS, Repeatability> table[] = {
//
{"HalfHigh", MPS::Half, Repeatability::High},
{"HalfMedium", MPS::Half, Repeatability::Medium},
{"HalfLow", MPS::Half, Repeatability::Low},
//
{"1High", MPS::One, Repeatability::High},
{"1Medium", MPS::One, Repeatability::Medium},
{"1Low", MPS::One, Repeatability::Low},
//
{"2High", MPS::Two, Repeatability::High},
{"2Medium", MPS::Two, Repeatability::Medium},
{"2Low", MPS::Two, Repeatability::Low},
//
{"4fHigh", MPS::Four, Repeatability::High},
{"4Medium", MPS::Four, Repeatability::Medium},
{"4Low", MPS::Four, Repeatability::Low},
//
{"10fHigh", MPS::Ten, Repeatability::High},
{"10Medium", MPS::Ten, Repeatability::Medium},
{"10Low", MPS::Ten, Repeatability::Low},
};
for (auto&& e : table) {
const char* s{};
MPS mps;
Repeatability rep;
std::tie(s, mps, rep) = e;
SCOPED_TRACE(s);
EXPECT_TRUE(unit->startPeriodicMeasurement(mps, rep));
EXPECT_TRUE(unit->inPeriodic());
// Cannot call all singleshot in periodic
for (auto&& e : ss_table) {
const char* s{};
Repeatability rep;
bool stretch{};
std::tie(s, rep, stretch) = e;
sht30::Data d{};
SCOPED_TRACE(s);
EXPECT_FALSE(unit->measureSingleshot(d, rep, stretch));
}
test_periodic_measurement(unit.get(), 4, 1, check_measurement_values);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
EXPECT_EQ(unit->available(), STORED_SIZE);
EXPECT_FALSE(unit->empty());
EXPECT_TRUE(unit->full());
uint32_t cnt{2};
while (cnt-- && unit->available()) {
// M5_LOGI("%s T:%f H:%f", s, unit->temperature(), unit->humidity());
EXPECT_TRUE(std::isfinite(unit->temperature()));
EXPECT_TRUE(std::isfinite(unit->humidity()));
EXPECT_FLOAT_EQ(unit->temperature(), unit->oldest().temperature());
EXPECT_FLOAT_EQ(unit->humidity(), unit->oldest().humidity());
EXPECT_FALSE(unit->empty());
unit->discard();
}
EXPECT_EQ(unit->available(), STORED_SIZE - 2);
EXPECT_FALSE(unit->empty());
EXPECT_FALSE(unit->full());
unit->flush();
EXPECT_EQ(unit->available(), 0);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
EXPECT_FALSE(std::isfinite(unit->temperature()));
EXPECT_FALSE(std::isfinite(unit->humidity()));
}
// ART Command (4 mps)
EXPECT_FALSE(unit->inPeriodic());
EXPECT_FALSE(unit->writeModeAccelerateResponseTime());
EXPECT_TRUE(unit->startPeriodicMeasurement(MPS::Half,
Repeatability::High)); // 0.5mps
EXPECT_TRUE(unit->inPeriodic());
EXPECT_EQ(unit->updatedMillis(), 0);
EXPECT_TRUE(unit->writeModeAccelerateResponseTime()); // boost to 4mps
// Cannot call all singleshot in periodic
for (auto&& e : ss_table) {
const char* s{};
Repeatability rep;
bool stretch{};
std::tie(s, rep, stretch) = e;
sht30::Data d{};
SCOPED_TRACE(s);
EXPECT_FALSE(unit->measureSingleshot(d, rep, stretch));
}
test_periodic_measurement(unit.get(), 4, 1, check_measurement_values);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
EXPECT_EQ(unit->available(), STORED_SIZE);
EXPECT_FALSE(unit->empty());
EXPECT_TRUE(unit->full());
// M5_LOGI("ART T:%f H:%f", unit->temperature(), unit->humidity());
EXPECT_TRUE(std::isfinite(unit->temperature()));
EXPECT_TRUE(std::isfinite(unit->humidity()));
EXPECT_FLOAT_EQ(unit->temperature(), unit->oldest().temperature());
EXPECT_FLOAT_EQ(unit->humidity(), unit->oldest().humidity());
unit->flush();
EXPECT_TRUE(std::isnan(unit->temperature()));
EXPECT_TRUE(std::isnan(unit->humidity()));
EXPECT_EQ(unit->available(), 0);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
// startPeriodicMeasurement after ART (ART is disabled)
EXPECT_TRUE(unit->startPeriodicMeasurement(MPS::Two,
Repeatability::High)); // 2 mps
EXPECT_TRUE(unit->inPeriodic());
EXPECT_EQ(unit->updatedMillis(), 0);
std::array<uint8_t, 6> rbuf{};
types::elapsed_time_t timeout_at{}, now{}, at[2]{};
uint32_t idx{};
timeout_at = m5::utility::millis() + 1100;
do {
m5::utility::delay(1);
at[idx] = now = m5::utility::millis();
unit->update();
if (unit->updated()) {
++idx;
}
} while (idx < 2 && now <= timeout_at);
EXPECT_EQ(idx, 2);
auto diff = at[1] - at[0];
EXPECT_GT(diff, 250); // 2mps(500) > 4mps(250)
}
namespace {
void printStatus(const Status& s)
{
#if 0
std::bitset<16> bits(s.value);
M5_LOGI("[%s]: %u/%u/%u/%u/%u/%u/%u", bits.to_string().c_str(),
s.alertPending(), s.heater(), s.trackingAlertRH(),
s.trackingAlert(), s.reset(), s.command(), s.checksum());
#endif
}
} // namespace
TEST_P(TestSHT30, HeaterAndStatus)
{
SCOPED_TRACE(ustr);
Status s{};
EXPECT_TRUE(unit->startHeater());
EXPECT_TRUE(unit->readStatus(s));
printStatus(s);
EXPECT_TRUE(s.heater());
// clearStatus will not clear heater status
EXPECT_TRUE(unit->clearStatus());
EXPECT_TRUE(unit->readStatus(s));
printStatus(s);
EXPECT_TRUE(s.heater());
EXPECT_TRUE(unit->stopHeater());
EXPECT_TRUE(unit->readStatus(s));
printStatus(s);
EXPECT_FALSE(s.heater());
}
TEST_P(TestSHT30, SoftReset)
{
SCOPED_TRACE(ustr);
// Soft reset is only possible in standby mode.
EXPECT_FALSE(unit->softReset());
EXPECT_TRUE(unit->stopPeriodicMeasurement());
Status s{};
// After a reset, the heaters are set to a deactivated state as a default
// condition (*1)
EXPECT_TRUE(unit->startHeater());
EXPECT_TRUE(unit->softReset());
EXPECT_TRUE(unit->readStatus(s));
EXPECT_FALSE(s.alertPending());
EXPECT_FALSE(s.heater()); // *1
EXPECT_FALSE(s.trackingAlertRH());
EXPECT_FALSE(s.trackingAlert());
EXPECT_FALSE(s.reset());
EXPECT_FALSE(s.command());
EXPECT_FALSE(s.checksum());
}
TEST_P(TestSHT30, GeneralReset)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->startHeater());
EXPECT_TRUE(unit->generalReset());
Status s{};
EXPECT_TRUE(unit->readStatus(s));
// The ALERT pin will also become active (high) after powerup and after
// resets
EXPECT_TRUE(s.alertPending());
EXPECT_FALSE(s.heater());
EXPECT_FALSE(s.trackingAlertRH());
EXPECT_FALSE(s.trackingAlert());
EXPECT_TRUE(s.reset());
EXPECT_FALSE(s.command());
EXPECT_FALSE(s.checksum());
}
TEST_P(TestSHT30, SerialNumber)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
{
// Read direct [MSB] SNB_3, SNB_2, CRC, SNB_1, SNB_0, CRC [LSB]
std::array<uint8_t, 6> rbuf{};
EXPECT_TRUE(unit->readRegister(GET_SERIAL_NUMBER_ENABLE_STRETCH, rbuf.data(), rbuf.size(), 1));
uint32_t d_sno = (((uint32_t)rbuf[0]) << 24) | (((uint32_t)rbuf[1]) << 16) | (((uint32_t)rbuf[3]) << 8) |
((uint32_t)rbuf[4]);
//
uint32_t sno{};
char ssno[9]{};
EXPECT_TRUE(unit->readSerialNumber(sno));
EXPECT_TRUE(unit->readSerialNumber(ssno));
EXPECT_EQ(sno, d_sno);
// M5_LOGI("s:[%s] uint32:[%x]", ssno, sno);
std::stringstream stream;
stream << std::uppercase << std::setw(8) << std::setfill('0') << std::hex << sno;
std::string s(stream.str());
EXPECT_STREQ(s.c_str(), ssno);
}
}

View file

@ -0,0 +1,245 @@
/*
* SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
/*
UnitTest for UnitSHT30
*/
#include <gtest/gtest.h>
#include <Wire.h>
#include <M5Unified.h>
#include <M5UnitUnified.hpp>
#include <googletest/test_template.hpp>
#include <googletest/test_helper.hpp>
#include <unit/unit_SHT40.hpp>
#include <chrono>
#include <cmath>
#include <random>
using namespace m5::unit::googletest;
using namespace m5::unit;
using namespace m5::unit::sht40;
using namespace m5::unit::sht40::command;
using m5::unit::types::elapsed_time_t;
constexpr size_t STORED_SIZE{4};
const ::testing::Environment* global_fixture = ::testing::AddGlobalTestEnvironment(new GlobalFixture<400000U>());
class TestSHT40 : public ComponentTestBase<UnitSHT40, bool> {
protected:
virtual UnitSHT40* get_instance() override
{
auto ptr = new m5::unit::UnitSHT40();
auto ccfg = ptr->component_config();
ccfg.stored_size = STORED_SIZE;
ptr->component_config(ccfg);
return ptr;
}
virtual bool is_using_hal() const override
{
return GetParam();
};
};
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestSHT40,
// ::testing::Values(false, true));
// INSTANTIATE_TEST_SUITE_P(ParamValues, TestSHT40, ::testing::Values(true));
INSTANTIATE_TEST_SUITE_P(ParamValues, TestSHT40, ::testing::Values(false));
namespace {
template <class U>
elapsed_time_t test_periodic(U* unit, const uint32_t times)
{
auto timeout_at = m5::utility::millis() + (times * unit->interval() * 2);
// First read
while (!unit->updated() && m5::utility::millis() < timeout_at) {
std::this_thread::yield();
unit->update();
}
// timeout
if (!unit->updated()) {
return 0;
}
//
uint32_t measured{};
auto start_at = m5::utility::millis();
unit->update();
do {
m5::utility::delay(1);
unit->update();
measured += unit->updated() ? 1 : 0;
} while (measured < times && m5::utility::millis() < timeout_at);
return (measured == times) ? m5::utility::millis() - start_at : 0;
}
std::tuple<const char*, Precision, Heater, elapsed_time_t> sm_table[] = {
//
{"HighLong", Precision::High, Heater::Long, 9},
{"HighShort", Precision::High, Heater::Short, 9},
{"HighNone", Precision::High, Heater::None, 9},
//
{"MediumLong", Precision::Medium, Heater::Long, 5},
{"MediumSHort", Precision::Medium, Heater::Short, 5},
{"MediumNone", Precision::Medium, Heater::None, 5},
//
{"LowLong", Precision::Low, Heater::Long, 2},
{"LowShort", Precision::Low, Heater::Short, 2},
{"LowNone", Precision::Low, Heater::None, 2},
};
} // namespace
TEST_P(TestSHT40, SoftReset)
{
SCOPED_TRACE(ustr);
// Soft reset is only possible in standby mode.
EXPECT_FALSE(unit->softReset());
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
EXPECT_TRUE(unit->softReset());
}
TEST_P(TestSHT40, GeneralReset)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->generalReset());
}
TEST_P(TestSHT40, SerialNumber)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
{
// Read direct [MSB] SNB_3, SNB_2, CRC, SNB_1, SNB_0, CRC [LSB]
std::array<uint8_t, 6> rbuf{};
EXPECT_TRUE(unit->readRegister(GET_SERIAL_NUMBER, rbuf.data(), rbuf.size(), 1));
uint32_t d_sno = (((uint32_t)rbuf[0]) << 24) | (((uint32_t)rbuf[1]) << 16) | (((uint32_t)rbuf[3]) << 8) |
((uint32_t)rbuf[4]);
//
uint32_t sno{};
char ssno[9]{};
EXPECT_TRUE(unit->readSerialNumber(sno));
EXPECT_TRUE(unit->readSerialNumber(ssno));
EXPECT_EQ(sno, d_sno);
// M5_LOGI("s:[%s] uint32:[%x]", ssno, sno);
std::stringstream stream;
stream << std::uppercase << std::setw(8) << std::setfill('0') << std::hex << sno;
std::string s(stream.str());
EXPECT_STREQ(s.c_str(), ssno);
}
}
TEST_P(TestSHT40, SingleShot)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& e : sm_table) {
const char* s{};
Precision p{};
Heater h{};
elapsed_time_t tm{};
std::tie(s, p, h, tm) = e;
SCOPED_TRACE(s);
uint32_t cnt{5}; // repeat 5 times
while (cnt--) {
sht40::Data d{};
EXPECT_TRUE(unit->measureSingleshot(d, p, h));
EXPECT_TRUE(std::isfinite(d.temperature()));
EXPECT_TRUE(std::isfinite(d.humidity()));
EXPECT_EQ(d.heater, h != Heater::None);
}
}
}
TEST_P(TestSHT40, Periodic)
{
SCOPED_TRACE(ustr);
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
for (auto&& e : sm_table) {
const char* s{};
Precision p{};
Heater h{};
elapsed_time_t tm{};
std::tie(s, p, h, tm) = e;
SCOPED_TRACE(s);
EXPECT_TRUE(unit->startPeriodicMeasurement(p, h));
EXPECT_TRUE(unit->inPeriodic());
// Cannot call all singleshot in periodic
for (auto&& single : sm_table) {
const char* s{};
Precision p{};
Heater h{};
elapsed_time_t tm{};
std::tie(s, p, h, tm) = single;
sht40::Data d{};
EXPECT_FALSE(unit->measureSingleshot(d, p, h));
}
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
//
EXPECT_TRUE(unit->startPeriodicMeasurement(p, h));
EXPECT_TRUE(unit->inPeriodic());
auto elapsed = test_periodic(unit.get(), STORED_SIZE);
EXPECT_NE(elapsed, 0);
EXPECT_GE(elapsed, STORED_SIZE * tm);
EXPECT_LE(elapsed, STORED_SIZE * tm + 1);
// M5_LOGW("[%s] %lu %zu", s, elapsed, unit->available());
EXPECT_TRUE(unit->stopPeriodicMeasurement());
EXPECT_FALSE(unit->inPeriodic());
EXPECT_EQ(unit->available(), STORED_SIZE);
EXPECT_FALSE(unit->empty());
EXPECT_TRUE(unit->full());
uint32_t cnt{2};
while (cnt-- && unit->available()) {
EXPECT_TRUE(std::isfinite(unit->temperature()));
EXPECT_TRUE(std::isfinite(unit->fahrenheit()));
EXPECT_TRUE(std::isfinite(unit->humidity()));
EXPECT_FLOAT_EQ(unit->temperature(), unit->oldest().temperature());
EXPECT_FLOAT_EQ(unit->humidity(), unit->oldest().humidity());
EXPECT_FALSE(unit->empty());
unit->discard();
}
EXPECT_EQ(unit->available(), STORED_SIZE - 2);
EXPECT_FALSE(unit->empty());
EXPECT_FALSE(unit->full());
unit->flush();
EXPECT_EQ(unit->available(), 0);
EXPECT_TRUE(unit->empty());
EXPECT_FALSE(unit->full());
EXPECT_FALSE(std::isfinite(unit->temperature()));
EXPECT_FALSE(std::isfinite(unit->humidity()));
}
}