#pragma once #include #include "config.h" class FanCharacterize { public: enum class Phase : uint8_t { Idle, Precool, Heat, Hold, Cooldown, Done, Failed }; FanCharacterize(); Phase phase() const { return phase_; } bool isActive() const; uint32_t elapsedMs(uint32_t nowMs) const; uint8_t profileIndex() const { return profileIndex_; } uint8_t profileCount() const { return FANCHARS_COARSE_COUNT + 1; } uint8_t currentFanPwm() const; float heaterPct() const { return heaterPct_; } uint8_t winnerFanPwm() const { return winnerFanPwm_; } bool isRefineRun() const { return refineRun_ && profileIndex_ >= FANCHARS_COARSE_COUNT; } const char *phaseName() const; bool start(float maxCornerC, float avgTempC); void abort(); void reset(); bool update(float avgTempC, float maxTempC, float spreadC, uint32_t nowMs, float &heaterDutyOut, uint8_t &fanPwmOut); void logIfDue(const float *sensorTemps, const bool *sensorValid, uint8_t sensorCount, float avgTempC, float minTempC, float maxTempC, float spreadC, uint32_t nowMs); private: struct ProfileResult { uint8_t fanPwm; float meanSpreadC; }; void resetProfileStats(); void beginProfileHeat(uint32_t nowMs); void enterHold(uint32_t nowMs); void finishProfile(uint32_t nowMs); void skipProfile(uint32_t nowMs, float maxTempC); void planRefine(uint32_t nowMs); void finishAll(uint32_t nowMs); void fail(const __FlashStringHelper *reason); Phase phase_; float maxCornerC_; float coolAvgC_; float heaterPct_; uint8_t profileIndex_; uint8_t refineFanPwm_; bool refineRun_; uint32_t sessionStartMs_; uint32_t phaseStartMs_; uint32_t lastLogMs_; float spreadSum_; uint16_t spreadSamples_; uint8_t resultCount_; uint8_t winnerFanPwm_; ProfileResult results_[FANCHARS_MAX_RESULTS]; };