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();
}