April 26, 2025
Description
The smart feeder aims to automate your pet's feeding, providing access to food at specific times and only when the pet is present. This is achieved by integrating several electronic components controlled by an Arduino Uno.
The electronic components required for the project are described below:
To complete this project, you will need the following tools:
Step 1:
This panel is 4 mm thick to provide rigidity, as the load cell requires a base that will not flex under the weight.
Step 2: Load Cell Assembly
Items Required for this Step:
Insert the 4 M4 nuts into their corresponding slots/recesses.
Mount the load cell onto its support.
Attach the support (with the load cell already mounted) to the composite base panel using the appropriate screws.
Route the load cell cables through the channel/conduit.
Attach the threaded base together with its spacer to the load cell.
Step 3: Gluing and assembling the left side structure
Items Required for this Step:
Insert the 7 M3 nuts into their corresponding slots/recesses in the side structure pieces.
Apply glue and join the "Lower Left Structure (Piece1)" piece to the "Lower Left Structure (Piece2)" piece.
Next, apply glue and attach the "FrontLeftGuide" piece.
Finally, apply glue and attach the "RearLeftGuide" piece.
Tip:
Once the parts of the left side structure are firmly glued and dry, mount this structure onto the composite base plate. Take the left leg (it is recommended to print this part in TPU) and assemble it to the structure/base assembly as shown in the photo.
Step 4: Gluing the right side structure
Items Required for this Step:
Proceed with gluing in the same manner as in Step 3 (Gluing and mounting of the Left Side Structure), using the corresponding pieces for the right side.
Step 5: Assembly of the Right Side Frame and Crossbars
Items Required for this Step:
Assembly Procedure:
Once the right side structure is firmly glued and dry, first mount the "Lower Rear Bar" piece.
Subsequently, assemble the right side structure together with the leg onto the composite base plate, securing it in place.
Then, join the upper part of both side structures (left and right) using the "Upper Rear Bar" piece.
Tip:
Step 6: Mounting Electronic Components and Arduino
Assembly Procedure:
Insert the M3 nuts into their slots. Make sure they are properly seated.
Place the Arduino Uno onto its corresponding holder and secure it using the screws. Likewise, mount the DS3231 module.
Place the holders onto the side structure, leaving a free space. As shown in the photo.
Once the three holders are mounted, attach the HX711 module to its holder.
Step 7: Engine assembly
2 M3 nuts
2 DIN 912 M3x25
1 DIN 912 M3x16
4 DIN 912 M3x12
1 DIN 912 M3x8
2 DIN M3 M3x6
1 608ZZ bearing
Step 8:
(in edition)
#include <Wire.h> // For I2C communication
#include <RTClib.h> // For DS3231 RTC
#include <LiquidCrystal_I2C.h> // For I2C LCD
#include <HX711_ADC.h> // For HX711 load cell amplifier
#include <EEPROM.h> // For non-volatile memory
// --- I2C Configuration ---
const uint8_t LCD_ADDRESS = 0x27; // Common I2C Address for LCD (might be 0x3F or other)
const uint8_t RTC_ADDRESS = 0x68; // Standard I2C Address for DS3231
// --- Object Initialization ---
RTC_DS3231 rtc; // Object for DS3231 RTC
LiquidCrystal_I2C lcd(LCD_ADDRESS, 20, 4); // Object for 20x4 LCD (address, columns, rows)
// --- Pin Definitions ---
const int TRIG_PIN = 2; // Ultrasonic sensor Trigger pin
const int ECHO_PIN = 3; // Ultrasonic sensor Echo pin
const int MOTOR_IN1 = 7; // Motor control pin 1
const int MOTOR_IN2 = 6; // Motor control pin 2
const int OPEN_LIMIT_SWITCH = 8; // Limit switch for fully open door
const int CLOSE_LIMIT_SWITCH = 9; // Limit switch for fully closed door
const int HX711_DOUT_PIN = 4; // HX711 Data Out pin (DT)
const int HX711_SCK_PIN = 5; // HX711 Serial Clock pin (SCK)
// Button Pins
const int TARE_BUTTON_PIN = 12; // Button to tare the scale
const int MODE_BUTTON_PIN = 13; // Button to enter/cycle settings mode
const int UP_BUTTON_PIN = 11; // Button to increment value in settings
const int DOWN_BUTTON_PIN = 10; // Button to decrement value in settings
// --- Configuration Constants ---
const unsigned long PRESENCE_TIMEOUT = 15000; // ms: Time door stays open after last presence
const int DISTANCE_THRESHOLD = 40; // cm: Distance to detect presence/obstacle
const int SENSOR_READ_INTERVAL = 100; // ms: How often to read sensors
const int MOTOR_START_DELAY = 50; // ms: Small delay after starting motor
const int LCD_UPDATE_INTERVAL = 500; // ms: How often to update LCD
const unsigned long DEBOUNCE_DELAY = 50; // ms: Debounce time for buttons
const unsigned long MANUAL_OVERRIDE_HOLD_TIME = 1000; // ms: Hold time for manual override buttons
const unsigned long MOTOR_TIMEOUT_DURATION = 6000; // 6 seconds motor run timeout
// --- Weight Unit Enum ---
enum WeightUnit { UNIT_GRAMS, UNIT_OUNCES };
// --- Settings Variables (loaded from EEPROM, with defaults) ---
int restrictedHourStart = 22; // Default restriction start time (10 PM)
int restrictedHourEnd = 6; // Default restriction end time (6 AM)
WeightUnit currentUnit = UNIT_GRAMS; // Default weight unit
// --- HX711 Calibration ---
// !!! REPLACE THIS VALUE WITH YOUR OWN OBTAINED FROM CALIBRATION (using grams) !!!
const float CALIBRATION_FACTOR = 419.0; // Example value - MUST BE CHANGED!
// --- EEPROM Addresses & Check Value ---
#define EEPROM_ADDR_R_START 0
#define EEPROM_ADDR_R_END (EEPROM_ADDR_R_START + sizeof(int))
#define EEPROM_ADDR_UNIT (EEPROM_ADDR_R_END + sizeof(int))
#define EEPROM_ADDR_CHECK (EEPROM_ADDR_UNIT + sizeof(WeightUnit))
#define EEPROM_CHECK_VALUE 0x5B // Use a consistent check value
// --- HX711 Object Initialization ---
HX711_ADC LoadCell(HX711_DOUT_PIN, HX711_SCK_PIN);
// --- System States (Door) ---
enum State {
INIT_OPENING, INIT_CLOSING, CLOSED, OPENING, OPEN, CLOSING, OBSTACLE_DETECTED,
ERROR_MOTOR_TIMEOUT / Error State
};
State currentState = CLOSED;
// --- Global Variables ---
unsigned long lastPresenceTime = 0;
unsigned long lastSensorReadTime = 0;
unsigned long lastLcdUpdateTime = 0;
long currentDistance = -1;
bool openLimitReached = false;
bool closeLimitReached = false;
DateTime now;
float currentWeight = 0.0; // Stores weight in GRAMS
bool hx711_ready = false;
// Settings Mode
bool isInSettingsMode = false;
enum SettingField { NONE, SET_HOUR, SET_MINUTE, SET_RESTRICT_START, SET_RESTRICT_END, SET_UNIT }; // Uses only fields from arduinoIngles.TXT
SettingField currentSettingField = NONE;
int tempHour = 0; int tempMinute = 0; int tempR_Start = 0; int tempR_End = 0; WeightUnit tempUnit = UNIT_GRAMS;
// Button Debounce
bool lastTareState = HIGH; unsigned long lastTareDebounceTime = 0;
bool lastModeState = HIGH; unsigned long lastModeDebounceTime = 0;
bool lastUpState = HIGH; unsigned long lastUpDebounceTime = 0;
bool lastDownState = HIGH; unsigned long lastDownDebounceTime = 0;
bool stableTareState = HIGH; bool stableModeState = HIGH; bool stableUpState = HIGH; bool stableDownState = HIGH;
bool tarePressed = false; bool modePressed = false; bool upPressed = false; bool downPressed = false;
// Long Press
unsigned long upButtonPressStartTime = 0;
unsigned long downButtonPressStartTime = 0;
// Motor Timeout
unsigned long motorStartTime = 0; // Timestamp when motor starts
// --- Function Prototypes ---
long getDistance();
void openDoor(); void closeDoor(); void stopMotor();
void readLimitSwitches(); void handleStateMachine(); bool isRestrictedTimeNow();
void updateLcdDisplay(); const char* getStateString(State s);
void readWeight(); void readButtons(); void handleSettingsMode(); void tareScale();
const char* getSettingFieldString(SettingField sf);
void loadConfigFromEEPROM(); void saveConfigToEEPROM();
const char* getUnitString(WeightUnit u);
// --- Initial Setup ---
void setup() {
Serial.begin(9600);
Serial.println(F("Starting System V1.1 (Motor Timeout Added)...")); // Version update
Wire.begin();
// Init LCD
lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print(F("Initializing Sys..."));
Serial.println(F("LCD initialized."));
// Init RTC
if (!rtc.begin()) { Serial.println(F("Error: DS3231 RTC!")); lcd.setCursor(0, 1); lcd.print(F("Error: RTC missing")); while (1) delay(10); }
Serial.println(F("DS3231 RTC found."));
if (rtc.lostPower()) { Serial.println(F("RTC lost power!")); lcd.setCursor(0, 1); lcd.print(F("RTC: Set Time")); /* rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); */ }
lcd.setCursor(0, 1); lcd.print(F("RTC OK")); delay(500); lcd.setCursor(0,1); lcd.print(F(" "));
// Init HX711
Serial.println(F("Initializing HX711...")); lcd.setCursor(0,1); lcd.print(F("HX711 Init..."));
LoadCell.begin(); LoadCell.start(2000, true);
if (LoadCell.getTareTimeoutFlag()) { Serial.println(F("HX711 tare timeout.")); lcd.setCursor(0,1); lcd.print(F("Error: HX711 Tare TO")); }
else { LoadCell.setCalFactor(CALIBRATION_FACTOR); Serial.println(F("HX711 ready.")); lcd.setCursor(0,1); lcd.print(F("HX711 OK")); hx711_ready = true; }
delay(500); lcd.setCursor(0,1); lcd.print(F(" "));
// Configure Pins
pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); pinMode(MOTOR_IN1, OUTPUT); pinMode(MOTOR_IN2, OUTPUT);
pinMode(OPEN_LIMIT_SWITCH, INPUT_PULLUP); pinMode(CLOSE_LIMIT_SWITCH, INPUT_PULLUP);
pinMode(TARE_BUTTON_PIN, INPUT_PULLUP); pinMode(MODE_BUTTON_PIN, INPUT_PULLUP); pinMode(UP_BUTTON_PIN, INPUT_PULLUP); pinMode(DOWN_BUTTON_PIN, INPUT_PULLUP);
// Initialize button states
stableTareState=digitalRead(TARE_BUTTON_PIN); stableModeState=digitalRead(MODE_BUTTON_PIN); stableUpState=digitalRead(UP_BUTTON_PIN); stableDownState=digitalRead(DOWN_BUTTON_PIN);
lastTareState=stableTareState; lastModeState=stableModeState; lastUpState=stableUpState; lastDownState=stableDownState;
// Load config from EEPROM
loadConfigFromEEPROM();
stopMotor(); // Ensure motor is stopped initially
readLimitSwitches(); // Read initial limit switch states
// Determine initial door state based on limit switches
if (closeLimitReached) { currentState = CLOSED; Serial.println(F("Initial state: CLOSED")); }
else if (openLimitReached) { currentState = OPEN; Serial.println(F("Initial state: OPEN")); }
else { currentState = INIT_OPENING; openDoor(); /* motorStartTime set in openDoor() */ Serial.println(F("Initial state: INIT_OPENING")); }
updateLcdDisplay();
} // End setup
// --- Main Loop ---
void loop() {
now = rtc.now();
readButtons();
if (isInSettingsMode) {
handleSettingsMode();
} else if (currentState == ERROR_MOTOR_TIMEOUT) {
// In error state, do nothing except update LCD (already handled below)
// Optionally, check for MODE button press to reset?
// if (modePressed) {
// Serial.println(F("Motor Timeout acknowledged. Resetting to CLOSED."));
// currentState = CLOSED;
// }
; // Do nothing for now
}
else {
// --- Normal Operation Mode ---
unsigned long currentMillis = millis();
if (tarePressed) { tareScale(); }
// Handle Manual Override
bool override_action_taken = false;
if (isRestrictedTimeNow() && currentState == CLOSED && stableUpState == LOW) {
if (upButtonPressStartTime != 0 && (currentMillis - upButtonPressStartTime > MANUAL_OVERRIDE_HOLD_TIME)) {
Serial.println(F("OVERRIDE: Opening door (UP long press)..."));
currentState = OPENING; openDoor(); // motorStartTime set inside openDoor
delay(MOTOR_START_DELAY); // Let motor start
upButtonPressStartTime = 0; override_action_taken = true;
}
} else if (isRestrictedTimeNow() && currentState == OPEN && stableDownState == LOW) {
if (downButtonPressStartTime != 0 && (currentMillis - downButtonPressStartTime > MANUAL_OVERRIDE_HOLD_TIME)) {
Serial.println(F("OVERRIDE: Closing door (DOWN long press)..."));
currentState = CLOSING; closeDoor(); // motorStartTime set inside closeDoor
delay(MOTOR_START_DELAY); // Let motor start
downButtonPressStartTime = 0; override_action_taken = true;
}
}
// Read Sensors (if no override)
if (!override_action_taken) {
if (currentMillis - lastSensorReadTime >= SENSOR_READ_INTERVAL) {
currentDistance = getDistance();
readLimitSwitches();
readWeight();
lastSensorReadTime = currentMillis;
}
} else { lastSensorReadTime = currentMillis; }
// Handle State Machine
handleStateMachine();
// Check for entering settings mode
if (modePressed) {
isInSettingsMode = true; currentSettingField = SET_HOUR;
tempHour = now.hour(); tempMinute = now.minute(); tempR_Start = restrictedHourStart; tempR_End = restrictedHourEnd; tempUnit = currentUnit;
Serial.println(F("Entering Settings Mode..."));
stopMotor(); // Stop motor and reset timer when entering settings
updateLcdDisplay(); lastLcdUpdateTime = currentMillis;
}
} // End Normal Mode
// Update LCD periodically
if (millis() - lastLcdUpdateTime >= LCD_UPDATE_INTERVAL) {
updateLcdDisplay(); lastLcdUpdateTime = millis();
}
} // End loop
// --- Function Implementations ---
// Reads buttons, handles debounce, sets flags, tracks long press start times
void readButtons() {
tarePressed = false; modePressed = false; upPressed = false; downPressed = false;
unsigned long now_ms = millis();
// --- TARE ---
bool rT = digitalRead(TARE_BUTTON_PIN);
if (rT != lastTareState) { lastTareDebounceTime = now_ms; }
if ((now_ms - lastTareDebounceTime) > DEBOUNCE_DELAY) { if (rT != stableTareState) { stableTareState = rT; if (stableTareState == LOW) { tarePressed = true; Serial.println(F(">>> TARE Pressed <<<")); } } }
lastTareState = rT;
// --- MODE ---
bool rM = digitalRead(MODE_BUTTON_PIN);
if (rM != lastModeState) { lastModeDebounceTime = now_ms; }
if ((now_ms - lastModeDebounceTime) > DEBOUNCE_DELAY) { if (rM != stableModeState) { stableModeState = rM; if (stableModeState == LOW) { modePressed = true; Serial.println(F(">>> MODE Pressed <<<")); } } }
lastModeState = rM;
// --- UP ---
bool rU = digitalRead(UP_BUTTON_PIN);
if (rU != lastUpState) { lastUpDebounceTime = now_ms; }
if ((now_ms - lastUpDebounceTime) > DEBOUNCE_DELAY) { if (rU != stableUpState) { stableUpState = rU; if (stableUpState == LOW) { upPressed = true; Serial.println(F(">>> UP Pressed <<<")); upButtonPressStartTime = now_ms; } else { upButtonPressStartTime = 0; } } }
lastUpState = rU;
// --- DOWN ---
bool rD = digitalRead(DOWN_BUTTON_PIN);
if (rD != lastDownState) { lastDownDebounceTime = now_ms; }
if ((now_ms - lastDownDebounceTime) > DEBOUNCE_DELAY) { if (rD != stableDownState) { stableDownState = rD; if (stableDownState == LOW) { downPressed = true; Serial.println(F(">>> DOWN Pressed <<<")); downButtonPressStartTime = now_ms; } else { downButtonPressStartTime = 0; } } }
lastDownState = rD;
}
// Performs tare operation
void tareScale() {
if (!hx711_ready) { Serial.println(F("HX711 not ready")); return; }
Serial.println(F("Taring scale..."));
lcd.setCursor(0, 0); lcd.print(F("Taring... "));
LoadCell.tare(); currentWeight = 0.0; delay(100);
updateLcdDisplay(); lastLcdUpdateTime = millis();
Serial.println(F("Scale tared."));
}
// Handles settings menu navigation and value changes
void handleSettingsMode() {
// MODE Button: Cycle or Save/Exit
if (modePressed) {
// Cycle to the next setting field
int currentIdx = (int)currentSettingField;
int nextIdx = currentIdx + 1;
// Cycle from last field (SET_UNIT) back to first (SET_HOUR) or exit (NONE)?
// Let's exit after last field.
if (nextIdx > SET_UNIT) { // If currently on last item (SET_UNIT)
// --- Save and Exit ---
Serial.println(F("Saving configuration and exiting..."));
now = rtc.now();
rtc.adjust(DateTime(now.year(), now.month(), now.day(), tempHour, tempMinute, now.second()));
restrictedHourStart = tempR_Start; restrictedHourEnd = tempR_End;
currentUnit = tempUnit;
saveConfigToEEPROM(); // Save relevant settings
currentSettingField = NONE;
isInSettingsMode = false;
Serial.println(F("Exited Settings Mode."));
// No updateLcdDisplay here, let the main loop handle it
return; // Exit function immediately
} else {
// Move to next field
currentSettingField = (SettingField)nextIdx;
Serial.print(F("New field selected: ")); Serial.println(getSettingFieldString(currentSettingField));
updateLcdDisplay(); lastLcdUpdateTime = millis(); // Update LCD immediately
}
} // End if modePressed
// UP/DOWN Buttons: Modify temporary value
if (upPressed || downPressed) {
int delta = upPressed ? 1 : -1;
bool valueChanged = true;
switch (currentSettingField) {
case SET_HOUR: tempHour = (tempHour + delta + 24) % 24; break;
case SET_MINUTE: tempMinute = (tempMinute + delta + 60) % 60; break;
case SET_RESTRICT_START: tempR_Start = (tempR_Start + delta + 24) % 24; break;
case SET_RESTRICT_END: tempR_End = (tempR_End + delta + 24) % 24; break;
case SET_UNIT: tempUnit = (tempUnit == UNIT_GRAMS) ? UNIT_OUNCES : UNIT_GRAMS; break; // Toggle
default: valueChanged = false; break; // No action if NONE
}
if (valueChanged) {
Serial.print(F(" Temp value updated for: ")); Serial.println(getSettingFieldString(currentSettingField));
updateLcdDisplay(); lastLcdUpdateTime = millis(); // Update LCD immediately
}
}
}
// Updates the LCD display based on current mode (Normal or Settings)
void updateLcdDisplay() {
char lineBuffer[21], floatBuffer[10];
const float G_TO_OZ_FACTOR = 0.035274;
// *** NEW: Handle ERROR_MOTOR_TIMEOUT state display ***
if (currentState == ERROR_MOTOR_TIMEOUT) {
lcd.clear();
lcd.setCursor(0, 0); lcd.print(F("!!! MOTOR ERROR !!! "));
lcd.setCursor(0, 1); lcd.print(F(" Movement Timeout "));
lcd.setCursor(0, 2); lcd.print(F(" Check Door / Limit "));
lcd.setCursor(0, 3); lcd.print(F(" Switches! "));
return; // Stop further display updates in error state
}
lcd.clear(); // Clear display for normal/settings redraw
if (isInSettingsMode) {
// --- Settings Mode Interface --- (Simplified, No Scroll)
lcd.setCursor(0, 0); lcd.print(F("SETUP:")); lcd.print(getSettingFieldString(currentSettingField));
lcd.setCursor(0, 1); snprintf(lineBuffer, sizeof(lineBuffer), "Time: %02d:%02d %c", tempHour, tempMinute, (currentSettingField == SET_HOUR || currentSettingField == SET_MINUTE) ? '<' : ' '); lcd.print(lineBuffer);
lcd.setCursor(0, 2); snprintf(lineBuffer, sizeof(lineBuffer), "Restr: %02d - %02d %c", tempR_Start, tempR_End, (currentSettingField == SET_RESTRICT_START || currentSettingField == SET_RESTRICT_END) ? '<' : ' '); lcd.print(lineBuffer);
lcd.setCursor(0, 3); snprintf(lineBuffer, sizeof(lineBuffer), "Unit: %-7s %c", getUnitString(tempUnit), (currentSettingField == SET_UNIT) ? '<' : ' '); lcd.print(lineBuffer);
} else {
// --- Normal Operation Interface ---
// Line 0: Weight
lcd.setCursor(0, 0);
if (hx711_ready) { float dW=currentWeight; const char* uS=" g"; int p=1; if(currentUnit==UNIT_OUNCES){dW*=G_TO_OZ_FACTOR; uS=" oz"; p=2;} dtostrf(dW,1,p,floatBuffer); snprintf(lineBuffer,sizeof(lineBuffer),"Weight:%-7s%s",floatBuffer,uS); }
else { snprintf(lineBuffer, sizeof(lineBuffer), "Weight: ---.- "); }
lcd.print(lineBuffer); for (int i=strlen(lineBuffer); i<20; i++){lcd.print(F(" "));}
// Line 1: Time
snprintf(lineBuffer,sizeof(lineBuffer),"Time: %02d:%02d:%02d",now.hour(),now.minute(),now.second()); lcd.setCursor(0,1); lcd.print(lineBuffer); for(int i=strlen(lineBuffer); i<20; i++){lcd.print(F(" "));}
// Line 2: Status
snprintf(lineBuffer, sizeof(lineBuffer), "Status: %-12s", getStateString(currentState)); lcd.setCursor(0, 2); lcd.print(lineBuffer); for (int i = strlen(lineBuffer); i < 20; i++) { lcd.print(F(" ")); }
// Line 3: Dist/Restr
lcd.setCursor(0,3); if(isRestrictedTimeNow()){snprintf(lineBuffer,sizeof(lineBuffer),"Restr: %02d-%02dh ACTIVE", restrictedHourStart, restrictedHourEnd);}else{if(currentDistance>=0 && currentDistance<999){snprintf(lineBuffer,sizeof(lineBuffer),"Dist: %3ld cm ",currentDistance);}else{snprintf(lineBuffer,sizeof(lineBuffer),"Dist: --- cm ");}} lcd.print(lineBuffer); for(int i=strlen(lineBuffer); i<20; i++){lcd.print(F(" "));}
}
}
// Converts SettingField enum to a printable string (English, No F() in return)
const char* getSettingFieldString(SettingField sf) {
switch (sf) { case SET_HOUR: return "HOUR"; case SET_MINUTE: return "MINUTE"; case SET_RESTRICT_START: return "R.START"; case SET_RESTRICT_END: return "R.END"; case SET_UNIT: return "UNIT"; case NONE: return " "; default: return "?"; }
}
// Converts WeightUnit enum to a printable string (English)
const char* getUnitString(WeightUnit u) {
return (u == UNIT_GRAMS) ? "GRAMS" : "OUNCES";
}
// Reads weight from HX711
void readWeight() { if (hx711_ready) { if (LoadCell.update()) { currentWeight = LoadCell.getData(); } } }
// Reads limit switches
void readLimitSwitches() { openLimitReached=(digitalRead(OPEN_LIMIT_SWITCH)==LOW); closeLimitReached=(digitalRead(CLOSE_LIMIT_SWITCH)==LOW); }
// Checks if current time is within restricted period
bool isRestrictedTimeNow() { int cH=now.hour(); bool r=false; if(restrictedHourStart > restrictedHourEnd){r=(cH>=restrictedHourStart || cH<restrictedHourEnd);}else{if(restrictedHourStart == restrictedHourEnd) return false; r=(cH>=restrictedHourStart && cH<restrictedHourEnd);} return r; }
// Main state machine for door operation
void handleStateMachine() {
// Don't run state machine if in settings or error state
if (isInSettingsMode || currentState == ERROR_MOTOR_TIMEOUT) return;
State previousState = currentState;
// --- Motor Timeout Check ---
// Check only if motor is supposed to be moving and timer is running
if (motorStartTime != 0 && (currentState == OPENING || currentState == CLOSING || currentState == INIT_OPENING || currentState == INIT_CLOSING)) {
if (millis() - motorStartTime > MOTOR_TIMEOUT_DURATION) {
Serial.println(F("!!! MOTOR TIMEOUT !!! State was: ")); Serial.println(getStateString(currentState));
stopMotor(); // This will also set motorStartTime = 0
currentState = ERROR_MOTOR_TIMEOUT;
// Update LCD immediately to show error
updateLcdDisplay();
lastLcdUpdateTime = millis();
return; // Exit state machine handling for this cycle
}
}
// --- Normal State Transitions ---
switch (currentState) {
case INIT_OPENING:
if (openLimitReached) { Serial.println(F("INIT: Open Limit")); stopMotor(); currentState=INIT_CLOSING; closeDoor(); /* timer started in closeDoor */ }
break;
case INIT_CLOSING:
if (closeLimitReached) { Serial.println(F("INIT: Close Limit")); stopMotor(); currentState=CLOSED; }
break;
case CLOSED:
if (currentDistance>=0 && currentDistance<DISTANCE_THRESHOLD) {
if (!isRestrictedTimeNow()) { Serial.println(F("CLOSED: Presence, opening")); currentState=OPENING; openDoor(); /* timer started in openDoor */ }
else { /*Serial.println(F("CLOSED: Presence, restricted"));*/ }
}
break;
case OPENING:
if (openLimitReached) { Serial.println(F("OPENING: Limit Reached")); stopMotor(); currentState=OPEN; }
break;
case OPEN:
if (currentDistance>=0 && currentDistance<DISTANCE_THRESHOLD) { lastPresenceTime=millis(); }
if (millis()-lastPresenceTime >= PRESENCE_TIMEOUT) { Serial.println(F("OPEN: Timeout, closing")); currentState=CLOSING; closeDoor(); /* timer started in closeDoor */ }
break;
case CLOSING:
if (currentDistance>=0 && currentDistance<DISTANCE_THRESHOLD) { Serial.println(F("CLOSING: Obstacle!")); stopMotor(); currentState=OBSTACLE_DETECTED; openDoor(); /* timer started in openDoor */ }
else if (closeLimitReached) { Serial.println(F("CLOSING: Limit Reached")); stopMotor(); currentState=CLOSED; }
break;
case OBSTACLE_DETECTED: // Re-opening after obstacle
if (openLimitReached) { Serial.println(F("OBSTACLE: Re-opened")); stopMotor(); currentState=OPEN; lastPresenceTime=millis(); }
break;
// ERROR_MOTOR_TIMEOUT case intentionally empty here - handled at start of loop/function
}
// If the state changed (and it wasn't to the error state already handled), update LCD
if (currentState != previousState) {
Serial.print(F("State Change: ")); Serial.println(getStateString(currentState));
updateLcdDisplay();
lastLcdUpdateTime = millis();
}
}
// Converts State enum to a pr
const char* getStateString(State s) {
switch(s){
case INIT_OPENING: return "Init Opening"; case INIT_CLOSING: return "Init Closing";
case CLOSED: return "Closed"; case OPENING: return "Opening"; case OPEN: return "Open";
case CLOSING: return "Closing"; case OBSTACLE_DETECTED: return "Obstacle!";
case ERROR_MOTOR_TIMEOUT: return "ERROR TIMEOUT";
default: return "Unknown";
}
}
// Measures distance using ultrasonic sensor
long getDistance() {
digitalWrite(TRIG_PIN,LOW);delayMicroseconds(2);digitalWrite(TRIG_PIN,HIGH);delayMicroseconds(10);digitalWrite(TRIG_PIN,LOW); long d=pulseIn(ECHO_PIN,HIGH,25000); return (d==0) ? 999 : d*0.0343/2.0;
}
// Motor control functions - Added motorStartTime handling
void openDoor() {
digitalWrite(MOTOR_IN1,HIGH); digitalWrite(MOTOR_IN2,LOW);
motorStartTime = millis(); // <<< Start timer
Serial.println(F("Motor: Opening"));
}
void closeDoor() {
digitalWrite(MOTOR_IN1,LOW); digitalWrite(MOTOR_IN2,HIGH);
motorStartTime = millis(); // <<< Start timer
Serial.println(F("Motor: Closing"));
}
void stopMotor() {
digitalWrite(MOTOR_IN1,LOW); digitalWrite(MOTOR_IN2,LOW);
motorStartTime = 0; // <<< Stop timer
Serial.println(F("Motor: Stopped"));
}
// Loads configuration from EEPROM
void loadConfigFromEEPROM() {
Serial.println(F("Loading config from EEPROM..."));
byte checkValue = EEPROM.read(EEPROM_ADDR_CHECK);
if (checkValue == EEPROM_CHECK_VALUE) {
EEPROM.get(EEPROM_ADDR_R_START, restrictedHourStart);
EEPROM.get(EEPROM_ADDR_R_END, restrictedHourEnd);
EEPROM.get(EEPROM_ADDR_UNIT, currentUnit);
if (currentUnit != UNIT_GRAMS && currentUnit != UNIT_OUNCES) { currentUnit = UNIT_GRAMS; Serial.println(F("Warn: Bad unit"));}
Serial.println(F("Configuration loaded."));
} else {
Serial.println(F("EEPROM invalid. Saving defaults."));
restrictedHourStart = 22; restrictedHourEnd = 6; currentUnit = UNIT_GRAMS; // Ensure defaults
saveConfigToEEPROM(); // Use save function
EEPROM.write(EEPROM_ADDR_CHECK, EEPROM_CHECK_VALUE);
Serial.println(F("Default values saved."));
}
Serial.print(F(" Restr Start: ")); Serial.println(restrictedHourStart);
Serial.print(F(" Restr End: ")); Serial.println(restrictedHourEnd);
Serial.print(F(" Weight Unit: ")); Serial.println(getUnitString(currentUnit));
}
// Saves configuration to EEPROM
void saveConfigToEEPROM() {
Serial.println(F("Saving config to EEPROM..."));
EEPROM.put(EEPROM_ADDR_R_START, restrictedHourStart);
EEPROM.put(EEPROM_ADDR_R_END, restrictedHourEnd);
EEPROM.put(EEPROM_ADDR_UNIT, currentUnit);
Serial.println(F("Configuration saved."));
}
#include <Wire.h> // For I2C communication
#include <RTClib.h> // For DS3231 RTC
#include <LiquidCrystal_I2C.h> // For I2C LCD
#include <HX711_ADC.h> // For HX711 load cell amplifier
#include <EEPROM.h> // For non-volatile memoryIncludes (#include ...): These lines bring in the necessary libraries for the code to function.
Wire.h: Essential for I2C (Inter-Integrated Circuit) communication, used to talk to the LCD and RTC.RTClib.h: Specific library for controlling the DS3231 Real-Time Clock (RTC) module. Allows getting the current date and time.LiquidCrystal_I2C.h: Library for controlling LCD screens connected via I2C. Makes it easier to write text to the display.HX711_ADC.h: Library for interacting with the HX711 amplifier, which reads data from the load cell (for weight measurement).EEPROM.h: Allows reading from and writing data to the Arduino's EEPROM memory. This memory retains data even when power is removed, useful for saving settings.// --- I2C Configuration ---
const uint8_t LCD_ADDRESS = 0x27; // Common I2C Address for LCD (might be 0x3F or other)
const uint8_t RTC_ADDRESS = 0x68; // Standard I2C Address for DS3231I2C Configuration: Defines the I2C addresses of the connected devices.
LCD_ADDRESS: The I2C address of the LCD screen. 0x27 is common, but 0x3F or others are possible. You must ensure this matches your specific screen.RTC_ADDRESS: The standard I2C address for the DS3231 RTC module (0x68).// --- Object Initialization ---
RTC_DS3231 rtc; // Object for DS3231 RTC
LiquidCrystal_I2C lcd(LCD_ADDRESS, 20, 4); // Object for 20x4 LCD (address, columns, rows)Object Initialization: Creates instances (objects) of the classes defined in the included libraries.
RTC_DS3231 rtc;: Creates an object named rtc that represents and allows control of the DS3231 clock.LiquidCrystal_I2C lcd(...);: Creates an object named lcd to control the LCD screen. It's given the I2C address (LCD_ADDRESS) and the dimensions (20 columns, 4 rows).// --- Pin Definitions --- (As per user's file arduinoIngles.TXT)
const int TRIG_PIN = 2; // Ultrasonic sensor Trigger pin
const int ECHO_PIN = 3; // Ultrasonic sensor Echo pin
const int MOTOR_IN1 = 7; // Motor control pin 1
const int MOTOR_IN2 = 6; // Motor control pin 2
const int OPEN_LIMIT_SWITCH = 8; // Limit switch for fully open door
const int CLOSE_LIMIT_SWITCH = 9; // Limit switch for fully closed door
const int HX711_DOUT_PIN = 4; // HX711 Data Out pin (DT)
const int HX711_SCK_PIN = 5; // HX711 Serial Clock pin (SCK)
// Button Pins
const int TARE_BUTTON_PIN = 12; // Button to tare the scale
const int MODE_BUTTON_PIN = 13; // Button to enter/cycle settings mode
const int UP_BUTTON_PIN = 11; // Button to increment value in settings
const int DOWN_BUTTON_PIN = 10; // Button to decrement value in settingsPin Definitions: Assigns descriptive names to the Arduino pin numbers being used.
TRIG_PIN, ECHO_PIN: Pins for the ultrasonic sensor (Trigger sends the pulse, Echo receives the reflection).MOTOR_IN1, MOTOR_IN2: Pins to control the motor's direction (likely connected to an H-Bridge driver).OPEN_LIMIT_SWITCH, CLOSE_LIMIT_SWITCH: Pins for the limit switches that detect if the door is fully open or closed.HX711_DOUT_PIN, HX711_SCK_PIN: Data Out (DT) and Serial Clock (SCK) pins for communication with the HX711 amplifier.TARE_BUTTON_PIN, MODE_BUTTON_PIN, UP_BUTTON_PIN, DOWN_BUTTON_PIN: Pins for the Tare (zero the scale), Mode (enter/navigate settings), Up, and Down buttons.// --- Configuration Constants ---
const unsigned long PRESENCE_TIMEOUT = 15000; // ms: Time door stays open after last presence
const int DISTANCE_THRESHOLD = 30; // cm: Distance to detect presence/obstacle
const int SENSOR_READ_INTERVAL = 100; // ms: How often to read sensors
const int MOTOR_START_DELAY = 50; // ms: Small delay after starting motor
const int LCD_UPDATE_INTERVAL = 500; // ms: How often to update LCD
const unsigned long DEBOUNCE_DELAY = 50; // ms: Debounce time for buttons
const unsigned long MANUAL_OVERRIDE_HOLD_TIME = 1000; // ms: Hold time for manual override buttons
const unsigned long MOTOR_TIMEOUT_DURATION = 6000; // <<< NEW: 6 seconds motor run timeoutConfiguration Constants: Fixed values defining the system's behavior.
PRESENCE_TIMEOUT: Time (in milliseconds) the door stays open after presence is no longer detected (15 seconds).DISTANCE_THRESHOLD: Distance (in centimeters) below which the ultrasonic sensor detects presence or an obstacle (30 cm).SENSOR_READ_INTERVAL: How often (in milliseconds) the sensors (ultrasonic, limits, weight) are read (every 100 ms).MOTOR_START_DELAY: A brief pause (in milliseconds) after starting the motor (50 ms). Used in manual override.LCD_UPDATE_INTERVAL: How often (in milliseconds) the information on the LCD is refreshed (every 500 ms).DEBOUNCE_DELAY: Time (in milliseconds) for button debouncing, preventing multiple triggers from electrical noise on a single press (50 ms).MANUAL_OVERRIDE_HOLD_TIME: Time (in milliseconds) a button (Up/Down) must be held to activate manual override during restricted periods (1 second).MOTOR_TIMEOUT_DURATION: Maximum time (in milliseconds) the motor can run continuously without hitting a limit switch before triggering an error state (6 seconds).// --- Weight Unit Enum ---
enum WeightUnit { UNIT_GRAMS, UNIT_OUNCES };
// --- Settings Variables (loaded from EEPROM, with defaults) ---
int restrictedHourStart = 22; // Default restriction start time (10 PM)
int restrictedHourEnd = 6; // Default restriction end time (6 AM)
WeightUnit currentUnit = UNIT_GRAMS; // Default weight unitEnum and Settings Variables:
enum WeightUnit: Defines a custom data type WeightUnit that can only hold the values UNIT_GRAMS or UNIT_OUNCES. Used to represent the selected weight unit.restrictedHourStart, restrictedHourEnd: Integer variables to store the start and end hour of the restricted period. They have default values (22:00 and 06:00) but will be loaded from EEPROM if valid data exists.currentUnit: Variable of type WeightUnit storing the current weight unit. Defaults to UNIT_GRAMS. Also loaded/saved to EEPROM.// --- HX711 Calibration ---
// !!! REPLACE THIS VALUE WITH YOUR OWN OBTAINED FROM CALIBRATION (using grams) !!!
const float CALIBRATION_FACTOR = 419.0; // Example value - MUST BE CHANGED!HX711 Calibration:
CALIBRATION_FACTOR: CRITICAL! This floating-point value converts the raw reading from the HX711 into grams. You MUST determine this value for your specific load cell by placing a known weight on it and adjusting the factor until the reading is correct. The value 419.0 is just an example.// --- EEPROM Addresses & Check Value ---
#define EEPROM_ADDR_R_START 0
#define EEPROM_ADDR_R_END (EEPROM_ADDR_R_START + sizeof(int))
#define EEPROM_ADDR_UNIT (EEPROM_ADDR_R_END + sizeof(int))
#define EEPROM_ADDR_CHECK (EEPROM_ADDR_UNIT + sizeof(WeightUnit))
#define EEPROM_CHECK_VALUE 0x5B // Use a consistent check valueEEPROM Addresses & Check Value: Defines locations within the EEPROM memory where data will be stored.
EEPROM_ADDR_R_START: Address to store restrictedHourStart (starting at 0).EEPROM_ADDR_R_END: Address for restrictedHourEnd (calculated by adding the size of an int to the previous address).EEPROM_ADDR_UNIT: Address for currentUnit (calculated by adding the size of an int to the previous).EEPROM_ADDR_CHECK: Address to store a check value (calculated by adding the size of WeightUnit to the previous).EEPROM_CHECK_VALUE: A "magic" value (0x5B) stored at EEPROM_ADDR_CHECK. On startup, this value is read; if it matches, the data saved in EEPROM is considered valid. If not, default values are used, and everything (including the check value) is saved.// --- HX711 Object Initialization ---
HX711_ADC LoadCell(HX711_DOUT_PIN, HX711_SCK_PIN);HX711 Object Initialization:
HX711_ADC LoadCell(...): Creates an object LoadCell to interact with the HX711, telling it which Data (DT) and Clock (SCK) pins are connected.// --- System States (Door) ---
enum State {
INIT_OPENING, INIT_CLOSING, CLOSED, OPENING, OPEN, CLOSING, OBSTACLE_DETECTED,
ERROR_MOTOR_TIMEOUT // Error State
};
State currentState = CLOSED;System States:
enum State: Defines a custom data type State to represent the different operational states of the door (INIT_OPENING: Initializing opening, INIT_CLOSING: Initializing closing, CLOSED: Door is closed, OPENING: Door is currently opening, OPEN: Door is fully open, CLOSING: Door is currently closing, OBSTACLE_DETECTED: Obstacle found while closing (re-opening), ERROR_MOTOR_TIMEOUT: Error due to motor running too long).State currentState = CLOSED;: Declares a variable currentState of type State to hold the system's current state. It's initialized to CLOSED.// --- Global Variables ---
unsigned long lastPresenceTime = 0;
unsigned long lastSensorReadTime = 0;
unsigned long lastLcdUpdateTime = 0;
long currentDistance = -1;
bool openLimitReached = false;
bool closeLimitReached = false;
DateTime now;
float currentWeight = 0.0; // Stores weight in GRAMS
bool hx711_ready = false;
// Settings Mode
bool isInSettingsMode = false;
enum SettingField { NONE, SET_HOUR, SET_MINUTE, SET_RESTRICT_START, SET_RESTRICT_END, SET_UNIT }; // Uses only fields from arduinoIngles.TXT
SettingField currentSettingField = NONE;
int tempHour = 0; int tempMinute = 0; int tempR_Start = 0; int tempR_End = 0; WeightUnit tempUnit = UNIT_GRAMS;
// Button Debounce
bool lastTareState = HIGH; unsigned long lastTareDebounceTime = 0;
// ... (similar variables for Mode, Up, Down buttons)
bool stableTareState = HIGH; bool stableModeState = HIGH; bool stableUpState = HIGH; bool stableDownState = HIGH;
bool tarePressed = false; bool modePressed = false; bool upPressed = false; bool downPressed = false;
// Long Press
unsigned long upButtonPressStartTime = 0;
unsigned long downButtonPressStartTime = 0;
// Motor Timeout
unsigned long motorStartTime = 0; // <<< NEW: Timestamp when motor startsGlobal Variables: Variables accessible from anywhere in the code.
lastPresenceTime: Stores the millis() timestamp of the last time presence was detected. Used for the PRESENCE_TIMEOUT.lastSensorReadTime: Stores the timestamp of the last sensor reading cycle. Used for periodic reads (SENSOR_READ_INTERVAL).lastLcdUpdateTime: Stores the timestamp of the last LCD update. Used for periodic updates (LCD_UPDATE_INTERVAL).currentDistance: Stores the last distance measured by the ultrasonic sensor (-1 if invalid).openLimitReached, closeLimitReached: Boolean flags indicating if the open or close limit switch is currently active (pressed).DateTime now: An object that will hold the current date and time read from the RTC.currentWeight: Stores the current measured weight (always in GRAMS internally).hx711_ready: Boolean flag indicating if the HX711 was successfully initialized and is ready to provide readings.isInSettingsMode: Boolean flag indicating if the system is currently in the configuration menu.enum SettingField: Defines the different fields that can be edited in settings mode (NONE, SET_HOUR, SET_MINUTE, SET_RESTRICT_START, SET_RESTRICT_END, SET_UNIT).currentSettingField: Variable indicating which specific field is currently selected for editing in settings mode.tempHour, tempMinute, tempR_Start, tempR_End, tempUnit: Temporary variables to hold values while they are being modified in settings mode, before being saved permanently.last...State, last...DebounceTime, stable...State): Used by the readButtons function to implement debouncing for each button. They store the last raw state read, the timestamp of the last detected change, and the stable state after the debounce delay....Pressed): Boolean flags set to true for one loop cycle when a debounced button press is detected....ButtonPressStartTime): Store the millis() timestamp when the UP or DOWN button was pressed. Used to detect if they are held down for MANUAL_OVERRIDE_HOLD_TIME.motorStartTime: Stores the millis() timestamp when the motor started moving. Used for the MOTOR_TIMEOUT_DURATION. Set to 0 when the motor stop.// --- Initial Setup ---
void setup() {
Serial.begin(9600);
Serial.println(F("Starting System V5.3 (Motor Timeout Added)...")); // Version update
Wire.begin();
// Init LCD
lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print(F("Initializing Sys..."));
Serial.println(F("LCD initialized."));
// Init RTC
if (!rtc.begin()) { /* Error handling */ }
Serial.println(F("DS3231 RTC found."));
if (rtc.lostPower()) { /* Handle power loss */ }
// ... (RTC messages)
// Init HX711
Serial.println(F("Initializing HX711...")); // ... (HX711 messages)
LoadCell.begin(); LoadCell.start(2000, true); // Start HX711, tare on startup
if (LoadCell.getTareTimeoutFlag()) { /* Handle tare timeout */ }
else { LoadCell.setCalFactor(CALIBRATION_FACTOR); /* Set factor */ hx711_ready = true; }
// ... (HX711 messages)
// Configure Pins
pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); // ... (set all pin modes)
pinMode(OPEN_LIMIT_SWITCH, INPUT_PULLUP); // ... (using internal pull-ups for switches/buttons)
// Initialize button states
stableTareState=digitalRead(TARE_BUTTON_PIN); // ... (read initial stable states)
lastTareState=stableTareState; // ... (sync last raw states)
// Load config from EEPROM
loadConfigFromEEPROM();
stopMotor(); // Ensure motor is stopped initially
readLimitSwitches(); // Read initial limit switch states
// Determine initial door state based on limit switches
if (closeLimitReached) { currentState = CLOSED; }
else if (openLimitReached) { currentState = OPEN; }
else { currentState = INIT_OPENING; openDoor(); /* Start opening */ }
updateLcdDisplay(); // Show initial status on LCD
} // End setupsetup() Function: This function runs once when the Arduino powers up or resets.
Serial.begin(9600): Starts serial communication at 9600 baud for debugging messages.Wire.begin(): Initializes the I2C communication bus.lcd.init(), turns on backlight lcd.backlight(), shows an init message).rtc.begin()). Includes error checking (if (!rtc.begin())) and checks if the RTC lost power (rtc.lostPower()) since the last run (useful for prompting time setting).LoadCell.begin(), LoadCell.start()). The start(2000, true) attempts an initial tare with a 2-second timeout. It checks for tare timeout, sets the crucial CALIBRATION_FACTOR, and sets the hx711_ready flag if successful.OUTPUT or an INPUT. INPUT_PULLUP is used for buttons and limit switches, enabling the internal pull-up resistor (meaning the pin reads HIGH unless connected to ground, typically when the switch/button is pressed).stable...State and last...State variables for the debounce logic.loadConfigFromEEPROM(): Calls the function to load saved settings (restricted hours, unit) from EEPROM.stopMotor(): Ensures the motor is initially off.readLimitSwitches(): Reads the status of the limit switches at startup.CLOSED. If the open switch is active, state is OPEN. If neither is active (door is partially open), it enters the INIT_OPENING state and starts the openDoor() sequence to try and reach a known position (it will then transition to INIT_CLOSING).updateLcdDisplay(): Updates the LCD with the initial status.// --- Main Loop ---
void loop() {
now = rtc.now(); // Get current time
readButtons(); // Read button states (handles debounce)
if (isInSettingsMode) {
handleSettingsMode(); // Handle settings menu logic
} else if (currentState == ERROR_MOTOR_TIMEOUT) {
// In error state, do nothing actively here (display handled below)
; // Placeholder
} else {
// --- Normal Operation Mode ---
unsigned long currentMillis = millis();
if (tarePressed) { tareScale(); } // If Tare button pressed, tare the scale
// Handle Manual Override (check conditions: restricted time, door state, button hold)
bool override_action_taken = false;
if (isRestrictedTimeNow() && currentState == CLOSED && stableUpState == LOW /* ... and hold time check */) {
// ... Open door manually ...
override_action_taken = true;
} else if (isRestrictedTimeNow() && currentState == OPEN && stableDownState == LOW /* ... and hold time check */) {
// ... Close door manually ...
override_action_taken = true;
}
// Read Sensors periodically (if no override action just happened)
if (!override_action_taken) {
if (currentMillis - lastSensorReadTime >= SENSOR_READ_INTERVAL) {
currentDistance = getDistance();
readLimitSwitches();
readWeight();
lastSensorReadTime = currentMillis;
}
} else { lastSensorReadTime = currentMillis; } // Reset timer if override occurred
// Handle State Machine (core door logic)
handleStateMachine();
// Check for entering settings mode
if (modePressed) {
isInSettingsMode = true; currentSettingField = SET_HOUR; // Enter settings
// ... Initialize temp variables ...
stopMotor(); // Stop motor when entering settings
updateLcdDisplay(); // Update display immediately
}
} // End Normal Mode
// Update LCD periodically
if (millis() - lastLcdUpdateTime >= LCD_UPDATE_INTERVAL) {
updateLcdDisplay();
lastLcdUpdateTime = millis();
}
} // End looploop() Function: This function runs repeatedly after setup() completes.
now = rtc.now(): Gets the current date and time from the RTC object.readButtons(): Calls the function to read all buttons, handle debouncing, and set the ...Pressed flags.isInSettingsMode is true, it calls handleSettingsMode() to manage the settings menu.currentState is ERROR_MOTOR_TIMEOUT, it currently does nothing active within the loop (the LCD update handles displaying the error).millis() time.tarePressed flag is set; if so, calls tareScale().stable...State == LOW) for longer than MANUAL_OVERRIDE_HOLD_TIME? If conditions met, initiates manual open/close, sets override_action_taken flag.SENSOR_READ_INTERVAL has passed since the last read. If yes, it calls getDistance(), readLimitSwitches(), readWeight(), and updates lastSensorReadTime.handleStateMachine(): Calls the core function that manages door state transitions based on sensor inputs and time.modePressed flag is set. If yes, it enters Settings Mode (isInSettingsMode = true), sets the initial field to edit, copies current settings to temporary variables, stops the motor, and updates the LCD immediately.LCD_UPDATE_INTERVAL has passed since the last update. If yes, calls updateLcdDisplay() and updates lastLcdUpdateTime.// Reads buttons, handles debounce, sets flags, tracks long press start times
void readButtons() {
tarePressed = false; modePressed = false; upPressed = false; downPressed = false; // Reset flags
unsigned long now_ms = millis();
// --- TARE --- (Example for one button, others are similar)
bool rT = digitalRead(TARE_BUTTON_PIN); // Read raw pin state
if (rT != lastTareState) { lastTareDebounceTime = now_ms; } // If state changed, reset debounce timer
if ((now_ms - lastTareDebounceTime) > DEBOUNCE_DELAY) { // If debounce delay passed
if (rT != stableTareState) { // If stable state needs update
stableTareState = rT; // Update stable state
if (stableTareState == LOW) { // If newly pressed (stable LOW)
tarePressed = true; // Set the flag for this loop cycle
Serial.println(F(">>> TARE Pressed <<<"));
}
}
}
lastTareState = rT; // Store current raw state for next comparison
// --- MODE --- (Similar logic)
// --- UP --- (Similar logic, plus long press start time)
// ...
if (stableUpState == LOW) { upPressed = true; /*...*/ upButtonPressStartTime = now_ms; } // Record press start time
else { upButtonPressStartTime = 0; } // Reset start time on release
// ...
// --- DOWN --- (Similar logic, plus long press start time)
// ...
}readButtons() Function: Reads all buttons, applies debouncing, and sets flags.
...Pressed flags to false at the beginning.millis()).digitalRead). Remember INPUT_PULLUP means LOW is pressed.rT, rM, etc.) to the last recorded raw state (lastTareState, etc.). If different, it means a change occurred (or noise), so the last...DebounceTime is updated to the current time.last...DebounceTime is greater than DEBOUNCE_DELAY. Only proceeds if enough time has passed without changes, ensuring the signal is stable.rT) is different from the currently accepted stableTareState.stableTareState is updated to the new stable value (rT).stableTareState is LOW (meaning a stable press was just confirmed), the corresponding ...Pressed flag (tarePressed) is set to true for this one iteration of the loop(). A debug message is printed.LOW, it also records the current time in upButtonPressStartTime or downButtonPressStartTime. If the stable state becomes HIGH (released), the corresponding start time is reset to 0.lastTareState (raw state) is updated for the next comparison.// Performs tare operation
void tareScale() {
if (!hx711_ready) { /*...*/ return; } // Check HX711 status
Serial.println(F("Taring scale..."));
lcd.setCursor(0, 0); lcd.print(F("Taring... "));
LoadCell.tare(); // Perform tare using the library function
currentWeight = 0.0; // Reset internal weight variable
delay(100); // Small delay
updateLcdDisplay(); // Update LCD immediately
Serial.println(F("Scale tared."));
}tareScale() Function: Executes the tare (zeroing) operation for the load cell.
hx711_ready flag is true. If not, prints an error and returns.LoadCell.tare() which commands the HX711 library to perform the tare operation (find the current zero offset).currentWeight variable to 0.0.delay(100).updateLcdDisplay() to show the result immediately.// Handles settings menu navigation and value changes
void handleSettingsMode() {
// MODE Button: Cycle or Save/Exit
if (modePressed) {
int currentIdx = (int)currentSettingField; // Get current field index
int nextIdx = currentIdx + 1; // Calculate next index
if (nextIdx > SET_UNIT) { // If currently on the last item (SET_UNIT)
// --- Save and Exit ---
Serial.println(F("Saving configuration and exiting..."));
now = rtc.now(); // Get current time for setting RTC
// Update RTC time with tempHour/tempMinute
rtc.adjust(DateTime(now.year(), now.month(), now.day(), tempHour, tempMinute, now.second()));
// Update global settings variables from temp variables
restrictedHourStart = tempR_Start; restrictedHourEnd = tempR_End;
currentUnit = tempUnit;
saveConfigToEEPROM(); // Save relevant settings to EEPROM
currentSettingField = NONE; // Reset current field
isInSettingsMode = false; // Exit settings mode
Serial.println(F("Exited Settings Mode."));
return; // Exit function immediately
} else {
// Move to next field
currentSettingField = (SettingField)nextIdx; // Cast next index back to enum type
Serial.print(F("New field selected: ")); Serial.println(getSettingFieldString(currentSettingField));
updateLcdDisplay(); // Update LCD immediately
}
} // End if modePressed
// UP/DOWN Buttons: Modify temporary value
if (upPressed || downPressed) {
int delta = upPressed ? 1 : -1; // Determine change direction (+1 or -1)
bool valueChanged = true;
switch (currentSettingField) { // Check which field is being edited
case SET_HOUR: tempHour = (tempHour + delta + 24) % 24; break; // Adjust hour (0-23 wrap)
case SET_MINUTE: tempMinute = (tempMinute + delta + 60) % 60; break; // Adjust minute (0-59 wrap)
case SET_RESTRICT_START: tempR_Start = (tempR_Start + delta + 24) % 24; break; // Adjust start hour
case SET_RESTRICT_END: tempR_End = (tempR_End + delta + 24) % 24; break; // Adjust end hour
case SET_UNIT: tempUnit = (tempUnit == UNIT_GRAMS) ? UNIT_OUNCES : UNIT_GRAMS; break; // Toggle unit
default: valueChanged = false; break; // No action if field is NONE
}
if (valueChanged) {
Serial.print(F(" Temp value updated for: ")); Serial.println(getSettingFieldString(currentSettingField));
updateLcdDisplay(); // Update LCD immediately if value changed
}
}
}handleSettingsMode() Function: Manages user interaction within the settings menu.
currentSettingField.SET_UNIT), it means the user pressed MODE on the last item, so it triggers the "Save and Exit" sequence:tempHour and tempMinute (rtc.adjust).tempR_Start, tempR_End, tempUnit) into the main global variables (restrictedHourStart, etc.).saveConfigToEEPROM() to persist the restriction times and unit setting.currentSettingField to NONE.isInSettingsMode to false.currentSettingField to the next field in the sequence and updates the LCD immediately.delta = 1 for UP, -1 for DOWN).switch statement based on the currentSettingField.tempHour, tempMinute, etc.). The modulo operator (%) is used with the range (e.g., + 24) % 24) to ensure values wrap around correctly (e.g., 23 + 1 becomes 0, 0 - 1 becomes 23).SET_UNIT, it simply toggles the tempUnit between UNIT_GRAMS and UNIT_OUNCES.// Updates the LCD display based on current mode (Normal or Settings)
void updateLcdDisplay() {
char lineBuffer[21], floatBuffer[10]; // Buffers for formatting strings
const float G_TO_OZ_FACTOR = 0.035274; // Conversion factor
// *** Handle ERROR_MOTOR_TIMEOUT state display ***
if (currentState == ERROR_MOTOR_TIMEOUT) {
lcd.clear();
lcd.setCursor(0, 0); lcd.print(F("!!! MOTOR ERROR !!! "));
lcd.setCursor(0, 1); lcd.print(F(" Movement Timeout "));
lcd.setCursor(0, 2); lcd.print(F(" Check Door / Limit "));
lcd.setCursor(0, 3); lcd.print(F(" Switches! "));
return; // Stop further display updates in error state
}
lcd.clear(); // Clear display for normal/settings redraw
if (isInSettingsMode) {
// --- Settings Mode Interface ---
lcd.setCursor(0, 0); lcd.print(F("SETUP:")); lcd.print(getSettingFieldString(currentSettingField)); // Line 0: Title + Field
// Line 1: Time (temp values) + indicator '<' if editing Hour/Minute
lcd.setCursor(0, 1); snprintf(lineBuffer, sizeof(lineBuffer), "Time: %02d:%02d %c", tempHour, tempMinute, (currentSettingField == SET_HOUR || currentSettingField == SET_MINUTE) ? '<' : ' '); lcd.print(lineBuffer);
// Line 2: Restriction Times (temp values) + indicator '<' if editing Start/End
lcd.setCursor(0, 2); snprintf(lineBuffer, sizeof(lineBuffer), "Restr: %02d - %02d %c", tempR_Start, tempR_End, (currentSettingField == SET_RESTRICT_START || currentSettingField == SET_RESTRICT_END) ? '<' : ' '); lcd.print(lineBuffer);
// Line 3: Unit (temp value) + indicator '<' if editing Unit
lcd.setCursor(0, 3); snprintf(lineBuffer, sizeof(lineBuffer), "Unit: %-7s %c", getUnitString(tempUnit), (currentSettingField == SET_UNIT) ? '<' : ' '); lcd.print(lineBuffer);
} else {
// --- Normal Operation Interface ---
// Line 0: Weight
lcd.setCursor(0, 0);
if (hx711_ready) {
float dW=currentWeight; const char* uS=" g"; int p=1; // Default grams, 1 decimal place
if(currentUnit==UNIT_OUNCES){dW*=G_TO_OZ_FACTOR; uS=" oz"; p=2;} // If ounces, convert & change suffix/precision
dtostrf(dW,1,p,floatBuffer); // Convert float to string
snprintf(lineBuffer,sizeof(lineBuffer),"Weight:%-7s%s",floatBuffer,uS); // Format line
} else { snprintf(lineBuffer, sizeof(lineBuffer), "Weight: ---.- "); } // Show placeholder if not ready
lcd.print(lineBuffer); // Print weight line
// ... (Pad rest of line with spaces - this happens for all lines) ...
// Line 1: Time (current time from 'now')
snprintf(lineBuffer,sizeof(lineBuffer),"Time: %02d:%02d:%02d",now.hour(),now.minute(),now.second()); lcd.setCursor(0,1); lcd.print(lineBuffer); //...pad...
// Line 2: Status (current door state)
snprintf(lineBuffer, sizeof(lineBuffer), "Status: %-12s", getStateString(currentState)); lcd.setCursor(0, 2); lcd.print(lineBuffer); //...pad...
// Line 3: Distance or Restriction Status
lcd.setCursor(0,3);
if(isRestrictedTimeNow()){ // If restricted time
snprintf(lineBuffer,sizeof(lineBuffer),"Restr: %02d-%02dh ACTIVE", restrictedHourStart, restrictedHourEnd);
} else { // If not restricted time, show distance
if(currentDistance>=0 && currentDistance<999){ // If valid distance
snprintf(lineBuffer,sizeof(lineBuffer),"Dist: %3ld cm ",currentDistance);
} else { // If invalid distance
snprintf(lineBuffer,sizeof(lineBuffer),"Dist: --- cm ");
}
}
lcd.print(lineBuffer); //...pad...
}
}updateLcdDisplay() Function: Controls what is shown on the 20x4 LCD screen.
lineBuffer, floatBuffer) for formatting text. Defines the grams-to-ounces conversion factor.currentState is ERROR_MOTOR_TIMEOUT. If so, it clears the LCD and displays a specific 4-line error message about the motor timeout, then returns immediately, skipping the rest of the display logic.lcd.clear()) before redrawing in normal or settings mode.isInSettingsMode is true:getSettingFieldString).tempHour:tempMinute). Adds a '<' character at the end if SET_HOUR or SET_MINUTE is the currentSettingField.tempR_Start - tempR_End). Adds '<' if editing start or end hour.getUnitString). Adds '<' if editing the unit.hx711_ready. If yes, it gets the currentWeight. If currentUnit is Ounces, it converts the weight using G_TO_OZ_FACTOR and sets the unit string to " oz" and precision to 2 decimal places; otherwise, it uses " g" and 1 decimal place. It uses dtostrf to convert the float weight to a string (floatBuffer) and snprintf to format the final line "Weight: VALUE UNIT". If HX711 is not ready, it displays "Weight: ---.- ". The line is padded with spaces to clear previous content.now.hour(), minute(), second()) into "Time: HH:MM:SS" using snprintf. Padded.currentState (using getStateString). Padded.isRestrictedTimeNow(). If true, displays "Restr: HH-HHh ACTIVE". If false, it checks if currentDistance is valid (0-998). If valid, displays "Dist: VALUE cm". If invalid, displays "Dist: --- cm". Padded.// Converts SettingField enum to a printable string
const char* getSettingFieldString(SettingField sf) {
switch (sf) { /* ... cases returning string literals like "HOUR", "MINUTE", etc. ... */ }
}
// Converts WeightUnit enum to a printable string (English)
const char* getUnitString(WeightUnit u) {
return (u == UNIT_GRAMS) ? "GRAMS" : "OUNCES";
}
getSettingFieldString() & getUnitString() Functions: Helper functions to convert enum values into human-readable strings for display.
getSettingFieldString: Takes a SettingField value and returns the corresponding string (e.g., "HOUR", "R.START").getUnitString: Takes a WeightUnit value and returns either "GRAMS" or "OUNCES".// Reads weight from HX711
void readWeight() { if (hx711_ready) { if (LoadCell.update()) { currentWeight = LoadCell.getData(); } } }
// Reads limit switches
void readLimitSwitches() { openLimitReached=(digitalRead(OPEN_LIMIT_SWITCH)==LOW); closeLimitReached=(digitalRead(CLOSE_LIMIT_SWITCH)==LOW); }readWeight() & readLimitSwitches() Functions: Read data from specific sensors.
readWeight: Checks if hx711_ready. If yes, calls LoadCell.update() (which checks if new data is available). If data is available, it updates the global currentWeight by calling LoadCell.getData().readLimitSwitches: Reads the digital state of the OPEN_LIMIT_SWITCH and CLOSE_LIMIT_SWITCH pins. Since INPUT_PULLUP is used, the switch is active (pressed) when the pin reads LOW. The boolean flags openLimitReached and closeLimitReached are updated accordingly.// Checks if current time is within restricted period
bool isRestrictedTimeNow() {
int cH=now.hour(); // Get current hour
bool r=false;
if(restrictedHourStart > restrictedHourEnd){ // Case 1: Restriction crosses midnight (e.g., 22:00 to 06:00)
r=(cH>=restrictedHourStart || cH<restrictedHourEnd); // Restricted if current hour >= start OR < end
} else { // Case 2: Restriction within the same day (or start == end)
if(restrictedHourStart == restrictedHourEnd) return false; // If start == end, no restriction
r=(cH>=restrictedHourStart && cH<restrictedHourEnd); // Restricted if current hour >= start AND < end
}
return r;
}sRestrictedTimeNow() Function: Determines if the current time falls within the configured restricted period.
cH) from the global now object.true if the current time is restricted, false otherwise.// Main state machine for door operation
void handleStateMachine() {
// Don't run state machine if in settings or error state
if (isInSettingsMode || currentState == ERROR_MOTOR_TIMEOUT) return;
State previousState = currentState; // Store state before processing
// --- Motor Timeout Check ---
// Check only if motor is supposed to be moving and timer is running
if (motorStartTime != 0 && (currentState == OPENING || currentState == CLOSING || currentState == INIT_OPENING || currentState == INIT_CLOSING)) {
if (millis() - motorStartTime > MOTOR_TIMEOUT_DURATION) { // If timeout exceeded
Serial.println(F("!!! MOTOR TIMEOUT !!! State was: ")); Serial.println(getStateString(currentState));
stopMotor(); // Stop motor (also resets motorStartTime)
currentState = ERROR_MOTOR_TIMEOUT; // Enter error state
updateLcdDisplay(); // Update LCD immediately to show error
lastLcdUpdateTime = millis();
return; // Exit state machine handling for this cycle
}
}
// --- Normal State Transitions ---
switch (currentState) {
case INIT_OPENING: // Initializing: trying to open fully first
if (openLimitReached) { /*...*/ stopMotor(); currentState=INIT_CLOSING; closeDoor(); }
break;
case INIT_CLOSING: // Initializing: now trying to close fully
if (closeLimitReached) { /*...*/ stopMotor(); currentState=CLOSED; }
break;
case CLOSED: // Door is closed
if (currentDistance>=0 && currentDistance<DISTANCE_THRESHOLD) { // If presence detected
if (!isRestrictedTimeNow()) { // And not restricted time
/*...*/ currentState=OPENING; openDoor(); // Start opening
} else { /* Restricted: do nothing (maybe log?) */ }
}
break;
case OPENING: // Door is opening
if (openLimitReached) { /*...*/ stopMotor(); currentState=OPEN; } // Stop when open limit hit
break;
case OPEN: // Door is open
if (currentDistance>=0 && currentDistance<DISTANCE_THRESHOLD) { lastPresenceTime=millis(); } // Update last presence time if detected
if (millis()-lastPresenceTime >= PRESENCE_TIMEOUT) { // If timeout passed since last presence
/*...*/ currentState=CLOSING; closeDoor(); // Start closing
}
break;
case CLOSING: // Door is closing
if (currentDistance>=0 && currentDistance<DISTANCE_THRESHOLD) { // If obstacle detected
/*...*/ stopMotor(); currentState=OBSTACLE_DETECTED; openDoor(); // Stop, change state, start re-opening
} else if (closeLimitReached) { // If close limit hit
/*...*/ stopMotor(); currentState=CLOSED; // Stop, change state to closed
}
break;
case OBSTACLE_DETECTED: // Door is re-opening after obstacle
if (openLimitReached) { // If fully re-opened
/*...*/ stopMotor(); currentState=OPEN; lastPresenceTime=millis(); // Stop, change state to open, reset presence timer
}
break;
// ERROR_MOTOR_TIMEOUT case intentionally empty here - handled at start
}
// If the state changed (and it wasn't to the error state already handled), update LCD
if (currentState != previousState) {
Serial.print(F("State Change: ")); Serial.println(getStateString(currentState));
updateLcdDisplay();
lastLcdUpdateTime = millis();
}
}handleStateMachine() Function: The core logic that determines the door's actions based on its current state and inputs.
ERROR_MOTOR_TIMEOUT state.previousState to detect changes later.motorStartTime != 0) AND if the currentState is one where the motor should be moving (OPENING, CLOSING, INIT_...).millis() - motorStartTime) has exceeded MOTOR_TIMEOUT_DURATION.stopMotor() (which also resets motorStartTime), sets currentState to ERROR_MOTOR_TIMEOUT, updates the LCD immediately to show the error, and returns, preventing further state logic in this cycle.switch statement based on the currentState:INIT_OPENING: If the open limit switch is reached, stop the motor, change state to INIT_CLOSING, and start closing (closeDoor()).INIT_CLOSING: If the close limit switch is reached, stop the motor and change state to CLOSED. The system is now initialized.CLOSED: If presence is detected (currentDistance < threshold) AND it's not restricted time (!isRestrictedTimeNow()), change state to OPENING and start opening (openDoor()).OPENING: If the open limit switch is reached, stop the motor and change state to OPEN.OPEN: If presence is detected, update lastPresenceTime to the current time. If the time elapsed since lastPresenceTime exceeds PRESENCE_TIMEOUT, change state to CLOSING and start closing (closeDoor()).CLOSING: If presence (an obstacle) is detected, stop the motor, change state to OBSTACLE_DETECTED, and start opening again (openDoor()). If no obstacle and the close limit switch is reached, stop the motor and change state to CLOSED.OBSTACLE_DETECTED (Door is re-opening): If the open limit switch is reached, stop the motor, change state back to OPEN, and reset lastPresenceTime (treating the obstacle detection like a new presence event).ERROR_MOTOR_TIMEOUT: No transitions are defined from this state within the switch (it's handled at the beginning and in the main loop).switch statement, if the currentState is different from previousState, it means a state transition occurred. It prints the new state to Serial and calls updateLcdDisplay() to show the change immediately.// Converts State enum to a printable string (English, No F() in return)
// Added ERROR_MOTOR_TIMEOUT case
const char* getStateString(State s) {
switch(s){
case INIT_OPENING: return "Init Opening"; // ... other cases ...
case ERROR_MOTOR_TIMEOUT: return "ERROR TIMEOUT";
default: return "Unknown";
}
}getStateString() Function: Returns a user-friendly string corresponding to a given State enum value. Used for display on the LCD and Serial monitor. Includes the string for the new ERROR_MOTOR_TIMEOUT state.
// Measures distance using ultrasonic sensor
long getDistance() {
digitalWrite(TRIG_PIN,LOW);delayMicroseconds(2); // Ensure TRIG is low
digitalWrite(TRIG_PIN,HIGH);delayMicroseconds(10); // Send 10us HIGH pulse
digitalWrite(TRIG_PIN,LOW); // Set TRIG low again
// Measure time for ECHO pulse to return (timeout 25000us = 25ms)
long d=pulseIn(ECHO_PIN,HIGH,25000);
// Calculate distance: (duration * speed_of_sound / 2)
// speed_of_sound = 0.0343 cm/us approx.
return (d==0) ? 999 : d*0.0343/2.0; // Return 999 if timeout (d=0), else calculated distance
}getDistance() Function: Measures distance using the ultrasonic sensor (HC-SR04 type logic).
TRIG_PIN.pulseIn(ECHO_PIN, HIGH, 25000) to measure the time (in microseconds) it takes for the echo pulse to return to the ECHO_PIN. It includes a timeout of 25000 microseconds (25 ms).pulseIn times out, it returns 0. The function checks for this and returns 999 (an invalid distance value).d is received, it calculates the distance in centimeters using the formula: distance = (duration * speed_of_sound) / 2. The speed of sound is approximately 0.0343 cm/microsecond. The division by 2 accounts for the sound traveling to the object and back.// Motor control functions - Added motorStartTime handling
void openDoor() {
digitalWrite(MOTOR_IN1,HIGH); digitalWrite(MOTOR_IN2,LOW); // Set pins for opening direction
motorStartTime = millis(); // <<< Start timer
Serial.println(F("Motor: Opening"));
}
void closeDoor() {
digitalWrite(MOTOR_IN1,LOW); digitalWrite(MOTOR_IN2,HIGH); // Set pins for closing direction
motorStartTime = millis(); // <<< Start timer
Serial.println(F("Motor: Closing"));
}
void stopMotor() {
digitalWrite(MOTOR_IN1,LOW); digitalWrite(MOTOR_IN2,LOW); // Set both pins LOW to stop (brake/coast depends on H-bridge)
motorStartTime = 0; // <<< Stop timer
Serial.println(F("Motor: Stopped"));
}Motor Control Functions (openDoor, closeDoor, stopMotor): Simple functions to control the motor direction and state, and manage the motor timeout timer.
openDoor: Sets MOTOR_IN1 HIGH and MOTOR_IN2 LOW (assuming this combination drives the motor in the "open" direction). Records the start time in motorStartTime. Prints status.closeDoor: Sets MOTOR_IN1 LOW and MOTOR_IN2 HIGH (assuming this drives the motor in the "close" direction). Records the start time in motorStartTime. Prints status.stopMotor: Sets both MOTOR_IN1 and MOTOR_IN2 LOW (this usually stops the motor; behavior might be brake or coast depending on the H-bridge). Resets motorStartTime to 0, effectively stopping the timeout timer. Prints status.// Loads configuration from EEPROM
void loadConfigFromEEPROM() {
Serial.println(F("Loading config from EEPROM..."));
byte checkValue = EEPROM.read(EEPROM_ADDR_CHECK); // Read the check value
if (checkValue == EEPROM_CHECK_VALUE) { // If check value matches
// Read saved values into global variables
EEPROM.get(EEPROM_ADDR_R_START, restrictedHourStart);
EEPROM.get(EEPROM_ADDR_R_END, restrictedHourEnd);
EEPROM.get(EEPROM_ADDR_UNIT, currentUnit);
// Sanity check for loaded unit
if (currentUnit != UNIT_GRAMS && currentUnit != UNIT_OUNCES) { currentUnit = UNIT_GRAMS; /*...*/ }
Serial.println(F("Configuration loaded."));
} else { // If check value doesn't match (invalid/first run)
Serial.println(F("EEPROM invalid. Saving defaults."));
// Ensure global variables have default values (redundant but safe)
restrictedHourStart = 22; restrictedHourEnd = 6; currentUnit = UNIT_GRAMS;
saveConfigToEEPROM(); // Save these defaults to EEPROM
EEPROM.write(EEPROM_ADDR_CHECK, EEPROM_CHECK_VALUE); // Write the correct check value now
Serial.println(F("Default values saved."));
}
// Print loaded/default values for confirmation
Serial.print(F(" Restr Start: ")); Serial.println(restrictedHourStart);
// ... (print other loaded values) ...
}loadConfigFromEEPROM() Function: Reads settings stored in EEPROM at startup.
EEPROM_ADDR_CHECK.checkValue with the predefined EEPROM_CHECK_VALUE.EEPROM.get() to read the values for restrictedHourStart, restrictedHourEnd, and currentUnit from their respective addresses directly into the global variables.currentUnit is valid.saveConfigToEEPROM() to write these default values into the EEPROM addresses.EEPROM_CHECK_VALUE to EEPROM_ADDR_CHECK so that the data will be considered valid on the next boot.// Saves configuration to EEPROM
void saveConfigToEEPROM() {
Serial.println(F("Saving config to EEPROM..."));
// Write current global variable values to EEPROM addresses
EEPROM.put(EEPROM_ADDR_R_START, restrictedHourStart);
EEPROM.put(EEPROM_ADDR_R_END, restrictedHourEnd);
EEPROM.put(EEPROM_ADDR_UNIT, currentUnit);
Serial.println(F("Configuration saved."));
// NOTE: This function doesn't update the check value; it assumes it was set correctly
// during setup or doesn't need changing if only values are updated.
}saveConfigToEEPROM() Function: Writes the current settings from global variables into EEPROM.EEPROM.put() to write the current values of restrictedHourStart, restrictedHourEnd, and currentUnit to their designated addresses in EEPROM. EEPROM.put() handles different data types automatically.EEPROM_CHECK_VALUE. The check value is typically written only once when defaults are saved after detecting invalid data.
License:
Creative Commons — Attribution — Noncommercial — Share Alike