diff --git a/fantemp/eeprom b/fantemp/eeprom index 33a4d55..3bcba0a 160000 --- a/fantemp/eeprom +++ b/fantemp/eeprom @@ -1 +1 @@ -Subproject commit 33a4d55f03451f487ace046a6f2328351fda69ee +Subproject commit 3bcba0a1919906d7cb358d3d8185a064bc1aa705 diff --git a/fantemp/statistics.cpp b/fantemp/statistics.cpp index 14ede73..187f25e 100644 --- a/fantemp/statistics.cpp +++ b/fantemp/statistics.cpp @@ -8,59 +8,95 @@ #include "clock.hpp" #include "controller.hpp" -uint64_t Statistics::m_lastTempSave = 0; -double Statistics::m_minTemp; -double Statistics::m_maxTemp; +uint64_t Statistics::m_lastTemperatureWriteback = 0; +uint64_t Statistics::m_lastTemperatureSample = 0; +uint32_t Statistics::m_temperatureHistogram[TEMPERATURE_RANGE]; namespace { // Must be in translation unit and cannot be forward declared -EEVAR(double, e_minTemp); -EEVAR(double, e_maxTemp); +EEARRAY(uint32_t, e_temperatureHistogram, 100); // Could be declared in every function that uses it, but that would be code duplication -Eeprom g_eepMinTemp; -Eeprom g_eepMaxTemp; +EepromArray g_eepTemperatureHistogram; } // namespace void Statistics::init() { - m_minTemp = getMinTemp(); - m_maxTemp = getMaxTemp(); - - if (isnan(m_minTemp)) { - m_minTemp = type::numeric_limits::max(); - } - if (isnan(m_maxTemp)) { - m_maxTemp = type::numeric_limits::lowest(); + for (uint8_t i = 0; i < g_eepTemperatureHistogram.size(); ++i) { + m_temperatureHistogram[i] = g_eepTemperatureHistogram[i]; + using temperature_t = type::decay_t; + if (m_temperatureHistogram[i] == type::numeric_limits::max()) { + m_temperatureHistogram[i] = 0; + } } } void Statistics::callback() { if (Controller::m_dataAvailable) { - if (Controller::m_temperature < m_minTemp) { - m_minTemp = Controller::m_temperature; - } - if (Controller::m_temperature > m_maxTemp) { - m_maxTemp = Controller::m_temperature; + if (clk::millis() >= m_lastTemperatureSample + TEMPERATURE_SAMPLE_DELAY) { + const auto temperature = clampTemperature(static_cast(round(Controller::m_temperature))); + ++m_temperatureHistogram[temperature]; + m_lastTemperatureSample = clk::millis(); } - if (clk::millis() >= m_lastTempSave + TEMP_SAVE_DELAY) { - g_eepMinTemp = m_minTemp; - g_eepMaxTemp = m_maxTemp; - m_lastTempSave = clk::millis(); + if (clk::millis() >= m_lastTemperatureWriteback + TEMPERATURE_WRITEBACK_DELAY) { + writeTemperatureHistogram(); + m_lastTemperatureWriteback = clk::millis(); } } } -double Statistics::getMinTemp() +uint8_t Statistics::getMinTemp() { - return g_eepMinTemp; + for (uint8_t i = 0; i < TEMPERATURE_RANGE; ++i) { + if (m_temperatureHistogram[i] > 0) + return i; + } + + return TEMPERATURE_RANGE; } -double Statistics::getMaxTemp() +uint8_t Statistics::getMaxTemp() { - return g_eepMaxTemp; + for (int8_t i = TEMPERATURE_RANGE - 1; i >= 0; --i) { + if (m_temperatureHistogram[i] > 0) + return i; + } + + return TEMPERATURE_RANGE; +} + +uint32_t Statistics::getMaximumHistogramSamples() +{ + uint32_t max = 1; // Avoid division by zero + for (uint8_t i = 0; i < TEMPERATURE_RANGE; ++i) { + if (m_temperatureHistogram[i] > max) { + max = m_temperatureHistogram[i]; + } + } + return max; +} + +uint8_t Statistics::getHistogram(const int8_t &temperature, const uint32_t &normalizationFactor) +{ + return static_cast(m_temperatureHistogram[clampTemperature(temperature)] / normalizationFactor); +} + +uint8_t Statistics::clampTemperature(const int8_t &temperature) +{ + if (temperature < 0) + return 0; + if (temperature >= TEMPERATURE_RANGE) + return TEMPERATURE_RANGE - 1; + return static_cast(temperature); +} + +void Statistics::writeTemperatureHistogram() +{ + for (uint8_t i = 0; i < g_eepTemperatureHistogram.size(); ++i) { + g_eepTemperatureHistogram[i] = m_temperatureHistogram[i]; + } } diff --git a/fantemp/statistics.hpp b/fantemp/statistics.hpp index a77356d..2a10f6a 100644 --- a/fantemp/statistics.hpp +++ b/fantemp/statistics.hpp @@ -7,14 +7,21 @@ class Statistics { static void init(); static void callback(); - static double getMinTemp(); - static double getMaxTemp(); + static uint8_t getMinTemp(); + static uint8_t getMaxTemp(); + + static uint32_t getMaximumHistogramSamples(); + static uint8_t getHistogram(const int8_t &temperature, const uint32_t &normalizationFactor); private: - static constexpr auto TEMP_SAVE_DELAY = 60000; + static constexpr auto TEMPERATURE_WRITEBACK_DELAY = 1'800'000; + static constexpr auto TEMPERATURE_SAMPLE_DELAY = 1'000; + static constexpr auto TEMPERATURE_RANGE = 100; - static uint64_t m_lastTempSave; + static uint64_t m_lastTemperatureWriteback; + static uint64_t m_lastTemperatureSample; + static uint32_t m_temperatureHistogram[TEMPERATURE_RANGE]; - static double m_minTemp; - static double m_maxTemp; + static uint8_t clampTemperature(const int8_t &temperature); + static void writeTemperatureHistogram(); }; diff --git a/fantemp/terminal.hpp b/fantemp/terminal.hpp index 359290c..6fecc4c 100644 --- a/fantemp/terminal.hpp +++ b/fantemp/terminal.hpp @@ -22,9 +22,10 @@ GF(MONITOR_CMD, "monitor"); GF(BOOTLOADER_CMD, "bootloader"); GF(UPTIME_CMD, "uptime"); GF(STATISTICS_CMD, "statistics"); +GF(HISTOGRAM_CMD, "histogram"); GF(VERSION_CMD, "version"); -GF(VERSION, "1.4"); +GF(VERSION, "1.5"); static inline bool substringEquals(const char *str, const ::detail::FlashString *flashStr, const size_t &size) { @@ -146,6 +147,8 @@ class Terminal { printUptime(); } else if (substringEquals(m_inputBuffer, detail::STATISTICS_CMD, m_inputSize)) { printStatistics(); + } else if (substringEquals(m_inputBuffer, detail::HISTOGRAM_CMD, m_inputSize)) { + printHistogram(); } else if (substringEquals(m_inputBuffer, detail::VERSION_CMD, m_inputSize)) { printVersion(); } else { @@ -175,6 +178,7 @@ class Terminal { m_serial << detail::BOOTLOADER_CMD << F(" .: enters the bootloader after 3 seconds") << detail::ENDL; m_serial << detail::UPTIME_CMD << F(" .....: show system uptime") << detail::ENDL; m_serial << detail::STATISTICS_CMD << F(" .: Print overall statistics like min and max temp") << detail::ENDL; + m_serial << detail::HISTOGRAM_CMD << F(" ..: Prints a histogram of the temperature") << detail::ENDL; m_serial << detail::VERSION_CMD << F(" ....: displays firmware version") << detail::ENDL; } @@ -202,7 +206,7 @@ class Terminal { m_serial.template txNumber(Controller::mapTemperature(i)); m_serial << F("%\t"); for (uint8_t s = 0; s < Controller::mapTemperature(i); ++s) { - m_serial << "#"; + m_serial << '#'; } m_serial << detail::ENDL; } @@ -239,12 +243,23 @@ class Terminal { static void printStatistics() { - char floatBuffer[16]; + m_serial << F("Minimum temperature .: ") << Statistics::getMinTemp() << F(" C") << detail::ENDL; + m_serial << F("Maximum temperature .: ") << Statistics::getMaxTemp() << F(" C") << detail::ENDL; + } - sprintf(floatBuffer, "%.2f", Statistics::getMinTemp()); - m_serial << F("Minimum temperature .: ") << floatBuffer << F(" C") << detail::ENDL; - sprintf(floatBuffer, "%.2f", Statistics::getMaxTemp()); - m_serial << F("Maximum temperature .: ") << floatBuffer << F(" C") << detail::ENDL; + static void printHistogram() + { + auto normalizationFactor = Statistics::getMaximumHistogramSamples(); + normalizationFactor = (normalizationFactor / 100 > 1) ? (normalizationFactor / 100) : 1; + + for (uint8_t t = 10; t <= 60; ++t) { + m_serial.template txNumber(t); + m_serial << F(" C\t"); + for (uint8_t i = 0; i < Statistics::getHistogram(t, normalizationFactor); ++i) { + m_serial << '#'; + } + m_serial << detail::ENDL; + } } static void printVersion()