diff --git a/README.md b/README.md new file mode 100644 index 0000000..06a908d --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ + +## TODO + +* check the ai suggestion +* time after completion is not displayed +* eeprom saving every 5 minutes and resuming +* profile selection diff --git a/hot_fermentation.ino b/hot_fermentation.ino index cb16c7a..5ff607e 100644 --- a/hot_fermentation.ino +++ b/hot_fermentation.ino @@ -1,7 +1,8 @@ #include #include #include -#include // Include the GyverOLED library +#include +#include "GyverEncoder.h" // Include the GyverEncoder library // MAX6675 configuration int max_SO = 12; @@ -12,6 +13,12 @@ MAX6675 thermocouple(max_SCK, max_CS, max_SO); // OLED configuration GyverOLED oled; +// Encoder configuration +#define CLK 5 +#define DT 6 +#define SW 7 +Encoder enc1(CLK, DT, SW, TYPE2); + // SSR pin configuration const int ssrPin = 7; @@ -36,13 +43,15 @@ Profile profiles[] = { {"Фитаза/Протеаза", 2, {{47, 120}, {53, 120}, {65, 150}, {72, 60}, {90, 105}, {50, 60}}, 6}, }; -// Select active profile (constant at this point) -const int activeProfileIndex = 0; // Index of the active profile, starting from 0 -Profile activeProfile = profiles[activeProfileIndex]; +// Global variables for profile selection and execution +int activeProfileIndex = 100; // 100 indicates no profile selected yet +int selectedProfileIndex = 0; // Index of the currently selected profile in the selection mode +Profile activeProfile; // The active profile once selected // Global variables for current phase, setpoint, and transition state int currentPhase; bool isInTransition = false; +bool inSelectionMode = true; // PID Control variables double Setpoint, Input, Output; @@ -67,46 +76,87 @@ void setup() { Serial.begin(9600); oled.init(); // Initialize the OLED oled.clear(); // Clear the display - oled.setScale(2); // Set text scale to 2 for better visibility - oled.print("Starting..."); - oled.update(); - oled.setScale(1); // Set text scale back to 1 for more detailed information - - calculateTotalTime(); - - // Begin the first phase - phaseStartTime = totalStartTime = millis(); - myPID.SetMode(AUTOMATIC); - myPID.SetOutputLimits(0, 1); // SSR is either ON or OFF - digitalWrite(ssrPin, HIGH); // Start with heater on - ssrLastSwitchTime = millis(); + displaySelection(); } void loop() { - unsigned long currentTime = millis(); - totalElapsedTime = (currentTime - totalStartTime) / 1000; // Total elapsed time in seconds + enc1.tick(); // Mandatory encoder update function - getPhaseAndTemperature(totalElapsedTime); + if (inSelectionMode) { + handleProfileSelection(); // Handle profile selection mode + } else { + unsigned long currentTime = millis(); + totalElapsedTime = (currentTime - totalStartTime) / 1000; // Total elapsed time in seconds - Input = (int) thermocouple.readCelsius(); // Cast to integer for display and control - myPID.Compute(); + getPhaseAndTemperature(totalElapsedTime); - // Switch SSR based on PID output and interval control - if (currentTime - ssrLastSwitchTime > ssrSwitchInterval) { - digitalWrite(ssrPin, Output > 0.5 ? HIGH : LOW); - ssrLastSwitchTime = currentTime; + Input = (int) thermocouple.readCelsius(); // Cast to integer for display and control + myPID.Compute(); + + // Switch SSR based on PID output and interval control + if (currentTime - ssrLastSwitchTime > ssrSwitchInterval) { + digitalWrite(ssrPin, Output > 0.5 ? HIGH : LOW); + ssrLastSwitchTime = currentTime; + } + + if (isComplete && currentPhase >= activeProfile.numPhases) { + finishTime = currentTime; + } + + // Display all phases and highlight the current one + printPhases(currentPhase, currentTime - phaseStartTime, currentTime); + + oled.update(); + + delay(1000); // Update every second + } +} + +void handleProfileSelection() { + + bool click = enc1.isClick(); + bool turn = enc1.isTurn(); + if (!click && !turn) { + return; } - if (isComplete && currentPhase >= activeProfile.numPhases) { - finishTime = currentTime; + // Handle encoder input for selecting the profile + if (enc1.isRight()) { + selectedProfileIndex = (selectedProfileIndex + 1) % (sizeof(profiles) / sizeof(profiles[0])); + } else if (enc1.isLeft()) { + selectedProfileIndex = (selectedProfileIndex - 1 + (sizeof(profiles) / sizeof(profiles[0]))) % (sizeof(profiles) / sizeof(profiles[0])); } - // Display all phases and highlight the current one - printPhases(currentPhase, currentTime - phaseStartTime, currentTime); + displaySelection(); + // Start the selected profile on button press + if (click) { + activeProfileIndex = selectedProfileIndex; + activeProfile = profiles[activeProfileIndex]; + calculateTotalTime(); + inSelectionMode = false; // Switch to execution mode + phaseStartTime = totalStartTime = millis(); // Start the timer + myPID.SetMode(AUTOMATIC); + myPID.SetOutputLimits(0, 1); // SSR is either ON or OFF + digitalWrite(ssrPin, HIGH); // Start with heater on + ssrLastSwitchTime = millis(); + } +} + +void displaySelection() { + oled.clear(); + // Display all profiles with the selected one highlighted + for (int i = 0; i < (int) (sizeof(profiles) / sizeof(profiles[0])); i++) { + if (i == selectedProfileIndex) { + oled.invertText(true); // Highlight the selected profile + } + + oled.setCursor(0, i); // Set cursor to the correct row + oled.print(profiles[i].name); + + oled.invertText(false); // Reset text inversion for other profiles + } oled.update(); - - delay(1000); // Update every second } void calculateTotalTime() { @@ -136,9 +186,9 @@ void getPhaseAndTemperature(unsigned long elapsedSeconds) { if (elapsedSeconds < accumulatedTime + transitionDuration) { isInTransition = true; - currentPhase = i; + currentPhase = i - 1; // Keep currentPhase as the previous phase int timeInTransition = elapsedSeconds - accumulatedTime; - Setpoint = previousTemp + ((targetTemp > previousTemp ? (double)timeInTransition / 60 : -(double)timeInTransition / 60) / (double)activeProfile.transitionMinutesPerDegree); + Setpoint = previousTemp + (double)timeInTransition / (60 * activeProfile.transitionMinutesPerDegree) * (targetTemp > previousTemp ? 1 : -1); return; }