From 60f12e22e41c76fcd57b14049f3dca0d3dc3cb12 Mon Sep 17 00:00:00 2001 From: Robin Cerny Date: Tue, 20 Jan 2026 08:14:52 +0100 Subject: [PATCH] added fade in if a new connection is established --- .../analog_system_monitor_arduino.ino | 208 +++++++++++++----- 1 file changed, 147 insertions(+), 61 deletions(-) diff --git a/analog_system_monitor_arduino/analog_system_monitor_arduino.ino b/analog_system_monitor_arduino/analog_system_monitor_arduino.ino index 835ce90..530d11d 100644 --- a/analog_system_monitor_arduino/analog_system_monitor_arduino.ino +++ b/analog_system_monitor_arduino/analog_system_monitor_arduino.ino @@ -122,6 +122,17 @@ unsigned long lastPacketTime = 0; const unsigned long watchdogTimeout = 5000; // 5 seconds unsigned long lastFadeTime = 0; +// ------------------------------- +// Connection state machine +// ------------------------------- +enum ConnectionState { + STATE_DISCONNECTED, // fade to zero + STATE_CONNECTING, // fade in 0 → 100% + STATE_CONNECTED // normal UDP-driven slew +}; + +ConnectionState connectionState = STATE_DISCONNECTED; + // ------------------------------- // ESPUI controls // ------------------------------- @@ -162,8 +173,6 @@ float applyCalibration(uint8_t ch, float logicalDuty) { // Calibration UI helpers & callbacks // ------------------------------- void refreshCalibrationUI() { - String label = "CH" + String(selectedCalChannel) + " (" + channelLabels[selectedCalChannel] + ")"; - for (int i = 0; i < 5; i++) { ESPUI.updateControlValue(calInputs[i], String(calibratedPoints[selectedCalChannel][i], 2)); } @@ -183,7 +192,6 @@ void calChannelCallback(Control *sender, int type) { refreshCalibrationUI(); } - void calPointCallback(Control *sender, int type) { int index = sender->id - calInputs[0]; if (index >= 0 && index < 5) { @@ -507,7 +515,7 @@ void setup() { ); // Start ESPUI - ESPUI.sliderContinuous = true; // <‑‑ enables live slider updates + ESPUI.sliderContinuous = true; // enables live slider updates ESPUI.begin("Analog Monitor UI"); } @@ -535,79 +543,157 @@ void loop() { if (idx == NUM_CHANNELS) { - // Start a new slew toward the target - for (int ch = 0; ch < NUM_CHANNELS; ch++) { - if (!overrideActive[ch]) { // <‑‑ NEW - targetDuty[ch] = values[ch]; - slewStartDuty[ch] = currentDuty[ch]; - } - } - slewStartTime = millis(); - lastPacketTime = millis(); + // First valid packet after being disconnected → start CONNECTING + if (connectionState == STATE_DISCONNECTED) { + Serial.println("STATE CHANGE: DISCONNECTED → CONNECTING (UDP connection established)"); + connectionState = STATE_CONNECTING; - // -------- Improved Debug Output -------- - Serial.println("Received UDP packet:"); - for (int i = 0; i < NUM_CHANNELS; i++) { - Serial.print(" CH"); - Serial.print(i); - Serial.print(" ("); - Serial.print(channelLabels[i]); - Serial.print("): "); - Serial.print(values[i], 2); - if (channelUnits[i][0] != '\0') { - Serial.print(" "); - Serial.print(channelUnits[i]); - } - Serial.println(); + // Initialize fade-in: start from 0 on all non-override channels + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + if (!overrideActive[ch]) { + currentDuty[ch] = 0.0f; + } + } + + lastPacketTime = millis(); // prevent watchdog during fade-in + // Ignore this packet's values during fade-in (Option B) + } + else if (connectionState == STATE_CONNECTING) { + // Ignore UDP values during fade-in, just keep watchdog alive + lastPacketTime = millis(); + } + else if (connectionState == STATE_CONNECTED) { + // Normal UDP-driven update + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + if (!overrideActive[ch]) { + targetDuty[ch] = values[ch]; + slewStartDuty[ch] = currentDuty[ch]; + } + } + slewStartTime = millis(); + lastPacketTime = millis(); + + // Debug output + Serial.println("Received UDP packet:"); + for (int i = 0; i < NUM_CHANNELS; i++) { + Serial.print(" CH"); + Serial.print(i); + Serial.print(" ("); + Serial.print(channelLabels[i]); + Serial.print("): "); + Serial.print(values[i], 2); + if (channelUnits[i][0] != '\0') { + Serial.print(" "); + Serial.print(channelUnits[i]); + } + Serial.println(); + } + Serial.println(); } - Serial.println(); } } - // -------- Slew-rate limiting -------- - if (millis() - lastPacketTime <= watchdogTimeout) { + // -------- Connection state machine -------- + unsigned long now = millis(); - unsigned long now = millis(); - float progress = (float)(now - slewStartTime) / (float)slewDuration; - if (progress > 1.0f) progress = 1.0f; + switch (connectionState) { - for (int ch = 0; ch < NUM_CHANNELS; ch++) { - - if (!overrideActive[ch]) { - float newDuty = slewStartDuty[ch] + (targetDuty[ch] - slewStartDuty[ch]) * progress; - currentDuty[ch] = newDuty; - - float calibratedDuty = applyCalibration(ch, newDuty); - int duty = (int)((calibratedDuty / 100.0f) * ((1 << pwmResolution) - 1)); - ledcWrite(pwmPins[ch], duty); + case STATE_CONNECTED: { + // Check for lost connection + if (now - lastPacketTime > watchdogTimeout) { + Serial.println("STATE CHANGE: CONNECTED → DISCONNECTED (UDP connection lost)"); + connectionState = STATE_DISCONNECTED; + break; } - } - } - // -------- Watchdog fade-to-zero -------- - if (millis() - lastPacketTime > watchdogTimeout) { + // Normal slew-rate limiting + float progress = (float)(now - slewStartTime) / (float)slewDuration; + if (progress > 1.0f) progress = 1.0f; - const unsigned long fadeInterval = 1; + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + if (!overrideActive[ch]) { + float newDuty = slewStartDuty[ch] + (targetDuty[ch] - slewStartDuty[ch]) * progress; + currentDuty[ch] = newDuty; - if (millis() - lastFadeTime >= fadeInterval) { - lastFadeTime = millis(); + float calibratedDuty = applyCalibration(ch, newDuty); + int duty = (int)((calibratedDuty / 100.0f) * ((1 << pwmResolution) - 1)); + ledcWrite(pwmPins[ch], duty); + } + } + } break; - for (int ch = 0; ch < NUM_CHANNELS; ch++) { + case STATE_CONNECTING: { + // If we lose packets even while connecting, fall back to DISCONNECTED + if (now - lastPacketTime > watchdogTimeout) { + Serial.println("STATE CHANGE: CONNECTING → DISCONNECTED (no packets during fade-in)"); + connectionState = STATE_DISCONNECTED; + break; + } - if (currentDuty[ch] > 0.0f) { + // Fade-in animation: 0 → 100% on all non-override channels + const unsigned long fadeInterval = 1; + const float fadeInFactor = 0.999f; // fast at start, slow near 100% - const float fadeFactor = 0.999f; - currentDuty[ch] *= fadeFactor; + if (now - lastFadeTime >= fadeInterval) { + lastFadeTime = now; - if (currentDuty[ch] < 0.01f) - currentDuty[ch] = 0.0f; + bool allReached = true; - float calibratedDuty = applyCalibration(ch, currentDuty[ch]); - int duty = (int)((calibratedDuty / 100.0f) * ((1 << pwmResolution) - 1)); + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + if (!overrideActive[ch]) { + float cd = currentDuty[ch]; - ledcWrite(pwmPins[ch], duty); - } - } - } + // Exponential approach to 100% + cd = cd * fadeInFactor + 100.0f * (1.0f - fadeInFactor); + currentDuty[ch] = cd; + + float calibratedDuty = applyCalibration(ch, cd); + int duty = (int)((calibratedDuty / 100.0f) * ((1 << pwmResolution) - 1)); + ledcWrite(pwmPins[ch], duty); + + // Check if we're close enough to 100% + if (cd < 99.0f) { + allReached = false; + } + } + } + + if (allReached) { + Serial.println("STATE CHANGE: CONNECTING → CONNECTED (fade-in complete)"); + // Initialize slew baseline from currentDuty + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + slewStartDuty[ch] = currentDuty[ch]; + } + slewStartTime = millis(); + connectionState = STATE_CONNECTED; + } + } + } break; + + case STATE_DISCONNECTED: { + // Watchdog fade-to-zero (always active in this state) + const unsigned long fadeInterval = 1; + + if (now - lastFadeTime >= fadeInterval) { + lastFadeTime = now; + + for (int ch = 0; ch < NUM_CHANNELS; ch++) { + + if (currentDuty[ch] > 0.0f) { + + const float fadeFactor = 0.999f; + currentDuty[ch] *= fadeFactor; + + if (currentDuty[ch] < 0.01f) + currentDuty[ch] = 0.0f; + + float calibratedDuty = applyCalibration(ch, currentDuty[ch]); + int duty = (int)((calibratedDuty / 100.0f) * ((1 << pwmResolution) - 1)); + + ledcWrite(pwmPins[ch], duty); + } + } + } + } break; } }