From 0ee489ef7d936306b58604dd837bc3fb1e9546f1 Mon Sep 17 00:00:00 2001 From: GOB Date: Thu, 27 Jun 2024 17:33:35 +0900 Subject: [PATCH] Add UnitKmeterISO --- examples/UnitMeter/KmeterISO/KmeterISO.cpp | 250 ++++++++++++++++++ lib/M5UnitComponent/src/M5UnitComponent.cpp | 9 + lib/M5UnitComponent/src/M5UnitComponent.hpp | 5 +- .../src/m5_unit_component/adapter.cpp | 20 +- lib/M5UnitMeter/src/unit/unit_KmeterISO.cpp | 191 +++++++++++++ lib/M5UnitMeter/src/unit/unit_KmeterISO.hpp | 237 +++++++++++++++++ platformio.ini | 32 ++- .../test_kmeterISO/kmeterISO_test.cpp | 146 ++++++++++ test/unit_unified_test.cpp | 2 + 9 files changed, 885 insertions(+), 7 deletions(-) create mode 100644 examples/UnitMeter/KmeterISO/KmeterISO.cpp create mode 100644 lib/M5UnitMeter/src/unit/unit_KmeterISO.cpp create mode 100644 lib/M5UnitMeter/src/unit/unit_KmeterISO.hpp create mode 100644 test/embedded/test_kmeterISO/kmeterISO_test.cpp diff --git a/examples/UnitMeter/KmeterISO/KmeterISO.cpp b/examples/UnitMeter/KmeterISO/KmeterISO.cpp new file mode 100644 index 0000000..946f0dc --- /dev/null +++ b/examples/UnitMeter/KmeterISO/KmeterISO.cpp @@ -0,0 +1,250 @@ +/* + Example: UnitKmeterISO + + SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD + + SPDX-License-Identifier: MIT +*/ + +// #define USING_M5HAL // When using M5HAL + +#include +#include +#include +#if !defined(USING_M5HAL) +#include +#endif + +namespace { +auto& display = M5.Display; +M5Canvas canvas[2]; + +m5::unit::UnitUnified Units; +m5::unit::UnitKmeterISO unit; + +// M5_KMeter::error_code_t* errdata_buf; +constexpr size_t avg_count = 1 << 5; +constexpr size_t delay_msec = 50; +float* tempdata_buf; +size_t tempdata_count; +size_t tempdata_idx = 0; +int graph_height; +int graph_y_offset; +float min_temp; +float max_temp; +float avg_buf[avg_count]; +size_t avg_index = 0; +int vertline_idx = 0; +} // namespace + +void setup() { + M5.begin(); + + m5::utility::delay(3000); + + auto pin_num_sda = M5.getPin(m5::pin_name_t::port_a_sda); + auto pin_num_scl = M5.getPin(m5::pin_name_t::port_a_scl); + M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl); + +#if defined(USING_M5HAL) +#pragma message "Using M5HAL" + // Using M5HAL + m5::hal::bus::I2CBusConfig i2c_cfg; + i2c_cfg.pin_sda = m5::hal::gpio::getPin(pin_num_sda); + i2c_cfg.pin_scl = m5::hal::gpio::getPin(pin_num_scl); + auto i2c_bus = m5::hal::bus::i2c::getBus(i2c_cfg); + M5_LOGI("Bus:%d", i2c_bus.has_value()); + if (!Units.add(unit, i2c_bus ? i2c_bus.value() : nullptr) || + !Units.begin()) { + M5_LOGE("Failed to begin"); + display.clear(TFT_RED); + while (true) { + m5::utility::delay(10000); + } + } +#else +#pragma message "Using Wire" + // Using TwoWire + Wire.begin(pin_num_sda, pin_num_scl, 100000U); + for (int i = 0; i < 10; ++i) { + Wire.beginTransmission(unit.address()); + auto wret = Wire.endTransmission(); + M5_LOGW(">>%d", wret); + delay(10); + } + + if (!Units.add(unit, Wire) || !Units.begin()) { + M5_LOGE("Failed to begin"); + display.clear(TFT_RED); + while (true) { + m5::utility::delay(10000); + } + } +#endif + + M5_LOGI("M5UnitUnified has been begun"); + M5_LOGI("%s", Units.debugInfo().c_str()); + display.clear(TFT_DARKGREEN); + + // + display.clear(); + display.setEpdMode(epd_mode_t::epd_fast); + if (display.width() > display.height()) { + display.setRotation(display.getRotation() ^ 1); + } + display.setFont(&fonts::Font4); + display.setTextColor((uint32_t)~display.getBaseColor(), + display.getBaseColor()); + display.setTextDatum(textdatum_t::top_right); + display.setTextPadding(display.textWidth("1888.88", &fonts::Font4)); + graph_y_offset = display.fontHeight(&fonts::Font4); + + graph_height = display.height() - graph_y_offset; + + for (int i = 0; i < 2; ++i) { + canvas[i].setColorDepth(display.getColorDepth()); + canvas[i].createSprite(1, graph_height); + canvas[i].setTextDatum(textdatum_t::bottom_right); + canvas[i].setTextColor(TFT_LIGHTGRAY); + } + + tempdata_count = display.width() + 1; + tempdata_buf = (float*)malloc(tempdata_count * sizeof(float)); + + int32_t tmp{}; + float temperature = unit.readCelsiusTemperature(tmp) + ? m5::unit::UnitKmeterISO::conversion(tmp) + : 0.0f; + min_temp = max_temp = temperature; + for (size_t i = 0; i < tempdata_count; ++i) { + tempdata_buf[i] = temperature; + } + for (size_t i = 0; i < avg_count; ++i) { + avg_buf[i] = temperature; + } +} + +void drawGraph(void) { + float min_t = INT16_MAX; + float max_t = INT16_MIN; + for (int i = 0; i < tempdata_count; ++i) { + float t = tempdata_buf[i]; + if (min_t > t) { + min_t = t; + } + if (max_t < t) { + max_t = t; + } + } + min_temp = (min_temp + (min_t - 0.5f)) / 2; + max_temp = (max_temp + (max_t + 0.5f)) / 2; + + float magnify = (float)graph_height / (max_temp - min_temp); + + static constexpr int steps[] = {1, 2, 5, 10, 20, + 50, 100, 200, 500, INT_MAX}; + int step_index = 0; + while (magnify * steps[step_index] < 10) { + ++step_index; + } + int step = steps[step_index]; + bool flip = 0; + + canvas[flip].clear(display.getBaseColor()); + + int gauge = ((int)min_temp / step) * step; + do { + canvas[flip].drawPixel(0, graph_height - ((gauge - min_temp) * magnify), + TFT_DARKGRAY); + } while (max_temp > (gauge += step)); + + size_t buffer_len = canvas[flip].bufferLength(); + auto buffer = (uint8_t*)alloca(buffer_len); + memcpy(buffer, canvas[flip].getBuffer(), buffer_len); + + int drawindex = tempdata_idx; + int draw_x = -1; + int y0 = 0; + + if (++vertline_idx >= 20) { + vertline_idx = 0; + } + int vidx = vertline_idx; + + display.startWrite(); + do { + if (++vidx >= 20) { + vidx = 0; + memset(canvas[flip].getBuffer(), 0x55, buffer_len); + } else { + memcpy(canvas[flip].getBuffer(), buffer, buffer_len); + } + + int y1 = y0; + y0 = graph_height - ((tempdata_buf[drawindex] - min_temp) * magnify); + if (++drawindex >= tempdata_count) { + drawindex = 0; + } + + if (display.width() - draw_x < 24) { + gauge = ((int)min_temp / step) * step; + do { + canvas[flip].drawNumber( + (int)gauge, display.width() - draw_x, + graph_height - ((gauge - min_temp) * magnify)); + } while (max_temp > (gauge += step)); + } + + canvas[flip].setColor(~display.getBaseColor()); + // : 0xFF0000u); + int y_min = y0, y_max = y1; + if (y_min > y_max) { + std::swap(y_min, y_max); + } + canvas[flip].drawFastVLine(0, y_min, y_max - y_min + 1); + canvas[flip].pushSprite(&display, draw_x, graph_y_offset); + flip = !flip; + } while (++draw_x < display.width()); + display.endWrite(); +} + +void loop() { + M5.update(); + Units.update(); + if (M5.BtnA.wasClicked()) { + } + + if (unit.updated()) { + // M5_LOGI("\n>Current:%f", std::fabs(raw) * correction); + } + + float temperature = unit.celsiusTemperature(); + + avg_buf[avg_index] = temperature; + if (++avg_index >= avg_count) { + avg_index = 0; + } + float avg_temp = 0; + for (size_t i = 0; i < avg_count; ++i) { + size_t k = abs((int)(i - avg_index) * 2 + 1); + if (k > avg_count) { + k = avg_count * 2 - k; + } + avg_temp += avg_buf[i] * k / avg_count; + } + + tempdata_buf[tempdata_idx] = 2 * avg_temp / avg_count; + if (++tempdata_idx >= tempdata_count) { + tempdata_idx = 0; + } + + drawGraph(); + + static uint32_t prev_msec; + uint32_t msec = millis(); + if (msec - prev_msec < delay_msec) { + M5_LOGI("\n>Temp:%6.2f", temperature); + m5gfx::delay(delay_msec - (msec - prev_msec)); + } + prev_msec = msec; +} diff --git a/lib/M5UnitComponent/src/M5UnitComponent.cpp b/lib/M5UnitComponent/src/M5UnitComponent.cpp index 2345f98..ce6a1c6 100644 --- a/lib/M5UnitComponent/src/M5UnitComponent.cpp +++ b/lib/M5UnitComponent/src/M5UnitComponent.cpp @@ -290,6 +290,15 @@ bool Component::generalCall(const uint8_t* data, const size_t len) { return _adapter->generalCall(data, len) == m5::hal::error::error_t::OK; } +bool Component::changeAddress(const uint8_t addr) { + if (m5::utility::isValidI2CAddress(addr)) { + _addr = addr; + _adapter.reset(_adapter->duplicate(addr)); + return true; + } + return false; +} + std::string Component::debugInfo() const { return m5::utility::formatString( "[%s]:ID{0X%08x}:ADDR{0X%02x/0X%02x} parent:%u children:%zu", diff --git a/lib/M5UnitComponent/src/M5UnitComponent.hpp b/lib/M5UnitComponent/src/M5UnitComponent.hpp index 0275605..c33dc2d 100644 --- a/lib/M5UnitComponent/src/M5UnitComponent.hpp +++ b/lib/M5UnitComponent/src/M5UnitComponent.hpp @@ -323,6 +323,8 @@ class Component { } bool add_child(Component* c); + // Functions for dynamically addressable devices + bool changeAddress(const uint8_t addr); protected: UnitUnified* _manager{}; @@ -347,7 +349,6 @@ class Component { } // namespace unit } // namespace m5 - ///@cond #define M5_UNIT_COMPONENT_HPP_BUILDER(cls, reg) \ public: \ @@ -371,6 +372,6 @@ class Component { inline virtual types::attr_t unit_attribute() const override { \ return attr; \ } \ -///@endcond + ///@endcond #endif diff --git a/lib/M5UnitComponent/src/m5_unit_component/adapter.cpp b/lib/M5UnitComponent/src/m5_unit_component/adapter.cpp index 406a572..5875c74 100644 --- a/lib/M5UnitComponent/src/m5_unit_component/adapter.cpp +++ b/lib/M5UnitComponent/src/m5_unit_component/adapter.cpp @@ -59,7 +59,7 @@ class WireImpl : public Adapter::Impl { } auto ret = _wire->endTransmission(stop); if (ret) { - M5_LIB_LOGE("%d endTransmission", ret); + M5_LIB_LOGE("%d endTransmission stop:%d", ret, stop); } return (ret == 0) ? m5::hal::error::error_t::OK : m5::hal::error::error_t::I2C_BUS_ERROR; @@ -78,7 +78,7 @@ class WireImpl : public Adapter::Impl { } auto ret = _wire->endTransmission(stop); if (ret) { - M5_LIB_LOGE("%d endTransmission", ret); + M5_LIB_LOGE("%d endTransmission stop:%d", ret, stop); } return (ret == 0) ? m5::hal::error::error_t::OK : m5::hal::error::error_t::I2C_BUS_ERROR; @@ -93,6 +93,10 @@ class WireImpl : public Adapter::Impl { return write_with_transaction(0x00, data, len, true); } + virtual m5::hal::error::error_t wakeup() { + return write_with_transaction(_addr, nullptr, 0, true); + } + protected: m5::hal::error::error_t write_with_transaction(const uint8_t addr, const uint8_t* data, @@ -104,7 +108,7 @@ class WireImpl : public Adapter::Impl { } auto ret = _wire->endTransmission(stop); if (ret) { - M5_LIB_LOGE("%d endTransmission", ret); + M5_LIB_LOGE("%d endTransmission stop:%d", ret, stop); } return (ret == 0) ? m5::hal::error::error_t::OK : m5::hal::error::error_t::I2C_BUS_ERROR; @@ -250,6 +254,10 @@ struct BusImpl : public Adapter::Impl { return write_with_transaction(gcfg, data, len, true); } + virtual m5::hal::error::error_t wakeup() { + return write_with_transaction(_access_cfg, nullptr, 0, true); + } + protected: m5::hal::error::error_t write_with_transaction( const m5::hal::bus::I2CMasterAccessConfig& cfg, const uint8_t* data, @@ -261,7 +269,11 @@ struct BusImpl : public Adapter::Impl { auto result = trans->startWrite() .and_then([&trans, &data, &len]() { - return trans->write(data, len); + return ((data && len) + ? trans->write(data, len) + : m5::stl::expected< + size_t, m5::hal::error::error_t>( + (size_t)0UL)); }) .and_then([&trans, &stop](size_t&&) { return stop ? trans->stop() diff --git a/lib/M5UnitMeter/src/unit/unit_KmeterISO.cpp b/lib/M5UnitMeter/src/unit/unit_KmeterISO.cpp new file mode 100644 index 0000000..c07060b --- /dev/null +++ b/lib/M5UnitMeter/src/unit/unit_KmeterISO.cpp @@ -0,0 +1,191 @@ +/*! + @file unit_KmeterISO.cpp + @brief KmeterISO Unit for M5UnitUnified + + SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD + + SPDX-License-Identifier: MIT +*/ +#include "unit_KmeterISO.hpp" +#include +#include +#include + +namespace { + +template +T array_to_type(const std::array& a) { + static_assert(std::is_integral::value && sizeof(T) == 4, "Invalid type"); + return (((uint32_t)a[3]) << 24) | (((uint32_t)a[2]) << 16) | + (((uint32_t)a[1]) << 8) | (((uint32_t)a[0]) << 0); +} +} // namespace + +namespace m5 { +namespace unit { + +using namespace m5::utility::mmh3; +using namespace kmeter; +using namespace kmeter::command; + +// class UnitKmeterISO +const char UnitKmeterISO::name[] = "UnitKmeterISO"; +const types::uid_t UnitKmeterISO::uid{"UnitKmeterISO"_mmh3}; +const types::uid_t UnitKmeterISO::attr{0}; + +bool UnitKmeterISO::begin() { + uint8_t ver{0x00}; + if (!readFirmwareVersion(ver) && (ver == 0x00)) { + M5_LIB_LOGE("Failed to read version"); + return false; + } + return _cfg.periodic ? startPeriodicMeasurement(_cfg.interval) : true; +} + +void UnitKmeterISO::update() { + if (inPeriodic()) { + unsigned long at{m5::utility::millis()}; + if (!_latest || at >= _latest + _interval) { + _updated = read_measurement(); + if (_updated) { + _latest = at; + } + } else { + _updated = false; + } + } +} + +bool UnitKmeterISO::startPeriodicMeasurement(const uint32_t interval) { + _interval = interval; + _periodic = true; + _latest = 0; + return true; +} + +bool UnitKmeterISO::stopPeriodicMeasurement() { + _periodic = _updated = false; + return true; +} + +bool UnitKmeterISO::readStatus(uint8_t& status) { + return readRegister8(ERROR_STATUS_REG, status, 0); +} + +bool UnitKmeterISO::readFirmwareVersion(uint8_t& ver) { + return readRegister8(FIRMWARE_VERSION_REG, ver, 0); +} + +bool UnitKmeterISO::readCelsiusTemperature(int32_t& ct) { + std::array rbuf{}; + if (readRegister(TEMP_CELSIUS_VAL_REG, rbuf.data(), rbuf.size(), 0)) { + ct = array_to_type(rbuf); + return true; + } + return false; +} + +bool UnitKmeterISO::readFahrenheitTemperature(int32_t& ft) { + std::array rbuf{}; + if (readRegister(TEMP_FAHRENHEIT_VAL_REG, rbuf.data(), rbuf.size(), 0)) { + ft = array_to_type(rbuf); + return true; + } + return false; +} + +bool UnitKmeterISO::readInternalCelsiusTemperature(int32_t& ct) { + std::array rbuf{}; + if (readRegister(INTERNAL_TEMP_CELSIUS_VAL_REG, rbuf.data(), rbuf.size(), + 0)) { + ct = array_to_type(rbuf); + return true; + } + return false; +} + +bool UnitKmeterISO::readInternalFahrenheitTemperature(int32_t& ft) { + std::array rbuf{}; + if (readRegister(INTERNAL_TEMP_FAHRENHEIT_VAL_REG, rbuf.data(), rbuf.size(), + 0)) { + ft = array_to_type(rbuf); + return true; + } + return false; +} + +bool UnitKmeterISO::readCelsiusTemperatureString(char* str) { + if (str && readRegister(TEMP_CELSIUS_STRING_REG, (uint8_t*)str, 8U, 0)) { + str[8] = '\0'; + return true; + } + return false; +} + +bool UnitKmeterISO::readFahrenheitTemperatureString(char* str) { + if (str && readRegister(TEMP_FAHRENHEIT_STRING_REG, (uint8_t*)str, 8U, 0)) { + str[8] = '\0'; + return true; + } + return false; +} + +bool UnitKmeterISO::readInternalCelsiusTemperatureString(char* str) { + if (readRegister(INTERNAL_TEMP_CELSIUS_STRING_REG, (uint8_t*)str, 8U, 0)) { + str[8] = '\0'; + return true; + } + return false; +} + +bool UnitKmeterISO::readInternalFahrenheitTemperatureString(char* str) { + if (str && readRegister(INTERNAL_TEMP_FAHRENHEIT_STRING_REG, (uint8_t*)str, + 8U, 0)) { + str[8] = '\0'; + return true; + } + return false; +} + +bool UnitKmeterISO::changeI2CAddress(const uint8_t i2c_address) { + if (!m5::utility::isValidI2CAddress(i2c_address)) { + M5_LIB_LOGE("Invalid address : %02X", i2c_address); + return false; + } + if (writeRegister8(I2C_ADDRESS_REG, i2c_address) && + changeAddress(i2c_address)) { + // Wait wakeup + uint8_t v{}; + bool done{}; + auto timeout_at = m5::utility::millis() + 100; + do { + done = readRegister8(I2C_ADDRESS_REG, v, 0); + std::this_thread::yield(); + } while (!done && m5::utility::millis() <= timeout_at); + return done; + } + return false; +} + +bool UnitKmeterISO::readI2CAddress(uint8_t& i2c_address) { + return readRegister8(I2C_ADDRESS_REG, i2c_address, 0); +} + +// +bool UnitKmeterISO::read_measurement() { + uint8_t status{0xFF}; + if (!readStatus(status) || (status != 0)) { + M5_LIB_LOGW("Not read or error:%Xx", status); + return false; + } + int32_t c{}, f{}; + if (readCelsiusTemperature(c) && readFahrenheitTemperature(f)) { + _temperatureC = conversion(c); + _temperatureF = conversion(f); + return true; + } + return false; +} + +} // namespace unit +} // namespace m5 diff --git a/lib/M5UnitMeter/src/unit/unit_KmeterISO.hpp b/lib/M5UnitMeter/src/unit/unit_KmeterISO.hpp new file mode 100644 index 0000000..2a1ddb3 --- /dev/null +++ b/lib/M5UnitMeter/src/unit/unit_KmeterISO.hpp @@ -0,0 +1,237 @@ +/* + * SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD + */ +/*! + @file unit_KmeterISO.hpp + @brief KmeterISO Unit for M5UnitUnified + + KMeterISO unitis an integrated K-type thermocouple sensor unit that integrates + the functions of "acquisition + isolation + communication", using + STM32F030+MAX31855KASA 14bit thermocouple digital conversion chip scheme to + achieve high-precision temperature acquisition and conversion, MCU using + STM32F030 to realize data acquisition and I2C communication interface, using + CA-IS3641HW as a signal isolator. The unit supports access to thermocouple + probes with a measurement range of -200°C to 1350°C, and adopts a universal + standard K-type flat interface, which is convenient for subsequent replacement + of different measuring probes to match different needs. This module is widely + used in application scenarios such as temperature collection, control, and + monitoring in industrial automation, instrumentation, power and electrical, + heat treatment and other fields. + + + + SPDX-License-Identifier: MIT +*/ +#ifndef M5_UNIT_METER_UNIT_K_METER_HPP +#define M5_UNIT_METER_UNIT_K_METER_HPP + +#include + +namespace m5 { +namespace unit { + +/*! + @class UnitKmeterISO + @brief KMeterISO unitis an integrated K-type thermocouple sensor unit that + integrates the functions of "acquisition + isolation + communication" + */ +class UnitKmeterISO : public Component { + M5_UNIT_COMPONENT_HPP_BUILDER(UnitKmeterISO, 0x66); // 66 + + public: + /*! + @struct config_t + @brief Settings + */ + struct config_t { + bool periodic{true}; //!< @brief start periodic? + uint32_t interval{1000}; //!< @breif periodic interval + }; + + explicit UnitKmeterISO(const uint8_t addr = DEFAULT_ADDRESS) + : Component(addr) { + } + virtual ~UnitKmeterISO() { + } + + virtual bool begin() override; + virtual void update() override; + +#if 0 + ///@name Settings + ///@{ + /*! @brief Gets the configration */ + config_t config() { + return _cfg; + } + //! @brief Set the configration + void config(const config_t& cfg) { + _cfg = cfg; + } + ///@} +#endif + + ///@name Properties + ///@{ + /*! @brief In periodic measurement? */ + inline bool inPeriodic() const { + return _periodic; + } + //! @brief Periodic measurement data updated? + inline bool updated() const { + return _updated; + } + /*! + @brief Time elapsed since start-up when the measurement data was updated + in update() + @return Updated time (Unit: ms) + */ + inline unsigned long updatedMillis() const { + return _latest; + } + //! @brief Latest celsius temperature + float celsiusTemperature() const { + return _temperatureC; + } + //! @brief Latest fahrenheit temperature + float fahrenheitTemperature() const { + return _temperatureF; + } + ///@} + + /*! + @brief Read status + @param[out] status Status + @return True if successful + */ + bool readStatus(uint8_t& status); + /*! + @brief Read firmware version + @param[out] ver version + @return True if successful + */ + bool readFirmwareVersion(uint8_t& ver); + + ///@name Measurement + ///@{ + bool startPeriodicMeasurement(const uint32_t interval); + bool stopPeriodicMeasurement(); + + /*! + @brief Read celsius temperature + @param[out] ct temperature(C) + @return True if successful + @note Value containing two decimal places + */ + bool readCelsiusTemperature(int32_t& ct); + /*! + @brief Read fahrenheit temperature + @param[out] ft temperature(F) + @return True if successful + @note Value containing two decimal places + */ + bool readFahrenheitTemperature(int32_t& ft); + /*! + @brief Read internal celsius temperature + @param[out] ct temperature(C) + @return True if successful + @note Value containing two decimal places + */ + bool readInternalCelsiusTemperature(int32_t& ct); + /*! + @brief Read internal fahrenheit temperature + @param[out] ft temperature(F) + @return True if successful + @note Value containing two decimal places + */ + bool readInternalFahrenheitTemperature(int32_t& ft); + /*! + @brief Read celsius temperature string + @param[out] str string buffer + @return True if successful + @warning str must be at least 9 bytes + */ + bool readCelsiusTemperatureString(char* str); + /*! + @brief Read fahrenheit temperature string + @param[out] str string buffer + @return True if successful + @warning str must be at least 9 bytes + */ + bool readFahrenheitTemperatureString(char* str); + /*! + @brief Read internal celsius temperature string + @param[out] str string buffer + @return True if successful + @warning str must be at least 9 bytes + */ + bool readInternalCelsiusTemperatureString(char* str); + /*! + @brief Read internal fahrenheit temperature string + @param[out] str string buffer + @return True if successful + @warning str must be at least 9 bytes + */ + bool readInternalFahrenheitTemperatureString(char* str); + ///@} + + ///@name I2C Address + ///@warning Handling warning + ///@{ + /*! + @brief Change device I2C address + @param i2c_address I2C address + @return True if successful + + */ + bool changeI2CAddress(const uint8_t i2c_address); + /*! + @brief Read device I2C address + @param[out] i2c_address I2C address + @return True if successful + */ + bool readI2CAddress(uint8_t& i2c_address); + ///@} + + /*! + @brief Temperature conversion + @param temp Value obtained + @return temperature(float) + */ + static inline float conversion(const int32_t temp) { + return temp / 100.0f; + } + + protected: + bool read_measurement(); + + protected: + bool _periodic{}; // During periodic measurement? + bool _updated{}; + float _temperatureC{}, _temperatureF{}; + unsigned long _latest{}, _interval{}; + + config_t _cfg{}; +}; + +///@cond0 +namespace kmeter { +namespace command { +constexpr uint8_t TEMP_CELSIUS_VAL_REG{0X00}; +constexpr uint8_t TEMP_FAHRENHEIT_VAL_REG{0X04}; +constexpr uint8_t INTERNAL_TEMP_CELSIUS_VAL_REG{0X10}; +constexpr uint8_t INTERNAL_TEMP_FAHRENHEIT_VAL_REG{0X14}; +constexpr uint8_t ERROR_STATUS_REG{0x20}; +constexpr uint8_t TEMP_CELSIUS_STRING_REG{0x30}; +constexpr uint8_t TEMP_FAHRENHEIT_STRING_REG{0x40}; +constexpr uint8_t INTERNAL_TEMP_CELSIUS_STRING_REG{0x50}; +constexpr uint8_t INTERNAL_TEMP_FAHRENHEIT_STRING_REG{0x60}; +constexpr uint8_t FIRMWARE_VERSION_REG{0xFE}; +constexpr uint8_t I2C_ADDRESS_REG{0xFF}; +} // namespace command +} // namespace kmeter +///@endcond + +} // namespace unit +} // namespace m5 +#endif diff --git a/platformio.ini b/platformio.ini index fc87e5a..4b80d0b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -28,7 +28,8 @@ test_speed = 115200 ;test_filter= embedded/test_vmeter ;test_filter= embedded/test_ameter ;test_filter= embedded/test_sgp30 -test_filter= embedded/test_paj7620u2 +;test_filter= embedded/test_paj7620u2 +test_filter= embedded/test_kmeterISO ;test_filter = embedded/test_pahub test_ignore= native/* @@ -76,6 +77,7 @@ board_build.partitions = default.csv [sdl] build_flags = -O3 -xc++ -std=c++14 -lSDL2 + -arch arm64 ; for arm mac -I"/usr/local/include/SDL2" ; for intel mac homebrew SDL2 -L"/usr/local/lib" ; for intel mac homebrew SDL2 -I"${sysenv.HOMEBREW_PREFIX}/include/SDL2" ; for arm mac homebrew SDL2 @@ -379,3 +381,31 @@ lib_deps = ${env.lib_deps} [env:example_gesture_NanoC6] extends=NanoC6, option_release build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitGesture/gesture> + +;;KmeterISO +[env:example_KmeterISO_Core] +extends=Core, option_release +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitMeter/KmeterISO> + +[env:example_KmeterISO_CoreS3] +extends=CoreS3, option_release +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitMeter/KmeterISO> + +[env:example_KmeterISO_StampS3] +extends=StampS3, option_release +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitMeter/KmeterISO> + +[env:example_KmeterISO_AtomS3] +extends=AtomS3, option_release +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitMeter/KmeterISO> + +[env:example_KmeterISO_Dial] +extends=StampS3, option_release +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitMeter/KmeterISO> +lib_deps = ${env.lib_deps} + m5stack/M5Dial + +[env:example_KmeterISO_NanoC6] +extends=NanoC6, option_release +build_src_filter = +<*> -<.git/> -<.svn/> +<../examples/UnitMeter/KmeterISO> + diff --git a/test/embedded/test_kmeterISO/kmeterISO_test.cpp b/test/embedded/test_kmeterISO/kmeterISO_test.cpp new file mode 100644 index 0000000..ffb0cdd --- /dev/null +++ b/test/embedded/test_kmeterISO/kmeterISO_test.cpp @@ -0,0 +1,146 @@ +/* + UnitTest for KMeterISO + + SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD + + SPDX-License-Identifier: MIT +*/ + +// Move to each libarry + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace m5::unit::googletest; +using namespace m5::unit; +using namespace m5::unit::kmeter; + +namespace { +float celsiusToFahrenheit(float celsius) { + return (celsius * 9.f / 5.f) + 32.f; +} + +} // namespace + +const ::testing::Environment* global_fixture = + ::testing::AddGlobalTestEnvironment(new GlobalFixture<100000U>()); + +class TestKmeterISO : public ComponentTestBase { + protected: + virtual UnitKmeterISO* get_instance() override { + return new m5::unit::UnitKmeterISO(); + } + virtual bool is_using_hal() const override { + return GetParam(); + }; +}; + +// INSTANTIATE_TEST_SUITE_P(ParamValues, TestKmeterISO, +// ::testing::Values(false, true)); +// INSTANTIATE_TEST_SUITE_P(ParamValues, TestKmeterISO, +// ::testing::Values(true)); +INSTANTIATE_TEST_SUITE_P(ParamValues, TestKmeterISO, ::testing::Values(false)); + +TEST_P(TestKmeterISO, Version) { + SCOPED_TRACE(ustr); + + uint8_t ver{0x00}; + EXPECT_TRUE(unit->readFirmwareVersion(ver)); + EXPECT_NE(ver, 0x00); +} + +TEST_P(TestKmeterISO, Measurement) { + SCOPED_TRACE(ustr); + + uint8_t status{0xFF}; + EXPECT_TRUE(unit->readStatus(status)); + EXPECT_EQ(status, 0); + + int32_t c{}, f{}; + float cc{}, ff{}; + char sc[9]{}, sf[9]{}; + + { + EXPECT_TRUE(unit->readCelsiusTemperature(c)); + EXPECT_TRUE(unit->readFahrenheitTemperature(f)); + cc = UnitKmeterISO::conversion(c); + ff = UnitKmeterISO::conversion(f); + EXPECT_NEAR(celsiusToFahrenheit(cc), ff, 0.01f); + + EXPECT_TRUE(unit->readCelsiusTemperatureString(sc)); + EXPECT_TRUE(unit->readFahrenheitTemperatureString(sf)); + // M5_LOGW("%f,[%s] %f,[%s]", cc, sc, ff, sf); + } + + { + EXPECT_TRUE(unit->readInternalCelsiusTemperature(c)); + EXPECT_TRUE(unit->readInternalFahrenheitTemperature(f)); + cc = UnitKmeterISO::conversion(c); + ff = UnitKmeterISO::conversion(f); + EXPECT_NEAR(celsiusToFahrenheit(cc), ff, 0.01f); + + EXPECT_TRUE(unit->readInternalCelsiusTemperatureString(sc)); + EXPECT_TRUE(unit->readInternalFahrenheitTemperatureString(sf)); + // M5_LOGW("%f,[%s] %f,[%s]", cc, sc, ff, sf); + } +} + +TEST_P(TestKmeterISO, Periodic) { + EXPECT_TRUE(unit->stopPeriodicMeasurement()); + EXPECT_FALSE(unit->inPeriodic()); + + constexpr uint32_t interval{2500}; + EXPECT_TRUE(unit->startPeriodicMeasurement(interval)); + EXPECT_TRUE(unit->inPeriodic()); + + auto now = m5::utility::millis(); + auto timeout_at = now + interval; + uint8_t count{0}; + // Between first and second mesured + do { + unit->update(); + bool upd = unit->updated(); + count += upd ? 1 : 0; + now = m5::utility::millis(); + if (upd && count == 1) { // First? + timeout_at = now + interval; + } + std::this_thread::yield(); + } while (count < 2 && now <= timeout_at); + EXPECT_EQ(count, 2); + EXPECT_LE(now, timeout_at); +} + +TEST_P(TestKmeterISO, I2CAddress) { + SCOPED_TRACE(ustr); + + uint8_t ver{}, addr{}; + EXPECT_FALSE(unit->changeI2CAddress(0x07)); + EXPECT_FALSE(unit->changeI2CAddress(0x78)); + + EXPECT_TRUE(unit->changeI2CAddress(0x08)); + + EXPECT_TRUE(unit->readI2CAddress(addr)); + EXPECT_EQ(addr, 0x08); + EXPECT_TRUE(unit->readFirmwareVersion(ver)); + EXPECT_NE(ver, 0x00); + + EXPECT_TRUE(unit->changeI2CAddress(0x77)); + EXPECT_TRUE(unit->readFirmwareVersion(ver)); + EXPECT_NE(ver, 0x00); + + EXPECT_TRUE(unit->changeI2CAddress(0x52)); + EXPECT_TRUE(unit->readFirmwareVersion(ver)); + EXPECT_NE(ver, 0x00); + + EXPECT_TRUE(unit->changeI2CAddress(UnitKmeterISO::DEFAULT_ADDRESS)); + EXPECT_TRUE(unit->readFirmwareVersion(ver)); + EXPECT_NE(ver, 0x00); +} diff --git a/test/unit_unified_test.cpp b/test/unit_unified_test.cpp index 874b48a..3a1509b 100644 --- a/test/unit_unified_test.cpp +++ b/test/unit_unified_test.cpp @@ -22,6 +22,7 @@ #include #include #include +#include namespace { @@ -86,6 +87,7 @@ TEST(UnitUnified, EachUnit) { each_unit_test(); each_unit_test(); each_unit_test(); + each_unit_test(); for (auto&& e : vec) { delete e;