/*
The Dryinator - Menu System v2
Platform: Arduino C++ (ESP32-compatible)
Display: 16x2 I2C LCD
Input: ESP32Encoder rotary encoder + button
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ESP32Encoder.h>
#include <DHTesp.h>
#include "driver/ledc.h"
// LCD setup
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Rotary encoder pins
#define ENCODER_CLK 32
#define ENCODER_DT 33
#define ENCODER_SW 25
#define ENCODER_REVERSED true
ESP32Encoder encoder;
long lastPosition = -999;
int menuIndex = 0;
long getEncoderPosition() {
long count = encoder.getCount() / 4;
return ENCODER_REVERSED ? -count : count;
}
// Button debounce
unsigned long lastPress = 0;
const unsigned long debounceDelay = 300;
// Temperature Sensor
DHTesp dht;
#define DHTPIN 15
// Heaters
#define HEATER1_PIN 4
#define HEATER2_PIN 16
#define FAN_PWM_PIN 14
const uint8_t fanDuty = 191;
#define FAN_PWM_CHANNEL 0
#define FAN_PWM_FREQ 25000
#define FAN_PWM_RES 8
// Menu items
const int menuLength = 4;
String menuItems[menuLength] = {
"Start Cycle",
"Set Temp",
"Set Timer",
"Debug Info"
};
// App states
enum AppState { MENU, SET_TEMP, SET_TIMER, CYCLE_RUNNING };
AppState currentState = MENU;
// Temperature control
int targetTemp = 55; // default PETG temp
int tempMin = 30;
int tempMax = 80;
float currentTemp = NAN;
float humidity = NAN;
// Timer control (in minutes)
int targetTime = 240; // default 4 hours
int timeMin = 15;
int timeMax = 720;
unsigned long cycleStartTime = 0;
bool cycleComplete = false;
bool shutdownRequested = false;
unsigned long buttonPressTime = 0;
const unsigned long shutdownHoldTime = 2000; // 2 seconds
void setup() {
Serial.begin(115200);
// Encoder setup
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
pinMode(ENCODER_SW, INPUT_PULLUP);
encoder.attachFullQuad(ENCODER_CLK, ENCODER_DT);
encoder.clearCount();
// LCD setup
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Jimmy is Awake");
delay(1000);
lcd.clear();
displayMenu();
// Temperature/Humidity Sensor setup
dht.setup(DHTPIN, DHTesp::DHT22);
// Heater Setup
pinMode(HEATER1_PIN, OUTPUT);
pinMode(HEATER2_PIN, OUTPUT);
digitalWrite(HEATER1_PIN, LOW);
digitalWrite(HEATER2_PIN, LOW);
//PWM Fan setup (ESP32)
//analogWriteResolution(FAN_PWM_PIN, 8);
//analogWriteFrequency(FAN_PWM_PIN, 25000);
//analogWrite(FAN_PWM_PIN, 0);
ledcSetup(FAN_PWM_CHANNEL, FAN_PWM_FREQ, FAN_PWM_RES); // Configure channel
ledcAttachPin(FAN_PWM_PIN, FAN_PWM_CHANNEL); // Attach pin to channel
ledcWrite(FAN_PWM_CHANNEL, fanDuty); // Set duty (0–255 for 8-bit)
}
void loop() {
Serial.print("Temp: ");
Serial.print(currentTemp);
Serial.print(" | Humidity: ");
Serial.println(humidity);
if (currentState == MENU) {
long newPos = getEncoderPosition();
if (newPos != lastPosition) {
lastPosition = newPos;
menuIndex = ((newPos % menuLength) + menuLength) % menuLength;
if (menuIndex < 0) menuIndex += menuLength;
displayMenu();
}
if (digitalRead(ENCODER_SW) == LOW && millis() - lastPress > debounceDelay) {
lastPress = millis();
handleSelect(menuIndex);
}
}
else if (currentState == SET_TEMP) {
long rawTemp = getEncoderPosition();
long newTemp = constrain(rawTemp, tempMin, tempMax);
if (newTemp != targetTemp) {
targetTemp = newTemp;
encoder.setCount(-targetTemp * 4);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Temp:");
lcd.setCursor(0, 1);
lcd.print(targetTemp);
lcd.print(" C");
}
if (digitalRead(ENCODER_SW) == LOW && millis() - lastPress > debounceDelay) {
lastPress = millis();
currentState = MENU;
encoder.setCount(-menuIndex * 4);
displayMenu();
}
}
else if (currentState == SET_TIMER) {
long rawTime = getEncoderPosition();
long newTime = constrain(rawTime, timeMin, timeMax);
if (newTime != targetTime) {
targetTime = newTime;
encoder.setCount(-targetTime * 4);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Time:");
lcd.setCursor(0, 1);
lcd.print(targetTime);
lcd.print(" min");
}
if (digitalRead(ENCODER_SW) == LOW && millis() - lastPress > debounceDelay) {
lastPress = millis();
currentState = MENU;
encoder.setCount(-menuIndex * 4);
displayMenu();
}
}
else if (currentState == CYCLE_RUNNING) {
updateSensors();
analogWrite(FAN_PWM_PIN, fanDuty);
unsigned long elapsed = (millis() - cycleStartTime) / 60000;
unsigned long remaining = (targetTime > elapsed) ? (targetTime - elapsed) : 0;
if (currentTemp < targetTemp) {
digitalWrite(HEATER1_PIN, HIGH);
digitalWrite(HEATER2_PIN, HIGH);
}
else {
digitalWrite(HEATER1_PIN, LOW);
digitalWrite(HEATER2_PIN, LOW);
}
lcd.setCursor(0, 0);
lcd.print("Temp: ");
lcd.print(targetTemp);
lcd.print("/");
if (!isnan(currentTemp)) {
lcd.print(currentTemp);
lcd.print("C ");
} else {
lcd.print("??.?C ");
}
lcd.setCursor(0, 1);
lcd.print(remaining);
lcd.print(" min ");
if (!isnan(humidity)) {
lcd.print("Hum:");
lcd.print((int)humidity);
lcd.print("%");
} else {
lcd.print("Hum:??%");
}
if (digitalRead(ENCODER_SW) == LOW) {
if (buttonPressTime == 0) {
buttonPressTime = millis();
} else if (millis() - buttonPressTime >= shutdownHoldTime && !shutdownRequested) {
shutdownRequested = true;
performShutdown();
}
} else {
buttonPressTime = 0;
}
if (remaining == 0 && !cycleComplete) {
cycleComplete = true;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Cycle Complete!");
performShutdown();
delay(3000);
currentState = MENU;
encoder.setCount(-menuIndex * 4);
displayMenu();
}
delay(1000);
}
}
void displayMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("> ");
lcd.print(menuItems[menuIndex]);
}
void handleSelect(int index) {
if (menuItems[index] == "Set Temp") {
currentState = SET_TEMP;
encoder.setCount(-targetTemp * 4);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Temp:");
lcd.setCursor(0, 1);
lcd.print(targetTemp);
lcd.print(" C");
} else if (menuItems[index] == "Set Timer") {
currentState = SET_TIMER;
encoder.setCount(-targetTime * 4);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Time:");
lcd.setCursor(0, 1);
lcd.print(targetTime);
lcd.print(" min");
} else if (menuItems[index] == "Start Cycle") {
currentState = CYCLE_RUNNING;
cycleStartTime = millis();
cycleComplete = false;
shutdownRequested = false;
buttonPressTime = 0;
lcd.clear();
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Selected:");
lcd.setCursor(0, 1);
lcd.print(menuItems[index]);
delay(1000);
displayMenu();
}
}
void performShutdown() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Cooling down...");
digitalWrite(HEATER1_PIN, LOW);
digitalWrite(HEATER2_PIN, LOW);
analogWrite(FAN_PWM_PIN, fanDuty);
delay(120000);
analogWrite(FAN_PWM_PIN, 0);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Shutdown done.");
delay(2000);
currentState = MENU;
cycleComplete = false;
shutdownRequested = false;
encoder.setCount(-menuIndex * 4);
displayMenu();
}
void updateSensors() {
TempAndHumidity data = dht.getTempAndHumidity();
if (!isnan(data.temperature)) currentTemp = data.temperature;
if (!isnan(data.humidity)) humidity = data.humidity;
}