Fix fan curve saving - use flat config structure for WLED compatibility

- Changed from nested JSON array to flat curve-t1/s1, curve-t2/s2, etc.
- Added appendConfigData() for config page labels
- Reduced max curve points to 5 for simpler config
- Added bounds checking for speed values
This commit is contained in:
dawie 2026-01-30 18:41:07 +02:00
parent 77a3853a4e
commit a3cd6bac16

View File

@ -10,7 +10,7 @@
* Features: * Features:
* - Web API for temperature updates from external sources (GPU, etc.) * - Web API for temperature updates from external sources (GPU, etc.)
* - Fixed speed mode with configurable percentage * - Fixed speed mode with configurable percentage
* - Temperature curve mode with up to 10 configurable points * - Temperature curve mode with up to 5 configurable points
* - Safety fallback to 100% if temperature data times out * - Safety fallback to 100% if temperature data times out
* - Integration with WLED's web interface * - Integration with WLED's web interface
* *
@ -54,19 +54,24 @@ class GPUFanControllerUsermod : public Usermod {
ControlMode controlMode = MODE_CURVE; ControlMode controlMode = MODE_CURVE;
uint8_t fixedSpeedPct = 50; // 0-100% uint8_t fixedSpeedPct = 50; // 0-100%
// Temperature curve (up to 10 points) // Temperature curve - using flat structure for WLED config compatibility
static const uint8_t MAX_CURVE_POINTS = 10; // 5 curve points with separate temp and speed values
static const uint8_t MAX_CURVE_POINTS = 5;
uint8_t curveCount = 4; uint8_t curveCount = 4;
struct CurvePoint {
float temp; // Curve point temperatures (°C)
uint8_t speed; int16_t curveTemp1 = 30;
}; int16_t curveTemp2 = 50;
CurvePoint curve[MAX_CURVE_POINTS] = { int16_t curveTemp3 = 70;
{30.0f, 30}, int16_t curveTemp4 = 85;
{50.0f, 50}, int16_t curveTemp5 = 95;
{70.0f, 75},
{85.0f, 100} // Curve point speeds (%)
}; uint8_t curveSpeed1 = 30;
uint8_t curveSpeed2 = 50;
uint8_t curveSpeed3 = 75;
uint8_t curveSpeed4 = 100;
uint8_t curveSpeed5 = 100;
// Runtime state // Runtime state
float currentTemp = 25.0f; float currentTemp = 25.0f;
@ -132,28 +137,57 @@ class GPUFanControllerUsermod : public Usermod {
#endif #endif
} }
// Get curve temperature at index
int16_t getCurveTemp(uint8_t idx) {
switch(idx) {
case 0: return curveTemp1;
case 1: return curveTemp2;
case 2: return curveTemp3;
case 3: return curveTemp4;
case 4: return curveTemp5;
default: return 50;
}
}
// Get curve speed at index
uint8_t getCurveSpeed(uint8_t idx) {
switch(idx) {
case 0: return curveSpeed1;
case 1: return curveSpeed2;
case 2: return curveSpeed3;
case 3: return curveSpeed4;
case 4: return curveSpeed5;
default: return 50;
}
}
// Calculate fan speed from temperature curve // Calculate fan speed from temperature curve
uint8_t calculateCurveSpeed(float temp) { uint8_t calculateCurveSpeed(float temp) {
if (curveCount < 2) return 50; // Fallback if (curveCount < 2) return 50; // Fallback
// Below first point // Below first point
if (temp <= curve[0].temp) { if (temp <= getCurveTemp(0)) {
return curve[0].speed; return getCurveSpeed(0);
} }
// Above last point // Above last point
if (temp >= curve[curveCount - 1].temp) { if (temp >= getCurveTemp(curveCount - 1)) {
return curve[curveCount - 1].speed; return getCurveSpeed(curveCount - 1);
} }
// Linear interpolation between points // Linear interpolation between points
for (uint8_t i = 0; i < curveCount - 1; i++) { for (uint8_t i = 0; i < curveCount - 1; i++) {
if (temp >= curve[i].temp && temp <= curve[i + 1].temp) { int16_t t1 = getCurveTemp(i);
float tempRange = curve[i + 1].temp - curve[i].temp; int16_t t2 = getCurveTemp(i + 1);
float speedRange = curve[i + 1].speed - curve[i].speed; uint8_t s1 = getCurveSpeed(i);
float tempDiff = temp - curve[i].temp; uint8_t s2 = getCurveSpeed(i + 1);
int speed = curve[i].speed + (int)((tempDiff / tempRange) * speedRange); if (temp >= t1 && temp <= t2) {
float tempRange = t2 - t1;
float speedRange = s2 - s1;
float tempDiff = temp - t1;
int speed = s1 + (int)((tempDiff / tempRange) * speedRange);
return constrain(speed, 0, 100); return constrain(speed, 0, 100);
} }
} }
@ -310,16 +344,39 @@ class GPUFanControllerUsermod : public Usermod {
top["pwm-pin"] = pwmPin; top["pwm-pin"] = pwmPin;
top["mode"] = (int)controlMode; top["mode"] = (int)controlMode;
top["fixed-speed"] = fixedSpeedPct; top["fixed-speed"] = fixedSpeedPct;
top["timeout-ms"] = tempTimeoutMs; top["timeout-ms"] = (int)tempTimeoutMs;
top["curve-points"] = curveCount;
// Save curve points // Save curve points as flat values
top["curve-count"] = curveCount; top["curve-t1"] = curveTemp1;
JsonArray curveArr = top.createNestedArray("curve"); top["curve-t2"] = curveTemp2;
for (uint8_t i = 0; i < curveCount; i++) { top["curve-t3"] = curveTemp3;
JsonObject point = curveArr.createNestedObject(); top["curve-t4"] = curveTemp4;
point["temp"] = curve[i].temp; top["curve-t5"] = curveTemp5;
point["speed"] = curve[i].speed; top["curve-s1"] = curveSpeed1;
} top["curve-s2"] = curveSpeed2;
top["curve-s3"] = curveSpeed3;
top["curve-s4"] = curveSpeed4;
top["curve-s5"] = curveSpeed5;
}
// Append config data (labels for config page)
void appendConfigData() override {
oappend(SET_F("addInfo('GPU-Fan:pwm-pin',1,'GPIO');"));
oappend(SET_F("addInfo('GPU-Fan:mode',1,'0=Fixed, 1=Curve');"));
oappend(SET_F("addInfo('GPU-Fan:fixed-speed',1,'%');"));
oappend(SET_F("addInfo('GPU-Fan:timeout-ms',1,'ms (safety timeout)');"));
oappend(SET_F("addInfo('GPU-Fan:curve-points',1,'2-5 points');"));
oappend(SET_F("addInfo('GPU-Fan:curve-t1',1,'°C (Point 1 temp)');"));
oappend(SET_F("addInfo('GPU-Fan:curve-s1',1,'% (Point 1 speed)');"));
oappend(SET_F("addInfo('GPU-Fan:curve-t2',1,'°C (Point 2 temp)');"));
oappend(SET_F("addInfo('GPU-Fan:curve-s2',1,'% (Point 2 speed)');"));
oappend(SET_F("addInfo('GPU-Fan:curve-t3',1,'°C (Point 3 temp)');"));
oappend(SET_F("addInfo('GPU-Fan:curve-s3',1,'% (Point 3 speed)');"));
oappend(SET_F("addInfo('GPU-Fan:curve-t4',1,'°C (Point 4 temp)');"));
oappend(SET_F("addInfo('GPU-Fan:curve-s4',1,'% (Point 4 speed)');"));
oappend(SET_F("addInfo('GPU-Fan:curve-t5',1,'°C (Point 5 temp)');"));
oappend(SET_F("addInfo('GPU-Fan:curve-s5',1,'% (Point 5 speed)');"));
} }
// Load configuration // Load configuration
@ -336,23 +393,28 @@ class GPUFanControllerUsermod : public Usermod {
newPwmPin = top["pwm-pin"] | newPwmPin; newPwmPin = top["pwm-pin"] | newPwmPin;
controlMode = (ControlMode)(top["mode"] | (int)controlMode); controlMode = (ControlMode)(top["mode"] | (int)controlMode);
fixedSpeedPct = top["fixed-speed"] | fixedSpeedPct; fixedSpeedPct = top["fixed-speed"] | fixedSpeedPct;
tempTimeoutMs = top["timeout-ms"] | tempTimeoutMs; tempTimeoutMs = top["timeout-ms"] | (int)tempTimeoutMs;
curveCount = top["curve-points"] | curveCount;
// Load curve points
curveCount = top["curve-count"] | curveCount;
curveCount = constrain(curveCount, 2, MAX_CURVE_POINTS); curveCount = constrain(curveCount, 2, MAX_CURVE_POINTS);
JsonArray curveArr = top["curve"]; // Load curve points
if (!curveArr.isNull()) { curveTemp1 = top["curve-t1"] | curveTemp1;
uint8_t i = 0; curveTemp2 = top["curve-t2"] | curveTemp2;
for (JsonObject point : curveArr) { curveTemp3 = top["curve-t3"] | curveTemp3;
if (i >= MAX_CURVE_POINTS) break; curveTemp4 = top["curve-t4"] | curveTemp4;
curve[i].temp = point["temp"] | curve[i].temp; curveTemp5 = top["curve-t5"] | curveTemp5;
curve[i].speed = point["speed"] | curve[i].speed; curveSpeed1 = top["curve-s1"] | curveSpeed1;
i++; curveSpeed2 = top["curve-s2"] | curveSpeed2;
} curveSpeed3 = top["curve-s3"] | curveSpeed3;
curveCount = i; curveSpeed4 = top["curve-s4"] | curveSpeed4;
} curveSpeed5 = top["curve-s5"] | curveSpeed5;
// Constrain speed values
curveSpeed1 = constrain(curveSpeed1, 0, 100);
curveSpeed2 = constrain(curveSpeed2, 0, 100);
curveSpeed3 = constrain(curveSpeed3, 0, 100);
curveSpeed4 = constrain(curveSpeed4, 0, 100);
curveSpeed5 = constrain(curveSpeed5, 0, 100);
if (!initDone) { if (!initDone) {
pwmPin = newPwmPin; pwmPin = newPwmPin;
@ -366,7 +428,7 @@ class GPUFanControllerUsermod : public Usermod {
} }
} }
return !top["curve-count"].isNull(); return !top["curve-t1"].isNull();
} }
uint16_t getId() override { uint16_t getId() override {