From 49daa09aa69c49d1f01eafb5935046f6242e1151 Mon Sep 17 00:00:00 2001 From: Robin Cerny Date: Mon, 19 Jan 2026 17:53:01 +0100 Subject: [PATCH] added first iteration of a calibration menu to the webui --- .../analog_system_monitor_arduino.ino | 285 ++++++++++++++---- 1 file changed, 232 insertions(+), 53 deletions(-) diff --git a/analog_system_monitor_arduino/analog_system_monitor_arduino.ino b/analog_system_monitor_arduino/analog_system_monitor_arduino.ino index 664e2b6..b4d114b 100644 --- a/analog_system_monitor_arduino/analog_system_monitor_arduino.ino +++ b/analog_system_monitor_arduino/analog_system_monitor_arduino.ino @@ -75,6 +75,20 @@ const char* channelUnits[NUM_CHANNELS] = { "" // Reserved }; +// ------------------------------- +// Channel labels ESPUI +// ------------------------------- +const char* channelDropdownLabels[NUM_CHANNELS] = { + "CH0 (CPU Load)", + "CH1 (CPU Temp)", + "CH2 (RAM Usage)", + "CH3 (GPU Load)", + "CH4 (GPU Temp)", + "CH5 (VRAM Usage)", + "CH6 (Reserved 6)", + "CH7 (Reserved 7)" +}; + // ------------------------------- // Calibration tables // ------------------------------- @@ -113,6 +127,20 @@ unsigned long lastFadeTime = 0; // ------------------------------- uint16_t portInput; // ID of the UDP port Number control +// --- Calibration UI Controls --- +uint16_t calChannelDropdown; +uint16_t calLabel; +uint16_t calInputs[5]; +uint16_t calTestValueInput; +uint16_t calTestButton; +uint16_t calSaveButton; +uint16_t calReleaseButton; +uint16_t calOverrideSwitch; + +uint8_t selectedCalChannel = 0; +bool overrideActive[NUM_CHANNELS] = {false}; + + // ------------------------------- // Calibration interpolation // ------------------------------- @@ -133,6 +161,112 @@ float applyCalibration(uint8_t ch, float logicalDuty) { return calibratedPoints[ch][4]; } +// ------------------------------- +// Calibration UI helpers & callbacks +// ------------------------------- +void refreshCalibrationUI() { + String label = "CH" + String(selectedCalChannel) + " (" + channelLabels[selectedCalChannel] + ")"; + ESPUI.updateControlValue(calLabel, label); + + for (int i = 0; i < 5; i++) { + ESPUI.updateControlValue(calInputs[i], String(calibratedPoints[selectedCalChannel][i], 2)); + } +} + +void calChannelCallback(Control *sender, int type) { + // Turn override OFF when switching channels + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + overrideActive[ch] = false; + } + ESPUI.updateControlValue(calOverrideSwitch, "0"); + + selectedCalChannel = sender->value.toInt(); + Serial.print("Calibration channel changed to "); + Serial.println(selectedCalChannel); + + refreshCalibrationUI(); +} + + +void calPointCallback(Control *sender, int type) { + int index = sender->id - calInputs[0]; + if (index >= 0 && index < 5) { + float val = sender->value.toFloat(); + calibratedPoints[selectedCalChannel][index] = val; + Serial.printf("Cal[%d][%d] = %.2f\n", selectedCalChannel, index, val); + } +} + +void calTestCallback(Control *sender, int type) { + if (!overrideActive[selectedCalChannel]) return; + + float logical = sender->value.toFloat(); // 0–100 integer + + if (logical < 0) logical = 0; + if (logical > 100) logical = 100; + + float calibrated = applyCalibration(selectedCalChannel, logical); + int duty = (int)((calibrated / 100.0f) * ((1 << pwmResolution) - 1)); + + ledcWrite(pwmPins[selectedCalChannel], duty); + + Serial.printf("Override update CH%d: logical=%.2f calibrated=%.2f duty=%d\n", + selectedCalChannel, logical, calibrated, duty); +} + +void calSaveCallback(Control *sender, int type) { + Serial.printf("Saving calibration for CH%d...\n", selectedCalChannel); + + for (int i = 0; i < 5; i++) { + String key = "cal_" + String(selectedCalChannel) + "_" + String(i); + prefs.putFloat(key.c_str(), calibratedPoints[selectedCalChannel][i]); + } + + Serial.println("Calibration saved."); +} + +void calReleaseOverrideCallback(Control *sender, int type) { + Serial.println("Manual override released for all channels."); + + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + overrideActive[ch] = false; + } +} + +void calOverrideSwitchCallback(Control *sender, int type) { + bool enabled = sender->value.toInt() == 1; + + if (enabled) { + Serial.println("Override enabled."); + + // Enable override only for the selected channel + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + overrideActive[ch] = (ch == selectedCalChannel); + } + + // Immediately apply the test value + float logical = ESPUI.getControl(calTestValueInput)->value.toFloat(); + if (logical < 0) logical = 0; + if (logical > 100) logical = 100; + + float calibrated = applyCalibration(selectedCalChannel, logical); + int duty = (int)((calibrated / 100.0f) * ((1 << pwmResolution) - 1)); + ledcWrite(pwmPins[selectedCalChannel], duty); + + Serial.printf("Override driving CH%d: logical=%.2f calibrated=%.2f duty=%d\n", + selectedCalChannel, logical, calibrated, duty); + + } else { + Serial.println("Override disabled. Returning to UDP control."); + + // Disable all overrides + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + overrideActive[ch] = false; + } + } +} + + // ------------------------------- // Setup // ------------------------------- @@ -176,6 +310,15 @@ void setup() { prefs.begin("analogmon", false); udpPort = prefs.getInt("udpPort", listenPort); + // Load calibration from flash (if present) + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + for (int i = 0; i < 5; i++) { + String key = "cal_" + String(ch) + "_" + String(i); + float val = prefs.getFloat(key.c_str(), calibratedPoints[ch][i]); + calibratedPoints[ch][i] = val; + } + } + // Start UDP with runtime port udp.begin(udpPort); Serial.print("Listening on UDP port "); @@ -280,71 +423,103 @@ void setup() { tabLighting ); + // ------------------------------- + // Calibration tab UI + // ------------------------------- + + // Channel selector + calChannelDropdown = ESPUI.addControl( + ControlType::Select, + "Selected Channel", + "0", + ControlColor::Peterriver, + tabCalibration, + calChannelCallback + ); + + // Add options 0–7 + for (int i = 0; i < NUM_CHANNELS; i++) { + char value[8]; + snprintf(value, sizeof(value), "%d", i); + + ESPUI.addControl( + ControlType::Option, + channelDropdownLabels[i], // static label + value, // static-ish value (OK) + ControlColor::None, + calChannelDropdown + ); + } + ESPUI.addControl( ControlType::Separator, - "CH0 (CPU Load)", + "", "", ControlColor::None, tabCalibration ); + // Calibration inputs + const char* calNames[5] = {"0%", "25%", "50%", "75%", "100%"}; + + for (int i = 0; i < 5; i++) { + calInputs[i] = ESPUI.addControl( + ControlType::Number, + calNames[i], + String(calibratedPoints[0][i], 2), + ControlColor::Wetasphalt, + tabCalibration, + calPointCallback + ); + } + ESPUI.addControl( ControlType::Separator, - "CH1 (CPU Temp)", + "", "", ControlColor::None, tabCalibration ); + // Test value input + calTestValueInput = ESPUI.addControl( + ControlType::Slider, + "Test Value", + "50", + ControlColor::Carrot, + tabCalibration, + calTestCallback + ); + + calOverrideSwitch = ESPUI.addControl( + ControlType::Switcher, + "Override", + "0", // default OFF + ControlColor::Alizarin, + tabCalibration, + calOverrideSwitchCallback + ); + ESPUI.addControl( ControlType::Separator, - "CH2 (RAM Usage)", + "", "", ControlColor::None, tabCalibration ); - ESPUI.addControl( - ControlType::Separator, - "CH3 (GPU Load)", - "", - ControlColor::None, - tabCalibration - ); - - ESPUI.addControl( - ControlType::Separator, - "CH4 (GPU Temp)", - "", - ControlColor::None, - tabCalibration - ); - - ESPUI.addControl( - ControlType::Separator, - "CH5 (VRAM Usage)", - "", - ControlColor::None, - tabCalibration - ); - - ESPUI.addControl( - ControlType::Separator, - "CH6 (Reserved 6)", - "", - ControlColor::None, - tabCalibration - ); - - ESPUI.addControl( - ControlType::Separator, - "CH7 (Reserved 7)", - "", - ControlColor::None, - tabCalibration + // Save button + calSaveButton = ESPUI.addControl( + ControlType::Button, + "Save Calibration", + "Save", + ControlColor::Emerald, + tabCalibration, + calSaveCallback ); // Start ESPUI + ESPUI.sliderContinuous = true; // <‑‑ enables live slider updates ESPUI.begin("Analog Monitor UI"); } @@ -374,8 +549,10 @@ void loop() { // Start a new slew toward the target for (int ch = 0; ch < NUM_CHANNELS; ch++) { - targetDuty[ch] = values[ch]; - slewStartDuty[ch] = currentDuty[ch]; + if (!overrideActive[ch]) { // <‑‑ NEW + targetDuty[ch] = values[ch]; + slewStartDuty[ch] = currentDuty[ch]; + } } slewStartTime = millis(); lastPacketTime = millis(); @@ -402,19 +579,21 @@ void loop() { // -------- Slew-rate limiting -------- if (millis() - lastPacketTime <= watchdogTimeout) { - unsigned long now = millis(); - float progress = (float)(now - slewStartTime) / (float)slewDuration; - if (progress > 1.0f) progress = 1.0f; + unsigned long now = millis(); + float progress = (float)(now - slewStartTime) / (float)slewDuration; + if (progress > 1.0f) progress = 1.0f; - for (int ch = 0; ch < NUM_CHANNELS; ch++) { - float newDuty = slewStartDuty[ch] + (targetDuty[ch] - slewStartDuty[ch]) * progress; - currentDuty[ch] = newDuty; + for (int ch = 0; ch < NUM_CHANNELS; ch++) { - float calibratedDuty = applyCalibration(ch, newDuty); - int duty = (int)((calibratedDuty / 100.0f) * ((1 << pwmResolution) - 1)); + if (!overrideActive[ch]) { + float newDuty = slewStartDuty[ch] + (targetDuty[ch] - slewStartDuty[ch]) * progress; + currentDuty[ch] = newDuty; - ledcWrite(pwmPins[ch], duty); - } + float calibratedDuty = applyCalibration(ch, newDuty); + int duty = (int)((calibratedDuty / 100.0f) * ((1 << pwmResolution) - 1)); + ledcWrite(pwmPins[ch], duty); + } + } } // -------- Watchdog fade-to-zero --------