remove fan-controls

This commit is contained in:
2026-07-08 16:11:57 +02:00
parent 8182b9efd2
commit 2fe53d5a8a
8 changed files with 114 additions and 118 deletions

View File

@@ -82,20 +82,14 @@ Verify access: `test -w /dev/ttyUSB0 && echo ok`
1. Flash firmware and open the serial monitor at 115200 baud. 1. Flash firmware and open the serial monitor at 115200 baud.
2. Confirm `TCA9548A detected` and four valid sensor channels (`ch2``ch5`). 2. Confirm `TCA9548A detected` and four valid sensor channels (`ch2``ch5`).
3. Find **stir fan** speed (chamber ~35°C, idle): 3. Find **stir fan** speed (optional — default is **PWM 178**, ~70%):
``` ```
target 0 target 0
fanchars fanchars
``` ```
Heats to max **60°C** at **30%, 100%, 60%, 80%** fan (cool to **40°C** avg between each). If the best is not at 30% or 100%, runs once at the midpoint of the two lowest spreads (~13 h). Or: `python3 scripts/fan_characterize.py`. When done: Sweeps **30%, 100%, 60%, 80%** fan — heats to max **60°C** corner at **100%** heater. Cools to **40°C** avg between runs. Skips a fan speed if 60°C isn't reached in time. Optional midpoint refine if best isn't at 30% or 100%. Or: `python3 scripts/fan_characterize.py`. When done, `fanchars save` writes the winner to EEPROM (or skip and keep the 178 default).
```
fanchars save
```
Stored PWM is the minimum-stir floor while regulating (≥40°C avg).
4. Tune **heat PI** (stored in EEPROM on autotune complete): 4. Tune **heat PI** (stored in EEPROM on autotune complete):
@@ -107,9 +101,9 @@ Verify access: `test -w /dev/ttyUSB0 && echo ok`
Emergency cutoff is fixed at **70°C** — you can autotune at 5055°C with ABS in the chamber while hot corners stay below that. Emergency cutoff is fixed at **70°C** — you can autotune at 5055°C with ABS in the chamber while hot corners stay below that.
5. Optional: adjust **mix PI** at target if spread still drifts (`mixpi`, `pid save`). Stir fan from step 3 is usually enough to start drying. 5. Dry at your target — fan runs at the stir PWM (default **178**) unless you override with `fan <pwm>`; `fan auto` returns to the default.
Send `help` over serial for all commands (`target`, `fanchars`, `mixpi`, `fan test`, `log on/off`, `status`, `pid`, etc.). Send `help` over serial for all commands (`target`, `fanchars`, `fan`, `log on/off`, `status`, `pid`, etc.).
## Raspberry Pi control ## Raspberry Pi control

View File

@@ -29,16 +29,12 @@ static const float TARGET_MAX_C = 80.0f;
static const float EMERGENCY_MAX_TEMP_C = 70.0f; static const float EMERGENCY_MAX_TEMP_C = 70.0f;
static const float CORNER_STOP_MARGIN_C = 3.0f; static const float CORNER_STOP_MARGIN_C = 3.0f;
// Dual PI — heat on avg, mix on spread (no D term) // Heat PI on average temp (no D term)
static const float HEAT_PI_KP = 4.0f; static const float HEAT_PI_KP = 4.0f;
static const float HEAT_PI_KI = 0.05f; static const float HEAT_PI_KI = 0.05f;
static const float MIX_PI_KP = 40.0f;
static const float MIX_PI_KI = 2.0f; // Fixed circulation fan (~70%, fanchars winner); override with "fan <pwm>" when regulating
static const float SPREAD_TARGET_C = 0.5f; static const uint8_t FAN_STIR_PWM = 178;
static const float HEAT_UP_BAND_C = 8.0f;
// Mix PI only at/above target; below target use minimum stir only (no mix PI).
// Cap mix fan — high airflow often increases spread / heat loss rather than fixing it.
static const uint8_t FAN_MIX_MAX_PWM = 140;
// Legacy aliases for autotuner relay math only // Legacy aliases for autotuner relay math only
static const float PID_KP = HEAT_PI_KP; static const float PID_KP = HEAT_PI_KP;
@@ -81,7 +77,7 @@ static const uint32_t AUTOTUNE_RELAY_PERIOD_MAX_MS = 2400000UL;
static const float FANCHARS_MAX_CORNER_C = 60.0f; static const float FANCHARS_MAX_CORNER_C = 60.0f;
static const float FANCHARS_COOL_AVG_C = 40.0f; static const float FANCHARS_COOL_AVG_C = 40.0f;
static const float FANCHARS_PRECOOL_MARGIN_C = 2.0f; static const float FANCHARS_PRECOOL_MARGIN_C = 2.0f;
static const float FANCHARS_HEATER_PCT = 85.0f; static const float FANCHARS_HEATER_PCT = 100.0f;
// Coarse sweep order: 30%, 100%, 60%, 80% fan // Coarse sweep order: 30%, 100%, 60%, 80% fan
static const uint8_t FANCHARS_COARSE_PWM[] = {77, 255, 153, 204}; static const uint8_t FANCHARS_COARSE_PWM[] = {77, 255, 153, 204};
static const uint8_t FANCHARS_COARSE_COUNT = static const uint8_t FANCHARS_COARSE_COUNT =

View File

@@ -271,7 +271,7 @@ private:
Serial.print(resultKp_, 3); Serial.print(resultKp_, 3);
Serial.print(F(" Ki=")); Serial.print(F(" Ki="));
Serial.println(resultKi_, 4); Serial.println(resultKi_, 4);
Serial.println(F(" tune mix PI separately: mixpi <Kp> <Ki>")); Serial.println(F(" tune heat PI: pid save after autotune"));
} }
void fail(const __FlashStringHelper *reason) { void fail(const __FlashStringHelper *reason) {

View File

@@ -12,7 +12,7 @@ static const int SETTINGS_EEPROM_ADDR = 16;
struct SettingsData { struct SettingsData {
uint16_t magic = 0; uint16_t magic = 0;
float targetC = TARGET_TEMP_C; float targetC = TARGET_TEMP_C;
uint8_t stirFanPwm = 0; // 0 = use FAN_IDLE_PWM (~30%) uint8_t stirFanPwm = 0; // 0 = use FAN_STIR_PWM from config
}; };
inline uint8_t settingsChecksum(const SettingsData &data) { inline uint8_t settingsChecksum(const SettingsData &data) {

View File

@@ -15,7 +15,6 @@ public:
ThermalController() ThermalController()
: heatPi_(HEAT_PI_KP, HEAT_PI_KI, 0.0f, 0.0f, 100.0f), : heatPi_(HEAT_PI_KP, HEAT_PI_KI, 0.0f, 0.0f, 100.0f),
mixPi_(MIX_PI_KP, MIX_PI_KI, 0.0f, 0.0f, 255.0f),
autotuner_(), autotuner_(),
fanchars_(), fanchars_(),
targetTempC_(TARGET_TEMP_C), targetTempC_(TARGET_TEMP_C),
@@ -24,10 +23,12 @@ public:
cornerSpreadC_(0.0f), cornerSpreadC_(0.0f),
lastMaxTempC_(0.0f), lastMaxTempC_(0.0f),
regulatingFanPwm_(0), regulatingFanPwm_(0),
stirFanPwm_(FAN_IDLE_PWM), stirFanPwm_(FAN_STIR_PWM),
fanManualPwm_(0),
fanPwm_(0), fanPwm_(0),
tuningLoaded_(false), tuningLoaded_(false),
fanIdleOverride_(false), fanIdleOverride_(false),
fanManualActive_(false),
sensorWarmValid_(false), sensorWarmValid_(false),
cutoffActive_(false), cutoffActive_(false),
failSafeActive_(true), failSafeActive_(true),
@@ -38,9 +39,7 @@ public:
heaterBlock_(HeaterBlock::None), heaterBlock_(HeaterBlock::None),
fanTestActive_(false), fanTestActive_(false),
fanTestPwm_(0), fanTestPwm_(0),
fanTestEndMs_(0) { fanTestEndMs_(0) {}
mixPi_.setSetpoint(0.0f);
}
void begin() { void begin() {
pinMode(FAN_PIN, OUTPUT); pinMode(FAN_PIN, OUTPUT);
@@ -50,7 +49,6 @@ public:
heatPi_.setSetpoint(targetTempC_); heatPi_.setSetpoint(targetTempC_);
heatPi_.reset(); heatPi_.reset();
mixPi_.reset();
heaterCycleStartMs_ = millis(); heaterCycleStartMs_ = millis();
lastHeaterUpdateMs_ = 0; lastHeaterUpdateMs_ = 0;
failSafeActive_ = true; failSafeActive_ = true;
@@ -83,44 +81,22 @@ public:
void applyTuning(const TuningData &data) { void applyTuning(const TuningData &data) {
heatPi_.setTunings(data.heatKp, data.heatKi, 0.0f); heatPi_.setTunings(data.heatKp, data.heatKi, 0.0f);
mixPi_.setTunings(data.mixKp, data.mixKi, 0.0f);
tuningLoaded_ = true; tuningLoaded_ = true;
} }
void clearTuning() { void clearTuning() {
tuningLoaded_ = false; tuningLoaded_ = false;
heatPi_.setTunings(HEAT_PI_KP, HEAT_PI_KI, 0.0f); heatPi_.setTunings(HEAT_PI_KP, HEAT_PI_KI, 0.0f);
mixPi_.setTunings(MIX_PI_KP, MIX_PI_KI, 0.0f);
tuningClear(); tuningClear();
heatPi_.reset(); heatPi_.reset();
mixPi_.reset();
Serial.println(F("PI reset to defaults")); Serial.println(F("PI reset to defaults"));
} }
void setMixTunings(float kp, float ki) {
mixPi_.setTunings(kp, ki, 0.0f);
mixPi_.reset();
Serial.print(F("Mix PI Kp="));
Serial.print(kp, 3);
Serial.print(F(" Ki="));
Serial.println(ki, 4);
}
void resetMixTunings() {
mixPi_.setTunings(MIX_PI_KP, MIX_PI_KI, 0.0f);
mixPi_.reset();
Serial.println(F("Mix PI reset to defaults"));
}
void printTuning() const { void printTuning() const {
Serial.print(F("Heat PI Kp=")); Serial.print(F("Heat PI Kp="));
Serial.print(heatPi_.kp(), 3); Serial.print(heatPi_.kp(), 3);
Serial.print(F(" Ki=")); Serial.print(F(" Ki="));
Serial.print(heatPi_.ki(), 4); Serial.print(heatPi_.ki(), 4);
Serial.print(F(" Mix PI Kp="));
Serial.print(mixPi_.kp(), 3);
Serial.print(F(" Ki="));
Serial.print(mixPi_.ki(), 4);
Serial.print(F(" tuned=")); Serial.print(F(" tuned="));
Serial.println(tuningLoaded_ ? F("yes") : F("no")); Serial.println(tuningLoaded_ ? F("yes") : F("no"));
} }
@@ -130,8 +106,6 @@ public:
data.magic = TUNING_MAGIC; data.magic = TUNING_MAGIC;
data.heatKp = heatPi_.kp(); data.heatKp = heatPi_.kp();
data.heatKi = heatPi_.ki(); data.heatKi = heatPi_.ki();
data.mixKp = mixPi_.kp();
data.mixKi = mixPi_.ki();
tuningSave(data); tuningSave(data);
tuningLoaded_ = true; tuningLoaded_ = true;
Serial.println(F("Saved PI to EEPROM")); Serial.println(F("Saved PI to EEPROM"));
@@ -139,8 +113,6 @@ public:
float heatKp() const { return heatPi_.kp(); } float heatKp() const { return heatPi_.kp(); }
float heatKi() const { return heatPi_.ki(); } float heatKi() const { return heatPi_.ki(); }
float mixKp() const { return mixPi_.kp(); }
float mixKi() const { return mixPi_.ki(); }
bool isTuningLoaded() const { return tuningLoaded_; } bool isTuningLoaded() const { return tuningLoaded_; }
@@ -150,7 +122,6 @@ public:
} }
cutoffActive_ = false; cutoffActive_ = false;
heatPi_.reset(); heatPi_.reset();
mixPi_.reset();
return autotuner_.start(setpointC); return autotuner_.start(setpointC);
} }
@@ -175,7 +146,6 @@ public:
stopFanTest(); stopFanTest();
cutoffActive_ = false; cutoffActive_ = false;
heatPi_.reset(); heatPi_.reset();
mixPi_.reset();
setTarget(0.0f, false); setTarget(0.0f, false);
return fanchars_.start(maxCornerC, avgTempC); return fanchars_.start(maxCornerC, avgTempC);
} }
@@ -204,6 +174,32 @@ public:
uint8_t stirFanPwm() const { return stirFanPwm_; } uint8_t stirFanPwm() const { return stirFanPwm_; }
bool isFanManualOverride() const { return fanManualActive_ && !isIdle(); }
bool setRegulatingFanManual(uint8_t pwm) {
if (isIdle()) {
return false;
}
fanManualPwm_ = pwm;
fanManualActive_ = true;
return true;
}
void clearRegulatingFanManual() {
fanManualActive_ = false;
}
bool setStirFanPwm(uint8_t pwm, bool persist = true) {
if (pwm == 0) {
return false;
}
stirFanPwm_ = pwm;
if (persist) {
settingsSaveStirFan(pwm);
}
return true;
}
void logFanCharacterizeIfDue(const float *sensorTemps, const bool *sensorValid, uint8_t sensorCount, void logFanCharacterizeIfDue(const float *sensorTemps, const bool *sensorValid, uint8_t sensorCount,
float avgTempC, float minTempC, float maxTempC, float spreadC, float avgTempC, float minTempC, float maxTempC, float spreadC,
uint32_t nowMs) { uint32_t nowMs) {
@@ -239,12 +235,10 @@ public:
data.magic = TUNING_MAGIC; data.magic = TUNING_MAGIC;
data.heatKp = autotuner_.resultKp(); data.heatKp = autotuner_.resultKp();
data.heatKi = autotuner_.resultKi(); data.heatKi = autotuner_.resultKi();
data.mixKp = mixPi_.kp();
data.mixKi = mixPi_.ki();
tuningSave(data); tuningSave(data);
tuningLoaded_ = true; tuningLoaded_ = true;
autotuner_.reset(); autotuner_.reset();
Serial.println(F("Saved heat PI to EEPROM (mix PI unchanged)")); Serial.println(F("Saved heat PI to EEPROM"));
return true; return true;
} }
@@ -252,7 +246,7 @@ public:
targetTempC_ = targetC; targetTempC_ = targetC;
heatPi_.setSetpoint(targetC); heatPi_.setSetpoint(targetC);
heatPi_.reset(); heatPi_.reset();
mixPi_.reset(); fanManualActive_ = false;
cutoffActive_ = false; cutoffActive_ = false;
if (targetC > 0.0f) { if (targetC > 0.0f) {
fanIdleOverride_ = false; fanIdleOverride_ = false;
@@ -390,7 +384,6 @@ public:
forceHeaterOff(); forceHeaterOff();
cutoffActive_ = false; cutoffActive_ = false;
heatPi_.reset(); heatPi_.reset();
mixPi_.reset();
lastHeaterUpdateMs_ = nowMs; lastHeaterUpdateMs_ = nowMs;
applyFan(nowMs); applyFan(nowMs);
return; return;
@@ -408,7 +401,6 @@ public:
forceHeaterOff(); forceHeaterOff();
applyFan(millis()); applyFan(millis());
heatPi_.reset(); heatPi_.reset();
mixPi_.reset();
autotuner_.abort(); autotuner_.abort();
fanchars_.abort(); fanchars_.abort();
} }
@@ -485,14 +477,12 @@ private:
regulatingFanPwm_ = FAN_MAX_PWM; regulatingFanPwm_ = FAN_MAX_PWM;
heaterBlock_ = HeaterBlock::Cutoff; heaterBlock_ = HeaterBlock::Cutoff;
heatPi_.reset(); heatPi_.reset();
mixPi_.reset();
return; return;
} }
if (cutoffActive_ && maxTempC < EMERGENCY_MAX_TEMP_C - 5.0f) { if (cutoffActive_ && maxTempC < EMERGENCY_MAX_TEMP_C - 5.0f) {
cutoffActive_ = false; cutoffActive_ = false;
heatPi_.reset(); heatPi_.reset();
mixPi_.reset();
} }
if (cutoffActive_) { if (cutoffActive_) {
@@ -523,17 +513,7 @@ private:
} }
heaterDutyPercent_ = applyHeaterRamp(duty, avgTempC, nowMs); heaterDutyPercent_ = applyHeaterRamp(duty, avgTempC, nowMs);
uint8_t fanPwm = stirFanPwm_; const uint8_t fanPwm = fanManualActive_ ? fanManualPwm_ : stirFanPwm_;
if (avgTempC < targetTempC_) {
mixPi_.reset();
} else {
const float mixInput = SPREAD_TARGET_C - cornerSpreadC_;
float fanOut = mixPi_.compute(mixInput, nowMs);
fanPwm = static_cast<uint8_t>(fanOut + 0.5f);
if (fanPwm > FAN_MIX_MAX_PWM) {
fanPwm = FAN_MIX_MAX_PWM;
}
}
regulatingFanPwm_ = fanWithMinStir(avgTempC, fanPwm); regulatingFanPwm_ = fanWithMinStir(avgTempC, fanPwm);
} }
@@ -541,9 +521,6 @@ private:
if (avgTempC < IDLE_AUTO_FAN_OFF_TEMP_C) { if (avgTempC < IDLE_AUTO_FAN_OFF_TEMP_C) {
return 0; return 0;
} }
if (pwm < stirFanPwm_) {
return stirFanPwm_;
}
return pwm; return pwm;
} }
@@ -665,7 +642,6 @@ private:
} }
PidController heatPi_; PidController heatPi_;
PidController mixPi_;
PidAutotuner autotuner_; PidAutotuner autotuner_;
FanCharacterize fanchars_; FanCharacterize fanchars_;
float targetTempC_; float targetTempC_;
@@ -675,9 +651,11 @@ private:
float lastMaxTempC_; float lastMaxTempC_;
uint8_t regulatingFanPwm_; uint8_t regulatingFanPwm_;
uint8_t stirFanPwm_; uint8_t stirFanPwm_;
uint8_t fanManualPwm_;
uint8_t fanPwm_; uint8_t fanPwm_;
bool tuningLoaded_; bool tuningLoaded_;
bool fanIdleOverride_; bool fanIdleOverride_;
bool fanManualActive_;
bool sensorWarmValid_; bool sensorWarmValid_;
bool cutoffActive_; bool cutoffActive_;
bool failSafeActive_; bool failSafeActive_;

View File

@@ -5,15 +5,13 @@
#include "config.h" #include "config.h"
static const uint16_t TUNING_MAGIC = 0xDA7B; static const uint16_t TUNING_MAGIC = 0xDA7C;
static const int TUNING_EEPROM_ADDR = 0; static const int TUNING_EEPROM_ADDR = 0;
struct TuningData { struct TuningData {
uint16_t magic = 0; uint16_t magic = 0;
float heatKp = HEAT_PI_KP; float heatKp = HEAT_PI_KP;
float heatKi = HEAT_PI_KI; float heatKi = HEAT_PI_KI;
float mixKp = MIX_PI_KP;
float mixKi = MIX_PI_KI;
}; };
inline uint8_t tuningChecksum(const TuningData &data) { inline uint8_t tuningChecksum(const TuningData &data) {

View File

@@ -200,7 +200,9 @@ def apply_status(state: DryerState, data: dict) -> None:
fan_raw = data["fan"] fan_raw = data["fan"]
state.fan = format_fan_display(fan_raw) state.fan = format_fan_display(fan_raw)
state.fan_note = "" state.fan_note = ""
if "(off)" in fan_raw or "(cooldown)" in fan_raw or "(cmd-off)" in fan_raw: if "(off)" in fan_raw or "(cooldown)" in fan_raw or "(off<40C)" in fan_raw:
state.fan_note = fan_raw[fan_raw.find("(") :] if "(" in fan_raw else ""
elif "(manual)" in fan_raw or "(stir)" in fan_raw:
state.fan_note = fan_raw[fan_raw.find("(") :] if "(" in fan_raw else "" state.fan_note = fan_raw[fan_raw.find("(") :] if "(" in fan_raw else ""
elif "(fanchars-" in fan_raw: elif "(fanchars-" in fan_raw:
state.fan_note = fan_raw[fan_raw.find("(fanchars-") :] state.fan_note = fan_raw[fan_raw.find("(fanchars-") :]

View File

@@ -150,8 +150,12 @@ void printStatus(float avgTemp, float minTemp, float maxTemp) {
Serial.print(F("(off)")); Serial.print(F("(off)"));
} else if (thermal.isIdleCooling()) { } else if (thermal.isIdleCooling()) {
Serial.print(F("(cooldown)")); Serial.print(F("(cooldown)"));
} else if (thermal.fanPwm() == 0) { } else if (thermal.isFanManualOverride()) {
Serial.print(F("(cmd-off)")); Serial.print(F("(manual)"));
} else if (!thermal.isIdle() && thermal.fanPwm() == 0) {
Serial.print(F("(off<40C)"));
} else if (!thermal.isIdle()) {
Serial.print(F("(stir)"));
} }
Serial.print(F(" cutoff=")); Serial.print(F(" cutoff="));
Serial.print(thermal.isCutoffActive() ? F("YES") : F("no")); Serial.print(thermal.isCutoffActive() ? F("YES") : F("no"));
@@ -224,17 +228,18 @@ void printHelp() {
Serial.println(F(" target <C> set target (0 = idle)")); Serial.println(F(" target <C> set target (0 = idle)"));
Serial.println(F(" fan off cancel idle fan override (auto-off below 40C)")); Serial.println(F(" fan off cancel idle fan override (auto-off below 40C)"));
Serial.println(F(" fan on idle fan 30% (optional, auto-off below 40C)")); Serial.println(F(" fan on idle fan 30% (optional, auto-off below 40C)"));
Serial.println(F(" fan auto regulating: back to default stir fan"));
Serial.println(F(" fan <pwm> regulating: manual fan override (0-255)"));
Serial.println(F(" fan stir show default stir fan PWM"));
Serial.println(F(" fan stir N set default stir fan + EEPROM"));
Serial.println(F(" fan test N set fan PWM 0-255 for 15s (verify wiring)")); Serial.println(F(" fan test N set fan PWM 0-255 for 15s (verify wiring)"));
Serial.println(F(" autotune [C] learn heat PI (default: 40C when idle)")); Serial.println(F(" autotune [C] learn heat PI (default: 40C when idle)"));
Serial.println(F(" autotune stop")); Serial.println(F(" autotune stop"));
Serial.println(F(" fanchars learn stir fan (30/100/60/80%% + refine)")); Serial.println(F(" fanchars learn stir fan (30/100/60/80%% + refine)"));
Serial.println(F(" fanchars stop | fanchars save")); Serial.println(F(" fanchars stop | fanchars save"));
Serial.println(F(" pid show heat + mix PI gains")); Serial.println(F(" pid show heat PI gains"));
Serial.println(F(" pid default reset all PI to factory")); Serial.println(F(" pid default reset heat PI to factory"));
Serial.println(F(" pid save write current PI to EEPROM")); Serial.println(F(" pid save write current PI to EEPROM"));
Serial.println(F(" mixpi show mix PI gains"));
Serial.println(F(" mixpi <Kp> <Ki> set mix PI (spread -> fan)"));
Serial.println(F(" mixpi default reset mix PI to factory"));
Serial.println(F(" status print current readings")); Serial.println(F(" status print current readings"));
Serial.println(F(" log on|off CSV data stream")); Serial.println(F(" log on|off CSV data stream"));
Serial.println(F(" help show this message")); Serial.println(F(" help show this message"));
@@ -323,6 +328,58 @@ void processSerialLine(const char *line) {
return; return;
} }
if (strcmp(line, "fan auto") == 0) {
if (thermal.isIdle()) {
Serial.println(F("ERR fan auto only when regulating (set target > 0)"));
return;
}
thermal.clearRegulatingFanManual();
Serial.print(F("OK fan stir "));
Serial.println(thermal.stirFanPwm());
return;
}
if (strncmp(line, "fan stir", 8) == 0) {
if (line[8] == '\0') {
Serial.print(F("stir fan PWM="));
Serial.println(thermal.stirFanPwm());
return;
}
if (line[8] == ' ') {
const int pwm = atoi(line + 9);
if (pwm < 1 || pwm > 255) {
Serial.println(F("ERR fan stir PWM must be 1-255"));
return;
}
thermal.setStirFanPwm(static_cast<uint8_t>(pwm));
if (!thermal.isIdle()) {
thermal.clearRegulatingFanManual();
}
Serial.print(F("OK stir fan "));
Serial.println(pwm);
return;
}
}
if (strncmp(line, "fan ", 4) == 0) {
const char *arg = line + 4;
if (*arg >= '0' && *arg <= '9') {
const int pwm = atoi(arg);
if (pwm < 0 || pwm > 255) {
Serial.println(F("ERR fan PWM must be 0-255"));
return;
}
if (thermal.isIdle()) {
Serial.println(F("ERR fan <pwm> only when regulating (or use fan test)"));
return;
}
thermal.setRegulatingFanManual(static_cast<uint8_t>(pwm));
Serial.print(F("OK fan manual "));
Serial.println(pwm);
return;
}
}
if (strcmp(line, "status") == 0) { if (strcmp(line, "status") == 0) {
const float avgTemp = averageValidTemperature(); const float avgTemp = averageValidTemperature();
const float minTemp = minValidTemperature(); const float minTemp = minValidTemperature();
@@ -415,35 +472,6 @@ void processSerialLine(const char *line) {
return; return;
} }
if (strcmp(line, "mixpi") == 0 || strcmp(line, "mixpi show") == 0) {
Serial.print(F("Mix PI Kp="));
Serial.print(thermal.mixKp(), 3);
Serial.print(F(" Ki="));
Serial.println(thermal.mixKi(), 4);
return;
}
if (strcmp(line, "mixpi default") == 0) {
thermal.resetMixTunings();
return;
}
if (strncmp(line, "mixpi ", 6) == 0) {
const float kp = atof(line + 6);
const char *space = strchr(line + 6, ' ');
if (space == nullptr) {
Serial.println(F("ERR mixpi requires: mixpi <Kp> <Ki>"));
return;
}
const float ki = atof(space + 1);
if (kp <= 0.0f || ki < 0.0f) {
Serial.println(F("ERR mixpi Kp must be > 0, Ki >= 0"));
return;
}
thermal.setMixTunings(kp, ki);
return;
}
if (strcmp(line, "help") == 0) { if (strcmp(line, "help") == 0) {
printHelp(); printHelp();
return; return;