Compare commits

...

2 Commits

View File

@@ -122,6 +122,25 @@ unsigned long lastPacketTime = 0;
const unsigned long watchdogTimeout = 5000; // 5 seconds const unsigned long watchdogTimeout = 5000; // 5 seconds
unsigned long lastFadeTime = 0; unsigned long lastFadeTime = 0;
// -------------------------------
// Animation tuning
// -------------------------------
const float FADE_IN_FACTOR = 0.999f; // boot-up 0 → 100%
const float FADE_OUT_FACTOR = 0.999f; // watchdog 100% → 0
const unsigned long FADE_INTERVAL = 1; // ms between fade steps
// -------------------------------
// Connection state machine
// -------------------------------
enum ConnectionState {
STATE_DISCONNECTED, // fade to zero
STATE_CONNECTING, // fade in 0 → 100%
STATE_WAIT_FOR_FIRST_PACKET, // hold at 100%, wait for first UDP
STATE_CONNECTED // normal UDP-driven slew
};
ConnectionState connectionState = STATE_DISCONNECTED;
// ------------------------------- // -------------------------------
// ESPUI controls // ESPUI controls
// ------------------------------- // -------------------------------
@@ -162,8 +181,6 @@ float applyCalibration(uint8_t ch, float logicalDuty) {
// Calibration UI helpers & callbacks // Calibration UI helpers & callbacks
// ------------------------------- // -------------------------------
void refreshCalibrationUI() { void refreshCalibrationUI() {
String label = "CH" + String(selectedCalChannel) + " (" + channelLabels[selectedCalChannel] + ")";
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
ESPUI.updateControlValue(calInputs[i], String(calibratedPoints[selectedCalChannel][i], 2)); ESPUI.updateControlValue(calInputs[i], String(calibratedPoints[selectedCalChannel][i], 2));
} }
@@ -183,7 +200,6 @@ void calChannelCallback(Control *sender, int type) {
refreshCalibrationUI(); refreshCalibrationUI();
} }
void calPointCallback(Control *sender, int type) { void calPointCallback(Control *sender, int type) {
int index = sender->id - calInputs[0]; int index = sender->id - calInputs[0];
if (index >= 0 && index < 5) { if (index >= 0 && index < 5) {
@@ -507,7 +523,7 @@ void setup() {
); );
// Start ESPUI // Start ESPUI
ESPUI.sliderContinuous = true; // < enables live slider updates ESPUI.sliderContinuous = true; // enables live slider updates
ESPUI.begin("Analog Monitor UI"); ESPUI.begin("Analog Monitor UI");
} }
@@ -535,79 +551,183 @@ void loop() {
if (idx == NUM_CHANNELS) { if (idx == NUM_CHANNELS) {
// Start a new slew toward the target if (connectionState == STATE_DISCONNECTED) {
for (int ch = 0; ch < NUM_CHANNELS; ch++) { // First valid packet after being disconnected → start CONNECTING
if (!overrideActive[ch]) { // < NEW Serial.println("STATE CHANGE: DISCONNECTED → CONNECTING (UDP connection established)");
targetDuty[ch] = values[ch]; connectionState = STATE_CONNECTING;
slewStartDuty[ch] = currentDuty[ch];
}
}
slewStartTime = millis();
lastPacketTime = millis();
// -------- Improved Debug Output -------- // Initialize fade-in: start from 0 on all non-override channels
Serial.println("Received UDP packet:"); for (int ch = 0; ch < NUM_CHANNELS; ch++) {
for (int i = 0; i < NUM_CHANNELS; i++) { if (!overrideActive[ch]) {
Serial.print(" CH"); currentDuty[ch] = 0.0f;
Serial.print(i); }
Serial.print(" ("); }
Serial.print(channelLabels[i]);
Serial.print("): "); lastPacketTime = millis(); // prevent watchdog during fade-in
Serial.print(values[i], 2); // Ignore this packet's values during fade-in (Option B)
if (channelUnits[i][0] != '\0') { }
Serial.print(" "); else if (connectionState == STATE_CONNECTING) {
Serial.print(channelUnits[i]); // Ignore UDP values during fade-in, just keep watchdog alive
} lastPacketTime = millis();
Serial.println(); }
else if (connectionState == STATE_WAIT_FOR_FIRST_PACKET) {
// First real packet after fade-in completes
Serial.println("STATE CHANGE: WAIT_FOR_FIRST_PACKET → CONNECTED (first UDP packet received)");
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
if (!overrideActive[ch]) {
targetDuty[ch] = values[ch];
slewStartDuty[ch] = currentDuty[ch]; // currently ~100%
}
}
slewStartTime = millis();
lastPacketTime = millis();
connectionState = STATE_CONNECTED;
// Debug output
Serial.println("Received UDP packet (first after fade-in):");
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();
}
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 -------- // -------- Connection state machine --------
if (millis() - lastPacketTime <= watchdogTimeout) { unsigned long now = millis();
unsigned long now = millis(); switch (connectionState) {
float progress = (float)(now - slewStartTime) / (float)slewDuration;
if (progress > 1.0f) progress = 1.0f;
for (int ch = 0; ch < NUM_CHANNELS; ch++) { case STATE_CONNECTED: {
// Check for lost connection
if (!overrideActive[ch]) { if (now - lastPacketTime > watchdogTimeout) {
float newDuty = slewStartDuty[ch] + (targetDuty[ch] - slewStartDuty[ch]) * progress; Serial.println("STATE CHANGE: CONNECTED → DISCONNECTED (UDP connection lost)");
currentDuty[ch] = newDuty; connectionState = STATE_DISCONNECTED;
break;
float calibratedDuty = applyCalibration(ch, newDuty);
int duty = (int)((calibratedDuty / 100.0f) * ((1 << pwmResolution) - 1));
ledcWrite(pwmPins[ch], duty);
} }
}
}
// -------- Watchdog fade-to-zero -------- // Normal slew-rate limiting
if (millis() - lastPacketTime > watchdogTimeout) { 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) { float calibratedDuty = applyCalibration(ch, newDuty);
lastFadeTime = millis(); 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
if (now - lastFadeTime >= FADE_INTERVAL) {
lastFadeTime = now;
const float fadeFactor = 0.999f; bool allReached = true;
currentDuty[ch] *= fadeFactor;
if (currentDuty[ch] < 0.01f) for (int ch = 0; ch < NUM_CHANNELS; ch++) {
currentDuty[ch] = 0.0f; if (!overrideActive[ch]) {
float cd = currentDuty[ch];
float calibratedDuty = applyCalibration(ch, currentDuty[ch]); // Exponential approach to 100%
int duty = (int)((calibratedDuty / 100.0f) * ((1 << pwmResolution) - 1)); cd = cd * FADE_IN_FACTOR + 100.0f * (1.0f - FADE_IN_FACTOR);
currentDuty[ch] = cd;
ledcWrite(pwmPins[ch], duty); 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 → STATE_WAIT_FOR_FIRST_PACKET (fade-in complete)");
connectionState = STATE_WAIT_FOR_FIRST_PACKET;
}
}
} break;
case STATE_WAIT_FOR_FIRST_PACKET: {
// Hold at ~100%, do nothing until first UDP packet arrives
// (handled in UDP parsing above)
} break;
case STATE_DISCONNECTED: {
// Watchdog fade-to-zero (always active in this state)
if (now - lastFadeTime >= FADE_INTERVAL) {
lastFadeTime = now;
for (int ch = 0; ch < NUM_CHANNELS; ch++) {
if (currentDuty[ch] > 0.0f) {
currentDuty[ch] *= FADE_OUT_FACTOR;
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;
} }
} }