-
-
Notifications
You must be signed in to change notification settings - Fork 4k
added: KOBA_EGPU mod for controlling cooling and lighting in the self… #5349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,334 @@ | ||||||||||||||||||||||||||||
| #include "wled.h" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #if not defined(CONFIG_IDF_TARGET_ESP32C3) | ||||||||||||||||||||||||||||
| //#error Adapted only for ESP32C3 | ||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #if not defined(THERMISTOR_GPU_PIN) or THERMISTOR_GPU_PIN not_eq 0 | ||||||||||||||||||||||||||||
| #error THERMISTOR_GPU_PIN NOT defined or not equals GPIO0 | ||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #if not defined(THERMISTOR_FAN_PIN) or THERMISTOR_FAN_PIN not_eq 1 | ||||||||||||||||||||||||||||
| #error THERMISTOR_FAN_PIN NOT defined or not equals GPIO1 | ||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #if not defined(FAN_TACHOMETER_PIN) or FAN_TACHOMETER_PIN not_eq 6 | ||||||||||||||||||||||||||||
| #error FAN_TACHOMETER_PIN NOT defined | ||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #if not defined(FAN_PWM_PIN) or FAN_PWM_PIN not_eq 10 | ||||||||||||||||||||||||||||
| #error FAN_PWM_PIN NOT defined | ||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| #include <esp_adc_cal.h> | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| namespace { | ||||||||||||||||||||||||||||
| float float_map(float x, float in_min, float in_max, float out_min, float out_max) { | ||||||||||||||||||||||||||||
| return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class NTCThermistor { | ||||||||||||||||||||||||||||
| private: | ||||||||||||||||||||||||||||
| float r0; | ||||||||||||||||||||||||||||
| float inv_t0; | ||||||||||||||||||||||||||||
| float beta; | ||||||||||||||||||||||||||||
| public: | ||||||||||||||||||||||||||||
| explicit NTCThermistor(float r0 = 10000.0f, float beta = 3950.0f, float t0 = 25.0f) | ||||||||||||||||||||||||||||
| : r0(r0), inv_t0(1.0f / (t0 + 273.15f)), beta(beta) {} | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| float calculateTemperatureFromResistance(float resistance) const { | ||||||||||||||||||||||||||||
| if (resistance <= 0.0f) return NAN; | ||||||||||||||||||||||||||||
| float inv_t = logf(resistance / r0) / beta + inv_t0; | ||||||||||||||||||||||||||||
| return 1.0f / inv_t - 273.15f; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| struct KobaEGPUUserModCoreConfig { | ||||||||||||||||||||||||||||
| float fan_max_speed_threshold_temperature = 70.0f; | ||||||||||||||||||||||||||||
| float fan_min_speed_threshold_temperature = 45.0f; | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class KobaEGPUUserModCore { | ||||||||||||||||||||||||||||
| private: | ||||||||||||||||||||||||||||
| bool _configured = false; | ||||||||||||||||||||||||||||
| uint32_t _delta_update_time_ms = 100; | ||||||||||||||||||||||||||||
| uint32_t _pulses_count = 0; | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Race condition on
🔒 Proposed fix using atomic operations+#include <atomic>
+
namespace {
// ...
class KobaEGPUUserModCore {
private:
bool _configured = false;
uint32_t _delta_update_time_ms = 100;
- uint32_t _pulses_count = 0;
+ std::atomic<uint32_t> _pulses_count{0};
// ...
void _update_tachometer_speed(uint32_t delta_ms) {
- float new_rpm = float(_pulses_count) * 60.0f * 1000.0f / float(delta_ms);
+ uint32_t pulses = _pulses_count.exchange(0);
+ float new_rpm = float(pulses) * 60.0f * 1000.0f / float(delta_ms);
_last_check_tachometer_speed = millis();
- _pulses_count = 0;
if (not isnan(new_rpm) and new_rpm not_eq _current_rpm) {
_current_rpm = new_rpm;
}
}
IRAM_ATTR static void tachometer_isr(void *ctx) {
- reinterpret_cast<KobaEGPUUserModCore *>(ctx)->_pulses_count++;
+ reinterpret_cast<KobaEGPUUserModCore *>(ctx)->_pulses_count.fetch_add(1, std::memory_order_relaxed);
}Also applies to: 105-112, 119-121 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| float _current_rpm = NAN; | ||||||||||||||||||||||||||||
| uint64_t _last_check_tachometer_speed = 0; | ||||||||||||||||||||||||||||
| ledc_channel_t _fan_pwm_channel = LEDC_CHANNEL_5; | ||||||||||||||||||||||||||||
| esp_adc_cal_characteristics_t *_adc_chars = nullptr; | ||||||||||||||||||||||||||||
| float _last_thr_gpu_temp = NAN; | ||||||||||||||||||||||||||||
| float _last_thr_fan_temp = NAN; | ||||||||||||||||||||||||||||
| NTCThermistor _thermistor = NTCThermistor(10000.0f, 3950.0f, 25.0f); | ||||||||||||||||||||||||||||
| KobaEGPUUserModCoreConfig _cfg; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| float _calc_temp(int adc_value) { | ||||||||||||||||||||||||||||
| if (adc_value > 4000 or adc_value < 100) return NAN; | ||||||||||||||||||||||||||||
| float voltage = (static_cast<float>(adc_value) / 4095.0f) * 3.0f; | ||||||||||||||||||||||||||||
| float rt = 10000.0f * (voltage / (3.3f - voltage)); | ||||||||||||||||||||||||||||
| return _thermistor.calculateTemperatureFromResistance(rt); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+65
to
+70
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent reference voltage (3.0V vs 3.3V) causes incorrect temperature calculation. Line 67 calculates voltage using 3.0V reference, but line 68 uses 3.3V for the voltage divider formula. This inconsistency will produce incorrect resistance and temperature values. 🐛 Proposed fix float _calc_temp(int adc_value) {
if (adc_value > 4000 or adc_value < 100) return NAN;
- float voltage = (static_cast<float>(adc_value) / 4095.0f) * 3.0f;
- float rt = 10000.0f * (voltage / (3.3f - voltage));
+ constexpr float vref = 3.3f;
+ float voltage = (static_cast<float>(adc_value) / 4095.0f) * vref;
+ float rt = 10000.0f * (voltage / (vref - voltage));
return _thermistor.calculateTemperatureFromResistance(rt);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void _update_fan_speed() { | ||||||||||||||||||||||||||||
| if (isnan(_last_thr_gpu_temp)) { | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| uint8_t new_pwm = _last_thr_gpu_temp > _cfg.fan_max_speed_threshold_temperature ? 255 : 0; | ||||||||||||||||||||||||||||
| if (_last_thr_gpu_temp > _cfg.fan_min_speed_threshold_temperature and | ||||||||||||||||||||||||||||
| _last_thr_gpu_temp < _cfg.fan_max_speed_threshold_temperature) { | ||||||||||||||||||||||||||||
| new_pwm = uint8_t(float_map( | ||||||||||||||||||||||||||||
| _last_thr_gpu_temp, | ||||||||||||||||||||||||||||
| _cfg.fan_min_speed_threshold_temperature, _cfg.fan_max_speed_threshold_temperature, | ||||||||||||||||||||||||||||
| 0, 255 | ||||||||||||||||||||||||||||
| )); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| _set_fan_speed(new_pwm); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void _update_temperatures() { | ||||||||||||||||||||||||||||
| auto gpu_temp = _calc_temp(adc1_get_raw(adc1_channel_t(THERMISTOR_GPU_PIN))); | ||||||||||||||||||||||||||||
| auto fan_temp = _calc_temp(adc1_get_raw(adc1_channel_t(THERMISTOR_FAN_PIN))); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| bool need_update_fan_speed = false; | ||||||||||||||||||||||||||||
| if (not isnan(fan_temp) and fan_temp not_eq _last_thr_fan_temp) { | ||||||||||||||||||||||||||||
| _last_thr_fan_temp = fan_temp; | ||||||||||||||||||||||||||||
| need_update_fan_speed = true; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| if (not isnan(gpu_temp) and gpu_temp not_eq _last_thr_gpu_temp) { | ||||||||||||||||||||||||||||
| _last_thr_gpu_temp = gpu_temp; | ||||||||||||||||||||||||||||
| need_update_fan_speed = true; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| if (need_update_fan_speed) _update_fan_speed(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void _update_tachometer_speed(uint32_t delta_ms) { | ||||||||||||||||||||||||||||
| float new_rpm = float(_pulses_count) * 60.0f * 1000.0f / float(delta_ms); | ||||||||||||||||||||||||||||
| _last_check_tachometer_speed = millis(); | ||||||||||||||||||||||||||||
| _pulses_count = 0; | ||||||||||||||||||||||||||||
| if (not isnan(new_rpm) and new_rpm not_eq _current_rpm) { | ||||||||||||||||||||||||||||
| _current_rpm = new_rpm; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void _set_fan_speed(uint8_t pwm_value) { | ||||||||||||||||||||||||||||
| ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, _fan_pwm_channel, pwm_value)); | ||||||||||||||||||||||||||||
| ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, _fan_pwm_channel)); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| IRAM_ATTR static void tachometer_isr(void *ctx) { | ||||||||||||||||||||||||||||
| reinterpret_cast<KobaEGPUUserModCore *>(ctx)->_pulses_count++; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public: | ||||||||||||||||||||||||||||
| KobaEGPUUserModCore() = default; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| ~KobaEGPUUserModCore() { | ||||||||||||||||||||||||||||
| // todo destroy adc | ||||||||||||||||||||||||||||
| delete _adc_chars; | ||||||||||||||||||||||||||||
| gpio_intr_disable(GPIO_NUM_8); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+126
to
+130
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Destructor disables wrong GPIO pin.
🐛 Proposed fix ~KobaEGPUUserModCore() {
// todo destroy adc
delete _adc_chars;
- gpio_intr_disable(GPIO_NUM_8);
+ gpio_isr_handler_remove(gpio_num_t(FAN_TACHOMETER_PIN));
+ gpio_intr_disable(gpio_num_t(FAN_TACHOMETER_PIN));
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| KobaEGPUUserModCoreConfig get_config() { | ||||||||||||||||||||||||||||
| return _cfg; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| float get_fan_rpm() const { | ||||||||||||||||||||||||||||
| return _current_rpm; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| float get_gpu_temp() const { | ||||||||||||||||||||||||||||
| return _last_thr_gpu_temp; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| float get_fan_temp() const { | ||||||||||||||||||||||||||||
| return _last_thr_fan_temp; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void set_cfg(KobaEGPUUserModCoreConfig cfg) { | ||||||||||||||||||||||||||||
| if (cfg.fan_min_speed_threshold_temperature >= cfg.fan_max_speed_threshold_temperature or | ||||||||||||||||||||||||||||
| cfg.fan_min_speed_threshold_temperature < 0.0f or cfg.fan_max_speed_threshold_temperature < 10.0f) { | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| _cfg = cfg; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void setup() { | ||||||||||||||||||||||||||||
| ledc_timer_config_t ledc_timer = { | ||||||||||||||||||||||||||||
| .speed_mode = LEDC_LOW_SPEED_MODE, | ||||||||||||||||||||||||||||
| .duty_resolution = LEDC_TIMER_8_BIT, | ||||||||||||||||||||||||||||
| .timer_num = LEDC_TIMER_0, | ||||||||||||||||||||||||||||
| .freq_hz = 20000, | ||||||||||||||||||||||||||||
| .clk_cfg = LEDC_AUTO_CLK | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); | ||||||||||||||||||||||||||||
| ledc_channel_config_t ledc_channel = { | ||||||||||||||||||||||||||||
| .gpio_num = FAN_PWM_PIN, | ||||||||||||||||||||||||||||
| .speed_mode = LEDC_LOW_SPEED_MODE, | ||||||||||||||||||||||||||||
| .channel = _fan_pwm_channel, | ||||||||||||||||||||||||||||
| .intr_type = LEDC_INTR_DISABLE, | ||||||||||||||||||||||||||||
| .timer_sel = LEDC_TIMER_0, | ||||||||||||||||||||||||||||
| .duty = 0, // Set duty to 0% | ||||||||||||||||||||||||||||
| .hpoint = 0 | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); | ||||||||||||||||||||||||||||
| _adc_chars = new esp_adc_cal_characteristics_t; | ||||||||||||||||||||||||||||
| esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 0, _adc_chars); | ||||||||||||||||||||||||||||
| ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_12)); | ||||||||||||||||||||||||||||
| ESP_ERROR_CHECK(adc1_config_channel_atten(adc1_channel_t(THERMISTOR_GPU_PIN), ADC_ATTEN_DB_11)); | ||||||||||||||||||||||||||||
| ESP_ERROR_CHECK(adc1_config_channel_atten(adc1_channel_t(THERMISTOR_FAN_PIN), ADC_ATTEN_DB_11)); | ||||||||||||||||||||||||||||
| ESP_ERROR_CHECK(gpio_set_direction(gpio_num_t(FAN_TACHOMETER_PIN), GPIO_MODE_INPUT)); | ||||||||||||||||||||||||||||
| ESP_ERROR_CHECK(gpio_set_intr_type(gpio_num_t(FAN_TACHOMETER_PIN), GPIO_INTR_NEGEDGE)); | ||||||||||||||||||||||||||||
| ESP_ERROR_CHECK(gpio_install_isr_service(0)); | ||||||||||||||||||||||||||||
| ESP_ERROR_CHECK(gpio_isr_handler_add(gpio_num_t(FAN_TACHOMETER_PIN), tachometer_isr, this)); | ||||||||||||||||||||||||||||
|
Comment on lines
+182
to
+183
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If another usermod has already installed the ISR service, this call returns 🛡️ Proposed fix- ESP_ERROR_CHECK(gpio_install_isr_service(0));
+ esp_err_t err = gpio_install_isr_service(0);
+ if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
+ ESP_ERROR_CHECK(err); // Will abort on unexpected errors
+ }
ESP_ERROR_CHECK(gpio_isr_handler_add(gpio_num_t(FAN_TACHOMETER_PIN), tachometer_isr, this));🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
| _last_check_tachometer_speed = millis(); | ||||||||||||||||||||||||||||
| _configured = true; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void task_iter() { | ||||||||||||||||||||||||||||
| if (not _configured) return; | ||||||||||||||||||||||||||||
| static uint64_t last_check_temp = 0; | ||||||||||||||||||||||||||||
| if (millis() - last_check_temp > _delta_update_time_ms) { | ||||||||||||||||||||||||||||
| _update_temperatures(); | ||||||||||||||||||||||||||||
| last_check_temp = millis(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| uint32_t delta = millis() - _last_check_tachometer_speed; | ||||||||||||||||||||||||||||
| if (delta > _delta_update_time_ms) { | ||||||||||||||||||||||||||||
| _update_tachometer_speed(delta); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class KobaEGPUUsermod : public Usermod { | ||||||||||||||||||||||||||||
| private: | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| bool _init_done = false; | ||||||||||||||||||||||||||||
| bool _is_enabled = true; | ||||||||||||||||||||||||||||
| KobaEGPUUserModCore _core; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| static const char _mod_name_key[]; | ||||||||||||||||||||||||||||
| static const char _mod_enabled_string[]; | ||||||||||||||||||||||||||||
| static const char _unavailable_string[]; | ||||||||||||||||||||||||||||
| static const char _celsius_prefix_string[]; | ||||||||||||||||||||||||||||
| static const char _rpm_prefix_string[]; | ||||||||||||||||||||||||||||
| static const char _fan_speed_string[]; | ||||||||||||||||||||||||||||
| static const char _gpu_temp_string[]; | ||||||||||||||||||||||||||||
| static const char _fan_temp_string[]; | ||||||||||||||||||||||||||||
| static const char _fan_min_speed_threshold_temperature_key[]; | ||||||||||||||||||||||||||||
| static const char _fan_max_speed_threshold_temperature_key[]; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public: | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void setup() override { | ||||||||||||||||||||||||||||
| _core.setup(); | ||||||||||||||||||||||||||||
| _init_done = true; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void loop() override { | ||||||||||||||||||||||||||||
| if (not _is_enabled or strip.isUpdating()) return; | ||||||||||||||||||||||||||||
| _core.task_iter(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void addToJsonInfo(JsonObject &root) override { | ||||||||||||||||||||||||||||
| JsonObject user = root["u"]; | ||||||||||||||||||||||||||||
| if (user.isNull()) user = root.createNestedObject("u"); | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| JsonArray infoArr = user.createNestedArray(FPSTR(_mod_name_key)); | ||||||||||||||||||||||||||||
| String uiDomString = F("<button class=\"btn btn-xs\" onclick=\"requestJson({'"); | ||||||||||||||||||||||||||||
| uiDomString += FPSTR(_mod_name_key); | ||||||||||||||||||||||||||||
| uiDomString += F("':{'"); | ||||||||||||||||||||||||||||
| uiDomString += FPSTR(_mod_enabled_string); | ||||||||||||||||||||||||||||
| uiDomString += F("':"); | ||||||||||||||||||||||||||||
| uiDomString += _is_enabled ? "false" : "true"; | ||||||||||||||||||||||||||||
| uiDomString += F("}});\"><i class=\"icons "); | ||||||||||||||||||||||||||||
| uiDomString += _is_enabled ? "on" : "off"; | ||||||||||||||||||||||||||||
| uiDomString += F("\"></i></button>"); | ||||||||||||||||||||||||||||
| infoArr.add(uiDomString); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| if (_is_enabled) { | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| JsonArray data = user.createNestedArray(_fan_speed_string); | ||||||||||||||||||||||||||||
| if (not isnan(_core.get_fan_rpm())) { | ||||||||||||||||||||||||||||
| data.add(uint32_t(_core.get_fan_rpm())); | ||||||||||||||||||||||||||||
| data.add(_rpm_prefix_string); | ||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||
| data.add(_unavailable_string); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| JsonArray data = user.createNestedArray(_gpu_temp_string); | ||||||||||||||||||||||||||||
| if (not isnan(_core.get_gpu_temp())) { | ||||||||||||||||||||||||||||
| data.add(_core.get_gpu_temp()); | ||||||||||||||||||||||||||||
| data.add(_celsius_prefix_string); | ||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||
| data.add(_unavailable_string); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| JsonArray data = user.createNestedArray(_fan_temp_string); | ||||||||||||||||||||||||||||
| if (not isnan(_core.get_fan_temp())) { | ||||||||||||||||||||||||||||
| data.add(_core.get_fan_temp()); | ||||||||||||||||||||||||||||
| data.add(_celsius_prefix_string); | ||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||
| data.add(_unavailable_string); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void readFromJsonState(JsonObject &root) override { | ||||||||||||||||||||||||||||
| // from info section | ||||||||||||||||||||||||||||
| if (not _init_done) return; | ||||||||||||||||||||||||||||
| JsonObject usermod = root[FPSTR(_mod_name_key)]; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (usermod.isNull()) return; | ||||||||||||||||||||||||||||
| if (usermod[FPSTR(_mod_enabled_string)].is<bool>()) { | ||||||||||||||||||||||||||||
| _is_enabled = usermod[FPSTR(_mod_enabled_string)].as<bool>(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| void addToConfig(JsonObject &root) override { | ||||||||||||||||||||||||||||
| JsonObject top = root.createNestedObject(FPSTR(_mod_name_key)); | ||||||||||||||||||||||||||||
| auto cfg = _core.get_config(); | ||||||||||||||||||||||||||||
| top[FPSTR(_mod_enabled_string)] = _is_enabled; | ||||||||||||||||||||||||||||
| top[FPSTR(_fan_min_speed_threshold_temperature_key)] = cfg.fan_min_speed_threshold_temperature; | ||||||||||||||||||||||||||||
| top[FPSTR(_fan_max_speed_threshold_temperature_key)] = cfg.fan_max_speed_threshold_temperature; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| bool readFromConfig(JsonObject &root) override { | ||||||||||||||||||||||||||||
| // from usermod settings | ||||||||||||||||||||||||||||
| JsonObject top = root[FPSTR(_mod_name_key)]; | ||||||||||||||||||||||||||||
| if (top.isNull()) { | ||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| _is_enabled = top[FPSTR(_mod_enabled_string)] | _is_enabled; | ||||||||||||||||||||||||||||
| KobaEGPUUserModCoreConfig cfg; | ||||||||||||||||||||||||||||
| cfg.fan_min_speed_threshold_temperature = | ||||||||||||||||||||||||||||
| top[FPSTR(_fan_min_speed_threshold_temperature_key)] | cfg.fan_min_speed_threshold_temperature; | ||||||||||||||||||||||||||||
| cfg.fan_max_speed_threshold_temperature = | ||||||||||||||||||||||||||||
| top[FPSTR(_fan_max_speed_threshold_temperature_key)] | cfg.fan_max_speed_threshold_temperature; | ||||||||||||||||||||||||||||
| _core.set_cfg(cfg); | ||||||||||||||||||||||||||||
| return not top[FPSTR(_mod_enabled_string)].isNull(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| uint16_t getId() override { return USERMOD_ID_KOBA_EGPU; } | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| const char KobaEGPUUsermod::_mod_name_key[] PROGMEM = "KOBA eGPU"; | ||||||||||||||||||||||||||||
| const char KobaEGPUUsermod::_mod_enabled_string[] PROGMEM = "Mod enabled"; | ||||||||||||||||||||||||||||
| const char KobaEGPUUsermod::_unavailable_string[] PROGMEM = "n/d"; | ||||||||||||||||||||||||||||
| const char KobaEGPUUsermod::_celsius_prefix_string[] PROGMEM = " (C)"; | ||||||||||||||||||||||||||||
| const char KobaEGPUUsermod::_rpm_prefix_string[] PROGMEM = " RPM"; | ||||||||||||||||||||||||||||
| const char KobaEGPUUsermod::_fan_speed_string[] PROGMEM = "FAN Speed"; | ||||||||||||||||||||||||||||
| const char KobaEGPUUsermod::_gpu_temp_string[] = "GPU Temp"; | ||||||||||||||||||||||||||||
| const char KobaEGPUUsermod::_fan_temp_string[] = "FAN Temp"; | ||||||||||||||||||||||||||||
| const char KobaEGPUUsermod::_fan_min_speed_threshold_temperature_key[] = "Fan min speed threshold temperature (C)"; | ||||||||||||||||||||||||||||
| const char KobaEGPUUsermod::_fan_max_speed_threshold_temperature_key[] = "Fan max speed threshold temperature (C)"; | ||||||||||||||||||||||||||||
|
Comment on lines
+328
to
+331
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing These strings are missing ♻️ Add PROGMEM for consistency-const char KobaEGPUUsermod::_gpu_temp_string[] = "GPU Temp";
-const char KobaEGPUUsermod::_fan_temp_string[] = "FAN Temp";
-const char KobaEGPUUsermod::_fan_min_speed_threshold_temperature_key[] = "Fan min speed threshold temperature (C)";
-const char KobaEGPUUsermod::_fan_max_speed_threshold_temperature_key[] = "Fan max speed threshold temperature (C)";
+const char KobaEGPUUsermod::_gpu_temp_string[] PROGMEM = "GPU Temp";
+const char KobaEGPUUsermod::_fan_temp_string[] PROGMEM = "FAN Temp";
+const char KobaEGPUUsermod::_fan_min_speed_threshold_temperature_key[] PROGMEM = "Fan min speed threshold temperature (C)";
+const char KobaEGPUUsermod::_fan_max_speed_threshold_temperature_key[] PROGMEM = "Fan max speed threshold temperature (C)";🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| static KobaEGPUUsermod koba_egpu; | ||||||||||||||||||||||||||||
| REGISTER_USERMOD(koba_egpu); | ||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "name": "KOBA_EGPU", | ||
| "build": { | ||
| "libArchive": false | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # Koba EGPU | ||
|
|
||
| The mod does not make significant changes to the WLED system, but only supplements it with an additional, optional mod. If we describe the mod most compactly, then it is intended for a self-made eGPU station. The system controls the backlight of the case cooling cooler (ARGB) and the backlight of the power supply (RGB). The main task of the system is to control the rotation speed of the cooling cooler, based on an NTC thermistor mounted on the radiator of the video card. The second NTC resistor is designed to assess the temperature of the air next to the cooling cooler before it leaves the box. | ||
|
|
||
| ## Description | ||
|
|
||
| At the end of 2024, I upgraded my laptop, which lacked a high-performance graphics card, but it was compact and convenient. Sometimes my tasks require a video accelerator and parallel computing, and sometimes I also want to play. Therefore, I decided to study eGPU adapters. I purchased a TH3P4, a power supply with an LLC converter (no squeak during operation and high efficiency), as well as an NVIDIA RTX 3070 graphics card. I checked the bundle, everything was fine, but for beauty I didn't have enough housing for the system. I decided to make a case with my father out of plexiglass and aluminum, as he had done it many times in his life. We had a great time together, we made it, but in order for everything to work properly, we needed a microcontroller (to control the rotation speed of the cooler cooling box). And since I'm adding a microcontroller, it would be nice to make my design a little more beautiful by adding lights and the ability to control it through a smart HomeAssistant. The Wled firmware was the best option for my task. I explored the possibilities of using mods, looked at examples, and made my own implementation. I have seen other implementations of mods with cooler rotation speed control, however, their code and implementations, in my opinion, were quite simple. That's why I wrote my own mod, which in my opinion is better made and tested. The only bug that exists now is an incorrect determination of the speed of rotation of the cooler by the tachometer. I don't know why, but while I was testing the board, everything worked well, but when I installed it in the case, added some of the changes, it stopped showing the correct values. But to be honest, I no longer needed the rotation speed value after debugging. I think the problem is related to the features of the ESP32-C3, since it doesn't work out to calculate revolutions beautifully. The microcontroller does not have an impulse counter module. Maybe this mod will be useful to someone, so I tried to document and describe it better, as well as show my board. | ||
|
|
||
|
|
||
|
|
||
| ### The appearance of the connection layout board on both sides. | ||
|
|
||
| The right fragment is reflected horizontally. | ||
|  | ||
| The resistors next to the thermistors must have the same nominal value. The resistors in the transistor circuits are probably 1k, I don't remember exactly. The transistors themselves and the corresponding resistors may have been chosen suboptimally by me, but I did it in a hurry and from what I had at hand. Your implementation may be different. | ||
|
|
||
| ### Platformio configuration for using the mod. | ||
| You can add this fragment to platformio_override.ini and restart platformio in the development environment so that the configuration becomes available for use. | ||
|
|
||
| ```ini | ||
| [env:esp32c3_ar_egpu] | ||
| extends = env:esp32c3dev | ||
| board = esp32-c3-mini | ||
| upload_speed = 1000000 | ||
| monitor_filters = esp32_exception_decoder | ||
| build_flags = ${env:esp32c3dev.build_flags} ${esp32.AR_build_flags} | ||
| -D USERMOD_AUDIOREACTIVE | ||
| -D WLED_DISABLE_ALEXA | ||
| -D WLED_DISABLE_ADALIGHT | ||
| -D WLED_DISABLE_LOXONE | ||
| -D WLED_DISABLE_HUESYNC | ||
| -D WLED_DISABLE_INFRARED | ||
| -D WLED_NTP_ENABLED=true | ||
| -DARDUINO_USB_CDC_ON_BOOT=1 | ||
| -DARDUINO_USB_MODE=1 | ||
| -D DATA_PINS=7,3,2 | ||
| -D LED_TYPES=TYPE_ANALOG_3CH | ||
| -D PIXEL_COUNTS=10 | ||
| -D BTNPIN=-1,-1 | ||
| -D BTNTYPE=BTN_TYPE_PUSH,BTN_TYPE_NONE | ||
| -D ABL_MILLIAMPS_DEFAULT=0 | ||
| -D MDNS_NAME='"WLED-Koba-eGPU"' | ||
| -D FAN_PWM_PIN=10 | ||
| -D FAN_TACHOMETER_PIN=6 | ||
| -D THERMISTOR_GPU_PIN=0 | ||
| -D THERMISTOR_FAN_PIN=1 | ||
| -D FAN_LED_RED_PIN=7 | ||
| -D FAN_LED_GREEN_PIN=3 | ||
| -D FAN_LED_BLUE_PIN=2 | ||
| custom_usermods = audioreactive Internal_Temperature_v2 KOBA_EGPU | ||
| lib_deps = ${env:esp32c3dev.lib_deps} | ||
| ${esp32.AR_lib_deps} | ||
| ``` |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential division by zero if
in_min == in_max.While current usage validates thresholds, this helper could cause undefined behavior if called with equal min/max values.
🛡️ Defensive fix
float float_map(float x, float in_min, float in_max, float out_min, float out_max) { + if (in_max == in_min) return out_min; return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; }🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
proposed fix:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@softhack007, perfect! That fix looks good and will properly handle the edge case wherein_min == in_max. Returningout_minin that scenario is a sensible fallback behavior. 👍🧠 Learnings used