#include #include "UI.h" // Local UI control static uint16_t portInput; // ------------------------------- // Calibration UI helpers & callbacks // ------------------------------- void refreshCalibrationUI() { 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 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; } } } void lightingSaveCallback(Control *sender, int type) { if (type != B_UP) return; // avoid double-trigger prefs.putUChar("light_hue", lightingHue); prefs.putUChar("light_sat", lightingSaturation); prefs.putUChar("light_bright", lightingBrightnessSaved); Serial.printf("Lighting saved (0–255): H=%d S=%d B=%d\n", lightingHue, lightingSaturation, lightingBrightness); } static int toSlider(uint8_t v) { return (int)((v / 255.0f) * 100.0f); } static uint8_t fromSlider(int v) { return (uint8_t)((v / 100.0f) * 255.0f); } // ------------------------------- // UI init // ------------------------------- void uiInit(uint16_t& tabSettings, uint16_t& tabLighting, uint16_t& tabCalibration) { // Create tabs tabSettings = ESPUI.addControl(ControlType::Tab, "Settings", "Settings"); tabLighting = ESPUI.addControl(ControlType::Tab, "Lighting", "Lighting"); tabCalibration= ESPUI.addControl(ControlType::Tab, "Calibration", "Calibration"); // Restart button callback auto restartCallback = [](Control *sender, int type) { ESP.restart(); }; // Port input callback auto portInputCallback = [](Control *sender, int type) { Serial.print("Port input changed to: "); Serial.println(sender->value); }; // Save & Apply callback auto savePortCallback = [](Control *sender, int type) { if (type != B_UP) return; // Prevent double-trigger Control* c = ESPUI.getControl(portInput); int newPort = c->value.toInt(); if (newPort < 1024 || newPort > 65535) { Serial.println("Invalid port (1024–65535)"); return; } prefs.putInt("udpPort", newPort); udpPort = newPort; udp.stop(); udp.begin(udpPort); Serial.print("New UDP port applied: "); Serial.println(udpPort); ESPUI.updateControlValue(portInput, String(newPort)); }; // ------------------------------------------- // Connection Status section // ------------------------------------------- ESPUI.addControl( ControlType::Separator, "Connection Status", "", ControlColor::None, tabSettings ); // Live-updating connection status label connectionStatusLabel = ESPUI.addControl( ControlType::Label, "Status", "Disconnected", ControlColor::Wetasphalt, tabSettings ); // ------------------------------------------- // UDP Telemetry Connection Settings section // ------------------------------------------- ESPUI.addControl( ControlType::Separator, "UDP Telemetry Connection Settings", "", ControlColor::None, tabSettings ); // UDP Port Number input portInput = ESPUI.addControl( ControlType::Number, "UDP Port", String(udpPort), ControlColor::Peterriver, tabSettings, portInputCallback ); // Save & Apply button ESPUI.addControl( ControlType::Button, "UDP Port", "Save & Apply", ControlColor::Emerald, tabSettings, savePortCallback ); // Existing separator (leave as-is) ESPUI.addControl( ControlType::Separator, "", "", ControlColor::None, tabSettings ); // Restart button (unchanged) ESPUI.addControl( ControlType::Button, "Restart ESP32", "Restart", ControlColor::Alizarin, tabSettings, restartCallback ); // ------------------------------- // Lighting Controls // ------------------------------- // Hue slider (0–360) ESPUI.addControl( ControlType::Slider, "Hue", String(toSlider(lightingHue)), ControlColor::Sunflower, tabLighting, [](Control *sender, int type) { int sliderVal = sender->value.toInt(); // 0–100 lightingHue = fromSlider(sliderVal); // convert to 0–255 Serial.printf("Lighting Hue changed (RAM only): %d\n", lightingHue); lightingFading = false; // cancel fade if user moves slider applyLighting(); } ); // Saturation slider (0–100) ESPUI.addControl( ControlType::Slider, "Saturation", String(toSlider(lightingSaturation)), ControlColor::Carrot, tabLighting, [](Control *sender, int type) { int sliderVal = sender->value.toInt(); // 0–100 lightingSaturation = fromSlider(sliderVal); Serial.printf("Lighting Saturation updated (RAM only): %d\n", lightingSaturation); lightingFading = false; // cancel fade if user moves slider applyLighting(); } ); // Brightness slider (0–100) ESPUI.addControl( ControlType::Slider, "Brightness", String(toSlider(lightingBrightness)), ControlColor::Emerald, tabLighting, [](Control *sender, int type) { int sliderVal = sender->value.toInt(); // 0–100 lightingBrightnessSaved = fromSlider(sliderVal); lightingBrightness = lightingBrightnessSaved; Serial.printf("Lighting Brightness updated (RAM only): %d\n", lightingBrightness); lightingFading = false; // cancel fade if user moves slider applyLighting(); } ); ESPUI.addControl( ControlType::Button, "Save Lighting Settings", "Save", ControlColor::Emerald, tabLighting, lightingSaveCallback ); // ------------------------------- // 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, "", "", 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, "", "", 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, "", "", ControlColor::None, tabCalibration ); // Save button calSaveButton = ESPUI.addControl( ControlType::Button, "Save Calibration", "Save", ControlColor::Emerald, tabCalibration, calSaveCallback ); }