Duration from CloudSchedule variable not working!

Time zone is correct, variable types and dashboard widgets are correct.

Stepper motor start at the scheduled time, however it doesn't stop at the duration set in the Scheduler widget (dashboard).

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include "thingProperties.h"

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Temperature Sensor Pins
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// Stepper Motor Pins for DRV8825 Driver
#define STEPPER_DIR 6
#define STEPPER_STEP 5

// Pump and Liquid Sensor Pins
#define PUMP_IN1 8
#define PUMP_IN2 9
#define LIQUID_SENSOR_PIN 7

// Global variables
const long interval = 5000;
const long PUMP_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes in milliseconds
unsigned long previousMillis = 0;
int state = 0;
int last_stepper_run_hour = -1; // Tracks the last hour the stepper ran
unsigned long pump_start_time = 0; // Tracks when the pump was turned on
bool stepper_running = false;
unsigned long stepper_start_time = 0;
unsigned long daily_pump_runtime = 0; // Total daily runtime in milliseconds
unsigned long pump_session_start = 0; // When current pump session started
bool pump_was_on = false; // Track previous pump state
int last_reset_day = -1; // Track day for daily reset
unsigned long schedule_start_time = 0;
unsigned long schedule_duration = 0;

void onTempAlertChange() {
  // This function is automatically called when tempAlert changes.
  // Add any logic you need here.
}

void onStepperScheduleChange() {
  // Schedule handling is in main loop
}

void onStepperOnChange() {
  // Handle dashboard switch control
  if (stepperOn && !stepper_running) {
    stepper_running = true;
    Serial.println("Stepper started manually");
  } else if (!stepperOn && stepper_running) {
    stepper_running = false;
    Serial.println("Stepper stopped manually");
  }
}

void onPumpOnChange() {
  // Handle pump hardware control
  if (pumpOn) {
    if (!pump_was_on) {
      pump_session_start = millis();
      pump_start_time = millis();
    }
    digitalWrite(PUMP_IN1, HIGH);
    digitalWrite(PUMP_IN2, LOW);
    Serial.println("Pump ON");
  } else {
    if (pump_was_on) {
      daily_pump_runtime += millis() - pump_session_start;
      pumpDailyRuntime = daily_pump_runtime / 60000;
    }
    digitalWrite(PUMP_IN1, LOW);
    digitalWrite(PUMP_IN2, LOW);
    Serial.println("Pump OFF");
  }
}

void onManualPumpControlChange() {
  Serial.print("Manual pump control: ");
  Serial.println(manualPumpControl ? "ENABLED" : "DISABLED");
}


void setup(void) {
  Serial.begin(9600);
  
  // Setup for OLED display
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;);
  }
  display.display();
  delay(2000);
  display.clearDisplay();
  
  // Setup for temperature sensor
  sensors.begin();

  // Setup for Stepper Motor
  pinMode(STEPPER_DIR, OUTPUT);
  pinMode(STEPPER_STEP, OUTPUT);
  digitalWrite(STEPPER_DIR, LOW); // Set default direction

  // Setup for Pump and Liquid Sensor
  pinMode(PUMP_IN1, OUTPUT);
  pinMode(PUMP_IN2, OUTPUT);
  pinMode(LIQUID_SENSOR_PIN, INPUT);

    // Ensure pump starts OFF
  digitalWrite(PUMP_IN1, LOW);
  digitalWrite(PUMP_IN2, LOW);
  
  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}

void loop(void) {


  unsigned long epochTime = ArduinoCloud.getLocalTime();


   // Update current time for dashboard
   currentTime = epochTime;
  
 // currentTime = ArduinoCloud.getLocalTime();
  
  ArduinoCloud.update();
  
  // Check if schedule is active and control stepper
  if (stepperSchedule.isActive()) {
    if (!stepper_running) {
      stepperOn = true;
      stepper_running = true;
      Serial.println("Stepper started by schedule");
    }
  } else if (stepper_running && !stepperOn) {
    // Schedule ended and manual switch is off
    stepper_running = false;
    Serial.println("Stepper stopped - schedule ended");
  }
  

  // Read temperature
  unsigned long currentMillis = millis();

  // Read temperature
  sensors.requestTemperatures();
  tempC = sensors.getTempCByIndex(0);
  
  // Convert temperature to Fahrenheit for display
  tempF = sensors.getTempFByIndex(0);

  // Check temperature and set cloud variable
  if (tempC < 24 || tempC > 26) {
    tempAlert = true;
  } else {
    tempAlert = false;
  }

    // Check schedule duration timeout
  if (stepper_running && schedule_duration > 0) {
    if (millis() - schedule_start_time >= schedule_duration) {
      stepperOn = false;
      stepper_running = false;
      schedule_duration = 0; // Reset
      Serial.println("Stepper stopped - schedule duration ended");
    }
  }
  
  // Run stepper motor when stepperOn is true (from dashboard or schedule)
      if (stepper_running) {
        // Step the motor (non-blocking)
        static unsigned long lastStepTime = 0;
        if (currentMillis - lastStepTime >= 2) { 
        // 2ms = ~500 steps/sec
        digitalWrite(STEPPER_STEP, HIGH);
        delayMicroseconds(50);
        digitalWrite(STEPPER_STEP, LOW);
        lastStepTime = currentMillis;
        }
   }

  // Status display on OLED (add to existing display logic)
 if (stepper_running) {
    display.setCursor(0, 56);
    display.setTextSize(1);
    display.println("Stepper ON");
  } 

  // Pump control logic based on liquid sensor and safety timer
  int liquidLevel = digitalRead(LIQUID_SENSOR_PIN);
  // Daily reset logic
  int currentDay = (epochTime / 86400) % 365; // Day of year
  if (currentDay != last_reset_day) {
    daily_pump_runtime = 0; // Reset daily counter
     pumpDailyRuntime = 0; // Reset cloud variable
    last_reset_day = currentDay;
  }


  // Automatic pump control (only when manual control is OFF)
  if (!manualPumpControl) {
    if (liquidLevel == LOW && !pumpOn) {
      // Water level low - turn on pump
      pumpOn = true;
    } else if (liquidLevel == HIGH && pumpOn) {
      // Water level OK - turn off pump
      pumpOn = false;
    }
  }
 
  
  // Safety timeout (applies to both manual and auto modes)
  if (pumpOn && (currentMillis - pump_start_time >= PUMP_TIMEOUT_MS)) {
    if (pump_was_on) {
      daily_pump_runtime += millis() - pump_session_start;
      pumpDailyRuntime = daily_pump_runtime / 60000;
    }
    pumpOn = false;
    Serial.println("Pump safety timeout - turned OFF");
  }
      
  
    // Update pump state tracking
   pump_was_on = pumpOn;
  
    
  // Display and Serial Monitor updates
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    state = 1 - state;
  }

  display.clearDisplay();

  if (state == 0) {
    String celsiusString = String(tempC, 1) + " C";
    String fahrenheitString = String(tempF, 1) + " F";
    display.setTextSize(2);
    display.setTextColor(WHITE);
    int16_t x1, y1; uint16_t w, h;
    display.getTextBounds(celsiusString, 0, 0, &x1, &y1, &w, &h);
    int16_t x = (SCREEN_WIDTH - w) / 2;
    display.setCursor(x, 0);
    display.println(celsiusString);
    display.getTextBounds(fahrenheitString, 0, 0, &x1, &y1, &w, &h);
    x = (SCREEN_WIDTH - w) / 2;
    display.setCursor(x, 30);
    display.println(fahrenheitString);
    Serial.print("Temp: ");
    Serial.print(tempC);
    Serial.println(" C");
  } else {
    display.setCursor(0, 0);
    if (tempAlert) {
      display.setTextSize(2); display.setTextColor(WHITE);
      String alertTitle = "TEMP ALERT";
      int16_t x1, y1; uint16_t w, h;
      display.getTextBounds(alertTitle, 0, 0, &x1, &y1, &w, &h);
      int16_t x = (SCREEN_WIDTH - w) / 2;
      display.setCursor(x, 0);
      display.println(alertTitle);
      display.setTextSize(1);
      String outOfRangeText = "OUT OF RANGE";
      display.getTextBounds(outOfRangeText, 0, 0, &x1, &y1, &w, &h);
      x = (SCREEN_WIDTH - w) / 2;
      display.setCursor(x, 30);
      display.println(outOfRangeText);
    } else {
      display.setTextSize(2); display.setTextColor(WHITE);
      String happyFish = "Happy fish";
      int16_t x1, y1; uint16_t w, h;
      display.getTextBounds(happyFish, 0, 0, &x1, &y1, &w, &h);
      int16_t x = (SCREEN_WIDTH - w) / 2;
      display.setCursor(x, 0);
      display.println(happyFish);
      String happyWife = "Happy wife";
      display.getTextBounds(happyWife, 0, 0, &x1, &y1, &w, &h);
      x = (SCREEN_WIDTH - w) / 2;
      display.setCursor(x, 30);
      display.println(happyWife);
    }
  }
  display.display();
}

I haven’t read all the code in details but this type of else is always suspicious

as you test two conditions.

I would be tempted to say that if the schedule is no longer active then you need to stop. What is setting stepperOn to false?

Thanks for your quick response. Let me first state that Im a newbie in the field of coding. I want to be able to control the stepper motor manually but also be able to have a schedule. So Im doing a switch in the dashboard for manual control and using CloudScheduler variable and the Scheduler widget to take care of the schedule. So Im going to say that StepperOn is set to false thru the switch.

If stepperOn is a cloud variable controlled by the dashboard (a virtual switch to for manual mode) then why do you force it to true in

And what will reset it when the schedule is terminated?

you might benefit from studying state machines. Here is a small introduction to the topic: Yet another Finite State Machine introduction