Add /gpu-fan/save endpoint with serializeConfig()
- New POST endpoint handles JSON body with curve config - Calls serializeConfig() to persist to flash - Simplified and cleaned up code
This commit is contained in:
parent
fdcd26b3ce
commit
211118f8ff
@ -1,44 +1,16 @@
|
|||||||
#include "wled.h"
|
#include "wled.h"
|
||||||
#include "gpu_fan_html.h"
|
#include "gpu_fan_html.h"
|
||||||
|
|
||||||
/*
|
|
||||||
* GPU Fan Controller Usermod for WLED
|
|
||||||
*
|
|
||||||
* This usermod controls a PWM fan based on GPU temperature received via HTTP API
|
|
||||||
* from a Python monitoring script. It supports fixed speed mode and temperature
|
|
||||||
* curve-based control.
|
|
||||||
*
|
|
||||||
* Features:
|
|
||||||
* - Web API for temperature updates from external sources (GPU, etc.)
|
|
||||||
* - Fixed speed mode with configurable percentage
|
|
||||||
* - Temperature curve mode with up to 5 configurable points
|
|
||||||
* - Visual curve editor at /gpu-fan
|
|
||||||
* - Safety fallback to 100% if temperature data times out
|
|
||||||
* - Integration with WLED's web interface
|
|
||||||
*
|
|
||||||
* Connections:
|
|
||||||
* - Fan GND -> ESP32 GND
|
|
||||||
* - Fan +12V -> 12V Power Supply
|
|
||||||
* - Fan PWM -> Configured GPIO (default: GPIO 13)
|
|
||||||
*
|
|
||||||
* API Endpoints:
|
|
||||||
* - POST /json/state with JSON: {"GPU-Fan": {"temperature": 65.5}}
|
|
||||||
* - GET /json/info
|
|
||||||
* - GET /gpu-fan (curve editor page)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PWM_FAN_PIN
|
#ifndef PWM_FAN_PIN
|
||||||
#define PWM_FAN_PIN 13
|
#define PWM_FAN_PIN 13
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// PWM configuration for 4-pin PC fans (Intel spec: 25kHz)
|
|
||||||
#define GPU_FAN_PWM_FREQ 25000
|
#define GPU_FAN_PWM_FREQ 25000
|
||||||
#define GPU_FAN_PWM_RESOLUTION 8
|
#define GPU_FAN_PWM_RESOLUTION 8
|
||||||
|
|
||||||
class GPUFanControllerUsermod : public Usermod {
|
class GPUFanControllerUsermod : public Usermod {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Configuration
|
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
bool initDone = false;
|
bool initDone = false;
|
||||||
bool webHandlerRegistered = false;
|
bool webHandlerRegistered = false;
|
||||||
@ -48,53 +20,43 @@ class GPUFanControllerUsermod : public Usermod {
|
|||||||
uint8_t pwmChannel = 255;
|
uint8_t pwmChannel = 255;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Control modes
|
|
||||||
enum ControlMode {
|
enum ControlMode {
|
||||||
MODE_FIXED = 0,
|
MODE_FIXED = 0,
|
||||||
MODE_CURVE = 1
|
MODE_CURVE = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fan configuration
|
|
||||||
ControlMode controlMode = MODE_CURVE;
|
ControlMode controlMode = MODE_CURVE;
|
||||||
uint8_t fixedSpeedPct = 50; // 0-100%
|
uint8_t fixedSpeedPct = 50;
|
||||||
|
|
||||||
// Temperature curve - using flat structure for WLED config compatibility
|
|
||||||
// 5 curve points with separate temp and speed values
|
|
||||||
static const uint8_t MAX_CURVE_POINTS = 5;
|
static const uint8_t MAX_CURVE_POINTS = 5;
|
||||||
uint8_t curveCount = 4;
|
uint8_t curveCount = 4;
|
||||||
|
|
||||||
// Curve point temperatures (°C)
|
|
||||||
int16_t curveTemp1 = 30;
|
int16_t curveTemp1 = 30;
|
||||||
int16_t curveTemp2 = 50;
|
int16_t curveTemp2 = 50;
|
||||||
int16_t curveTemp3 = 70;
|
int16_t curveTemp3 = 70;
|
||||||
int16_t curveTemp4 = 85;
|
int16_t curveTemp4 = 85;
|
||||||
int16_t curveTemp5 = 95;
|
int16_t curveTemp5 = 95;
|
||||||
|
|
||||||
// Curve point speeds (%)
|
|
||||||
uint8_t curveSpeed1 = 30;
|
uint8_t curveSpeed1 = 30;
|
||||||
uint8_t curveSpeed2 = 50;
|
uint8_t curveSpeed2 = 50;
|
||||||
uint8_t curveSpeed3 = 75;
|
uint8_t curveSpeed3 = 75;
|
||||||
uint8_t curveSpeed4 = 100;
|
uint8_t curveSpeed4 = 100;
|
||||||
uint8_t curveSpeed5 = 100;
|
uint8_t curveSpeed5 = 100;
|
||||||
|
|
||||||
// Runtime state
|
|
||||||
float currentTemp = 25.0f;
|
float currentTemp = 25.0f;
|
||||||
uint8_t currentPWM = 0;
|
uint8_t currentPWM = 0;
|
||||||
unsigned long lastTempUpdate = 0;
|
unsigned long lastTempUpdate = 0;
|
||||||
unsigned long tempTimeoutMs = 10000; // 10 second timeout
|
unsigned long tempTimeoutMs = 10000;
|
||||||
unsigned long lastLoopTime = 0;
|
unsigned long lastLoopTime = 0;
|
||||||
unsigned long loopIntervalMs = 2000; // Update every 2 seconds
|
unsigned long loopIntervalMs = 2000;
|
||||||
|
|
||||||
// String constants
|
|
||||||
static const char _name[];
|
static const char _name[];
|
||||||
static const char _enabled[];
|
static const char _enabled[];
|
||||||
|
|
||||||
// Initialize PWM
|
|
||||||
void initPWM() {
|
void initPWM() {
|
||||||
if (pwmPin < 0 || !PinManager::allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) {
|
if (pwmPin < 0 || !PinManager::allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) {
|
||||||
enabled = false;
|
enabled = false;
|
||||||
pwmPin = -1;
|
pwmPin = -1;
|
||||||
DEBUG_PRINTLN(F("GPU Fan: PWM pin allocation failed"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,20 +67,15 @@ class GPUFanControllerUsermod : public Usermod {
|
|||||||
pwmChannel = PinManager::allocateLedc(1);
|
pwmChannel = PinManager::allocateLedc(1);
|
||||||
if (pwmChannel == 255) {
|
if (pwmChannel == 255) {
|
||||||
deinitPWM();
|
deinitPWM();
|
||||||
DEBUG_PRINTLN(F("GPU Fan: LEDC channel allocation failed"));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ledcSetup(pwmChannel, GPU_FAN_PWM_FREQ, GPU_FAN_PWM_RESOLUTION);
|
ledcSetup(pwmChannel, GPU_FAN_PWM_FREQ, GPU_FAN_PWM_RESOLUTION);
|
||||||
ledcAttachPin(pwmPin, pwmChannel);
|
ledcAttachPin(pwmPin, pwmChannel);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
DEBUG_PRINTLN(F("GPU Fan: PWM initialized successfully"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deinitialize PWM
|
|
||||||
void deinitPWM() {
|
void deinitPWM() {
|
||||||
if (pwmPin < 0) return;
|
if (pwmPin < 0) return;
|
||||||
|
|
||||||
PinManager::deallocatePin(pwmPin, PinOwner::UM_Unspecified);
|
PinManager::deallocatePin(pwmPin, PinOwner::UM_Unspecified);
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
if (pwmChannel != 255) {
|
if (pwmChannel != 255) {
|
||||||
@ -129,10 +86,8 @@ class GPUFanControllerUsermod : public Usermod {
|
|||||||
pwmPin = -1;
|
pwmPin = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set fan speed (0-255 PWM value)
|
|
||||||
void setFanPWM(uint8_t pwmValue) {
|
void setFanPWM(uint8_t pwmValue) {
|
||||||
if (!enabled || pwmPin < 0) return;
|
if (!enabled || pwmPin < 0) return;
|
||||||
|
|
||||||
currentPWM = pwmValue;
|
currentPWM = pwmValue;
|
||||||
#ifdef ESP8266
|
#ifdef ESP8266
|
||||||
analogWrite(pwmPin, pwmValue);
|
analogWrite(pwmPin, pwmValue);
|
||||||
@ -141,7 +96,6 @@ class GPUFanControllerUsermod : public Usermod {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get curve temperature at index
|
|
||||||
int16_t getCurveTemp(uint8_t idx) {
|
int16_t getCurveTemp(uint8_t idx) {
|
||||||
switch(idx) {
|
switch(idx) {
|
||||||
case 0: return curveTemp1;
|
case 0: return curveTemp1;
|
||||||
@ -153,7 +107,6 @@ class GPUFanControllerUsermod : public Usermod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get curve speed at index
|
|
||||||
uint8_t getCurveSpeed(uint8_t idx) {
|
uint8_t getCurveSpeed(uint8_t idx) {
|
||||||
switch(idx) {
|
switch(idx) {
|
||||||
case 0: return curveSpeed1;
|
case 0: return curveSpeed1;
|
||||||
@ -165,21 +118,12 @@ class GPUFanControllerUsermod : public Usermod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
||||||
|
|
||||||
// Below first point
|
if (temp <= getCurveTemp(0)) return getCurveSpeed(0);
|
||||||
if (temp <= getCurveTemp(0)) {
|
if (temp >= getCurveTemp(curveCount - 1)) return getCurveSpeed(curveCount - 1);
|
||||||
return getCurveSpeed(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Above last point
|
|
||||||
if (temp >= getCurveTemp(curveCount - 1)) {
|
|
||||||
return getCurveSpeed(curveCount - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linear interpolation between points
|
|
||||||
for (uint8_t i = 0; i < curveCount - 1; i++) {
|
for (uint8_t i = 0; i < curveCount - 1; i++) {
|
||||||
int16_t t1 = getCurveTemp(i);
|
int16_t t1 = getCurveTemp(i);
|
||||||
int16_t t2 = getCurveTemp(i + 1);
|
int16_t t2 = getCurveTemp(i + 1);
|
||||||
@ -187,40 +131,19 @@ class GPUFanControllerUsermod : public Usermod {
|
|||||||
uint8_t s2 = getCurveSpeed(i + 1);
|
uint8_t s2 = getCurveSpeed(i + 1);
|
||||||
|
|
||||||
if (temp >= t1 && temp <= t2) {
|
if (temp >= t1 && temp <= t2) {
|
||||||
float tempRange = t2 - t1;
|
float ratio = (temp - t1) / (float)(t2 - t1);
|
||||||
float speedRange = s2 - s1;
|
return s1 + (uint8_t)(ratio * (s2 - s1));
|
||||||
float tempDiff = temp - t1;
|
|
||||||
|
|
||||||
int speed = s1 + (int)((tempDiff / tempRange) * speedRange);
|
|
||||||
return constrain(speed, 0, 100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 50;
|
||||||
return 50; // Fallback
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update fan speed based on current mode and temperature
|
|
||||||
void updateFanSpeed() {
|
void updateFanSpeed() {
|
||||||
uint8_t targetSpeedPct;
|
uint8_t targetSpeedPct = (controlMode == MODE_FIXED) ? fixedSpeedPct : calculateCurveSpeed(currentTemp);
|
||||||
|
|
||||||
if (controlMode == MODE_FIXED) {
|
|
||||||
targetSpeedPct = fixedSpeedPct;
|
|
||||||
} else {
|
|
||||||
targetSpeedPct = calculateCurveSpeed(currentTemp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert percentage to PWM (0-255)
|
|
||||||
uint8_t targetPWM = map(constrain(targetSpeedPct, 0, 100), 0, 100, 0, 255);
|
uint8_t targetPWM = map(constrain(targetSpeedPct, 0, 100), 0, 100, 0, 255);
|
||||||
|
if (targetPWM != currentPWM) setFanPWM(targetPWM);
|
||||||
if (targetPWM != currentPWM) {
|
|
||||||
setFanPWM(targetPWM);
|
|
||||||
DEBUG_PRINTF("GPU Fan: %d%% (PWM: %d) | Temp: %.1f°C | Mode: %s\n",
|
|
||||||
targetSpeedPct, currentPWM, currentTemp,
|
|
||||||
controlMode == MODE_FIXED ? "Fixed" : "Curve");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register web handler - call this once
|
|
||||||
void registerWebHandler() {
|
void registerWebHandler() {
|
||||||
if (webHandlerRegistered) return;
|
if (webHandlerRegistered) return;
|
||||||
|
|
||||||
@ -228,26 +151,50 @@ class GPUFanControllerUsermod : public Usermod {
|
|||||||
request->send_P(200, "text/html", GPU_FAN_HTML);
|
request->send_P(200, "text/html", GPU_FAN_HTML);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Custom save endpoint
|
||||||
|
server.on("/gpu-fan/save", HTTP_POST,
|
||||||
|
[](AsyncWebServerRequest *request) {
|
||||||
|
request->send(200, "application/json", "{\"success\":true}");
|
||||||
|
},
|
||||||
|
NULL,
|
||||||
|
[this](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||||
|
DynamicJsonDocument doc(1024);
|
||||||
|
if (deserializeJson(doc, (const char*)data, len)) return;
|
||||||
|
|
||||||
|
if (doc.containsKey("mode")) controlMode = (ControlMode)doc["mode"].as<int>();
|
||||||
|
if (doc.containsKey("fixed-speed")) fixedSpeedPct = doc["fixed-speed"].as<int>();
|
||||||
|
if (doc.containsKey("curve-points")) curveCount = constrain(doc["curve-points"].as<int>(), 2, 5);
|
||||||
|
|
||||||
|
if (doc.containsKey("curve-t1")) curveTemp1 = doc["curve-t1"].as<int>();
|
||||||
|
if (doc.containsKey("curve-t2")) curveTemp2 = doc["curve-t2"].as<int>();
|
||||||
|
if (doc.containsKey("curve-t3")) curveTemp3 = doc["curve-t3"].as<int>();
|
||||||
|
if (doc.containsKey("curve-t4")) curveTemp4 = doc["curve-t4"].as<int>();
|
||||||
|
if (doc.containsKey("curve-t5")) curveTemp5 = doc["curve-t5"].as<int>();
|
||||||
|
if (doc.containsKey("curve-s1")) curveSpeed1 = constrain(doc["curve-s1"].as<int>(), 0, 100);
|
||||||
|
if (doc.containsKey("curve-s2")) curveSpeed2 = constrain(doc["curve-s2"].as<int>(), 0, 100);
|
||||||
|
if (doc.containsKey("curve-s3")) curveSpeed3 = constrain(doc["curve-s3"].as<int>(), 0, 100);
|
||||||
|
if (doc.containsKey("curve-s4")) curveSpeed4 = constrain(doc["curve-s4"].as<int>(), 0, 100);
|
||||||
|
if (doc.containsKey("curve-s5")) curveSpeed5 = constrain(doc["curve-s5"].as<int>(), 0, 100);
|
||||||
|
|
||||||
|
serializeConfig();
|
||||||
|
updateFanSpeed();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
webHandlerRegistered = true;
|
webHandlerRegistered = true;
|
||||||
DEBUG_PRINTLN(F("GPU Fan: Web handler registered at /gpu-fan"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void setup() override {
|
void setup() override {
|
||||||
initPWM();
|
initPWM();
|
||||||
setFanPWM((fixedSpeedPct * 255) / 100); // Initial speed
|
setFanPWM((fixedSpeedPct * 255) / 100);
|
||||||
lastTempUpdate = millis();
|
lastTempUpdate = millis();
|
||||||
initDone = true;
|
initDone = true;
|
||||||
|
|
||||||
// Register web handler immediately during setup
|
|
||||||
registerWebHandler();
|
registerWebHandler();
|
||||||
|
|
||||||
DEBUG_PRINTLN(F("GPU Fan Controller initialized"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void connected() override {
|
void connected() override {
|
||||||
// Also try to register here in case setup was too early
|
|
||||||
registerWebHandler();
|
registerWebHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,110 +203,75 @@ class GPUFanControllerUsermod : public Usermod {
|
|||||||
|
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
|
|
||||||
// Check for temperature timeout in curve mode
|
|
||||||
if (controlMode == MODE_CURVE && (now - lastTempUpdate > tempTimeoutMs)) {
|
if (controlMode == MODE_CURVE && (now - lastTempUpdate > tempTimeoutMs)) {
|
||||||
if (currentPWM != 255) {
|
if (currentPWM != 255) setFanPWM(255);
|
||||||
setFanPWM(255);
|
|
||||||
DEBUG_PRINTLN(F("GPU Fan: Temperature timeout - running at 100% for safety"));
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update fan speed periodically
|
|
||||||
if (now - lastLoopTime > loopIntervalMs) {
|
if (now - lastLoopTime > loopIntervalMs) {
|
||||||
updateFanSpeed();
|
updateFanSpeed();
|
||||||
lastLoopTime = now;
|
lastLoopTime = now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle HTTP API requests
|
bool handleButton(uint8_t b) override { return false; }
|
||||||
bool handleButton(uint8_t b) override {
|
|
||||||
// Not used for this usermod
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add info to the WLED info panel
|
|
||||||
void addToJsonInfo(JsonObject& root) override {
|
void addToJsonInfo(JsonObject& root) override {
|
||||||
JsonObject user = root["u"];
|
JsonObject user = root["u"];
|
||||||
if (user.isNull()) user = root.createNestedObject("u");
|
if (user.isNull()) user = root.createNestedObject("u");
|
||||||
|
|
||||||
// Add enable/disable button and link to curve editor
|
|
||||||
JsonArray infoArr = user.createNestedArray(FPSTR(_name));
|
JsonArray infoArr = user.createNestedArray(FPSTR(_name));
|
||||||
String uiDomString = F("<button class=\"btn btn-xs\" onclick=\"requestJson({'");
|
String uiDomString = F("<button class=\"btn btn-xs\" onclick=\"requestJson({'");
|
||||||
uiDomString += FPSTR(_name);
|
uiDomString += FPSTR(_name);
|
||||||
uiDomString += F("':{'");
|
uiDomString += F("':{'enabled':");
|
||||||
uiDomString += FPSTR(_enabled);
|
|
||||||
uiDomString += F("':");
|
|
||||||
uiDomString += enabled ? "false" : "true";
|
uiDomString += enabled ? "false" : "true";
|
||||||
uiDomString += F("}});\"><i class=\"icons ");
|
uiDomString += F("}});\"><i class=\"icons ");
|
||||||
uiDomString += enabled ? "on" : "off";
|
uiDomString += enabled ? "on" : "off";
|
||||||
uiDomString += F("\"></i></button>");
|
uiDomString += F("\"></i></button> <a href=\"/gpu-fan\" target=\"_blank\" style=\"color:#e94560\">[Editor]</a>");
|
||||||
uiDomString += F(" <a href=\"/gpu-fan\" target=\"_blank\" style=\"color:#e94560;font-size:0.9em;\">[Editor]</a>");
|
|
||||||
infoArr.add(uiDomString);
|
infoArr.add(uiDomString);
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
// Temperature display
|
|
||||||
JsonArray tempArr = user.createNestedArray(F("GPU Temp"));
|
JsonArray tempArr = user.createNestedArray(F("GPU Temp"));
|
||||||
tempArr.add(currentTemp);
|
tempArr.add(currentTemp);
|
||||||
tempArr.add(F("°C"));
|
tempArr.add(F("°C"));
|
||||||
|
|
||||||
// Fan speed display
|
|
||||||
JsonArray speedArr = user.createNestedArray(F("Fan Speed"));
|
JsonArray speedArr = user.createNestedArray(F("Fan Speed"));
|
||||||
uint8_t speedPct = (currentPWM * 100) / 255;
|
speedArr.add((currentPWM * 100) / 255);
|
||||||
speedArr.add(speedPct);
|
|
||||||
speedArr.add(F("%"));
|
speedArr.add(F("%"));
|
||||||
|
|
||||||
// Mode display
|
|
||||||
JsonArray modeArr = user.createNestedArray(F("Fan Mode"));
|
JsonArray modeArr = user.createNestedArray(F("Fan Mode"));
|
||||||
modeArr.add(controlMode == MODE_FIXED ? F("Fixed") : F("Curve"));
|
modeArr.add(controlMode == MODE_FIXED ? F("Fixed") : F("Curve"));
|
||||||
|
|
||||||
// Manual speed slider
|
|
||||||
JsonArray sliderArr = user.createNestedArray(F("Manual"));
|
|
||||||
String sliderStr = F("<div class=\"slider\"><div class=\"sliderwrap il\"><input class=\"noslide\" onchange=\"requestJson({'");
|
|
||||||
sliderStr += FPSTR(_name);
|
|
||||||
sliderStr += F("':{'speed':parseInt(this.value)}});\" oninput=\"updateTrail(this);\" max=100 min=0 type=\"range\" value=");
|
|
||||||
sliderStr += String(fixedSpeedPct);
|
|
||||||
sliderStr += F(" /><div class=\"sliderdisplay\"></div></div></div>");
|
|
||||||
sliderArr.add(sliderStr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle state changes from JSON API
|
|
||||||
void readFromJsonState(JsonObject& root) override {
|
void readFromJsonState(JsonObject& root) override {
|
||||||
if (!initDone) return;
|
if (!initDone) return;
|
||||||
|
|
||||||
JsonObject usermod = root[FPSTR(_name)];
|
JsonObject usermod = root[FPSTR(_name)];
|
||||||
if (!usermod.isNull()) {
|
if (usermod.isNull()) return;
|
||||||
// Enable/disable
|
|
||||||
if (usermod[FPSTR(_enabled)].is<bool>()) {
|
if (usermod["enabled"].is<bool>()) {
|
||||||
enabled = usermod[FPSTR(_enabled)].as<bool>();
|
enabled = usermod["enabled"].as<bool>();
|
||||||
if (!enabled) setFanPWM(0);
|
if (!enabled) setFanPWM(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manual speed control
|
if (usermod["speed"].is<int>()) {
|
||||||
if (enabled && !usermod["speed"].isNull() && usermod["speed"].is<int>()) {
|
|
||||||
fixedSpeedPct = usermod["speed"].as<int>();
|
fixedSpeedPct = usermod["speed"].as<int>();
|
||||||
controlMode = MODE_FIXED; // Switch to fixed mode when manually setting speed
|
controlMode = MODE_FIXED;
|
||||||
updateFanSpeed();
|
updateFanSpeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mode selection
|
if (usermod["mode"].is<int>()) {
|
||||||
if (!usermod["mode"].isNull() && usermod["mode"].is<int>()) {
|
|
||||||
controlMode = (ControlMode)usermod["mode"].as<int>();
|
controlMode = (ControlMode)usermod["mode"].as<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temperature update (from external source like Python script)
|
|
||||||
if (!usermod["temperature"].isNull()) {
|
if (!usermod["temperature"].isNull()) {
|
||||||
currentTemp = usermod["temperature"].as<float>();
|
currentTemp = usermod["temperature"].as<float>();
|
||||||
lastTempUpdate = millis();
|
lastTempUpdate = millis();
|
||||||
if (controlMode == MODE_CURVE) {
|
if (controlMode == MODE_CURVE) updateFanSpeed();
|
||||||
updateFanSpeed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save configuration
|
|
||||||
void addToConfig(JsonObject& root) override {
|
void addToConfig(JsonObject& root) override {
|
||||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
top[FPSTR(_enabled)] = enabled;
|
top[FPSTR(_enabled)] = enabled;
|
||||||
@ -368,8 +280,6 @@ class GPUFanControllerUsermod : public Usermod {
|
|||||||
top["fixed-speed"] = fixedSpeedPct;
|
top["fixed-speed"] = fixedSpeedPct;
|
||||||
top["timeout-ms"] = (int)tempTimeoutMs;
|
top["timeout-ms"] = (int)tempTimeoutMs;
|
||||||
top["curve-points"] = curveCount;
|
top["curve-points"] = curveCount;
|
||||||
|
|
||||||
// Save curve points as flat values
|
|
||||||
top["curve-t1"] = curveTemp1;
|
top["curve-t1"] = curveTemp1;
|
||||||
top["curve-t2"] = curveTemp2;
|
top["curve-t2"] = curveTemp2;
|
||||||
top["curve-t3"] = curveTemp3;
|
top["curve-t3"] = curveTemp3;
|
||||||
@ -382,86 +292,51 @@ class GPUFanControllerUsermod : public Usermod {
|
|||||||
top["curve-s5"] = curveSpeed5;
|
top["curve-s5"] = curveSpeed5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append config data (labels for config page)
|
|
||||||
void appendConfigData() override {
|
void appendConfigData() override {
|
||||||
oappend(SET_F("addInfo('GPU-Fan:pwm-pin',1,'GPIO');"));
|
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:mode',1,'0=Fixed, 1=Curve');"));
|
||||||
oappend(SET_F("addInfo('GPU-Fan:fixed-speed',1,'%');"));
|
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
|
|
||||||
bool readFromConfig(JsonObject& root) override {
|
bool readFromConfig(JsonObject& root) override {
|
||||||
int8_t newPwmPin = pwmPin;
|
int8_t newPwmPin = pwmPin;
|
||||||
|
|
||||||
JsonObject top = root[FPSTR(_name)];
|
JsonObject top = root[FPSTR(_name)];
|
||||||
if (top.isNull()) {
|
if (top.isNull()) return false;
|
||||||
DEBUG_PRINTLN(F("GPU Fan: No config found, using defaults"));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled = top[FPSTR(_enabled)] | enabled;
|
enabled = top[FPSTR(_enabled)] | enabled;
|
||||||
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"] | (int)tempTimeoutMs;
|
tempTimeoutMs = top["timeout-ms"] | (int)tempTimeoutMs;
|
||||||
curveCount = top["curve-points"] | curveCount;
|
curveCount = constrain(top["curve-points"] | curveCount, 2, MAX_CURVE_POINTS);
|
||||||
curveCount = constrain(curveCount, 2, MAX_CURVE_POINTS);
|
|
||||||
|
|
||||||
// Load curve points
|
|
||||||
curveTemp1 = top["curve-t1"] | curveTemp1;
|
curveTemp1 = top["curve-t1"] | curveTemp1;
|
||||||
curveTemp2 = top["curve-t2"] | curveTemp2;
|
curveTemp2 = top["curve-t2"] | curveTemp2;
|
||||||
curveTemp3 = top["curve-t3"] | curveTemp3;
|
curveTemp3 = top["curve-t3"] | curveTemp3;
|
||||||
curveTemp4 = top["curve-t4"] | curveTemp4;
|
curveTemp4 = top["curve-t4"] | curveTemp4;
|
||||||
curveTemp5 = top["curve-t5"] | curveTemp5;
|
curveTemp5 = top["curve-t5"] | curveTemp5;
|
||||||
curveSpeed1 = top["curve-s1"] | curveSpeed1;
|
curveSpeed1 = constrain(top["curve-s1"] | curveSpeed1, 0, 100);
|
||||||
curveSpeed2 = top["curve-s2"] | curveSpeed2;
|
curveSpeed2 = constrain(top["curve-s2"] | curveSpeed2, 0, 100);
|
||||||
curveSpeed3 = top["curve-s3"] | curveSpeed3;
|
curveSpeed3 = constrain(top["curve-s3"] | curveSpeed3, 0, 100);
|
||||||
curveSpeed4 = top["curve-s4"] | curveSpeed4;
|
curveSpeed4 = constrain(top["curve-s4"] | curveSpeed4, 0, 100);
|
||||||
curveSpeed5 = top["curve-s5"] | curveSpeed5;
|
curveSpeed5 = constrain(top["curve-s5"] | curveSpeed5, 0, 100);
|
||||||
|
|
||||||
// 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;
|
||||||
DEBUG_PRINTLN(F("GPU Fan: Config loaded"));
|
} else if (pwmPin != newPwmPin) {
|
||||||
} else {
|
|
||||||
if (pwmPin != newPwmPin) {
|
|
||||||
DEBUG_PRINTLN(F("GPU Fan: Re-initializing pins"));
|
|
||||||
deinitPWM();
|
deinitPWM();
|
||||||
pwmPin = newPwmPin;
|
pwmPin = newPwmPin;
|
||||||
setup();
|
setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !top["curve-t1"].isNull();
|
uint16_t getId() override { return USERMOD_ID_UNSPECIFIED; }
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t getId() override {
|
|
||||||
return USERMOD_ID_UNSPECIFIED; // Change this if you add to const.h
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// String constants
|
|
||||||
const char GPUFanControllerUsermod::_name[] PROGMEM = "GPU-Fan";
|
const char GPUFanControllerUsermod::_name[] PROGMEM = "GPU-Fan";
|
||||||
const char GPUFanControllerUsermod::_enabled[] PROGMEM = "enabled";
|
const char GPUFanControllerUsermod::_enabled[] PROGMEM = "enabled";
|
||||||
|
|
||||||
// Register the usermod
|
|
||||||
static GPUFanControllerUsermod gpuFanController;
|
static GPUFanControllerUsermod gpuFanController;
|
||||||
REGISTER_USERMOD(gpuFanController);
|
REGISTER_USERMOD(gpuFanController);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user