Objective:
Clearly this cute pooch can't feed itself...at least not on a timed basis...or can she? Goal is to create an automatic dogfeeder/refueler and have it run on a clock so that it feeds, errr, excuse me, refuels at 3 set times. This will likely be the closest we get to her making her own meals, except maybe a rat salad.
The refueler should include an LCD display and provide the ability to skip a refuel and manually refuel the dog with either full or half portions. Finally it should have a reset button and a pulsed reverse for use in the event of a jam. Oh yeah, it should also have in-house webbased controls and status display.
Method:
I decided on a hopper feeder design, likely from memories of extruding powder coatings. A bulldoser scoup was briefly on the table but more complex. So I jumped onto Blender and designed a feeder. Then spend some time in the garage gathering resources.
Step1:
Runa a basic feeder system test.
I got this idea from a websearch which showed how ranchers transport pigfeed from silo to trough. It is basically a single screw extruder which pushes from the periphery of the inside of the tube. Fuel flows along and pours out the other end.
Step2:
Designed the frame out of scrap wood. Must have thought about throwing this out 100times but feels good to put it to use. The hopper needs to be on an angle so the fuel drains to one side. Thus I used a 20° decline which is just enough to overcome friction forces between the food and basin. Lots of soh-cah-toa later we have this frame.
step3:
Time for weeks of coding. The new arduino Uno R4 wifi is sweet because it has wifi, web server capability and an onboard timer that stays accurate via a webpage.
The name has evolved over time as well.
What started out as the dogfeeder grew, appropriately into:
Bill and Todd's Excellent Inventions presents Hotbod's Dogrefueler kay9
Fits.
What started out as the dogfeeder grew, appropriately into:
Bill and Todd's Excellent Inventions presents Hotbod's Dogrefueler kay9
Fits.
HARDWARE
** Arduino uno R4 wifi with its own 5V power supply
** Adafruit motorshield v2.3 stacked onto the arduino. Has a separate 12V 5A (60W) power supply for the stepper motor. The motor shield is powered by the arduino.
You can also use 6 AA batteries for a total of 9V to power the stepper motor
** Adafruit nema17 bipolar stepper motor 200steps/revolution. 12V 0.35A per phase. The wiring from the Left is: red,yellow, greed,grey.
Important to make sure the stepper motor, motor shield, and power supply all match up as my usongshine 3.5V 1.5A per phase was pulling too much current and tripping the power supply. For the usongshine, wiring is red,blue, green,black but the rotation is reversed between the two.
** 16x2 LCD display
** Arduino uno R4 wifi with its own 5V power supply
** Adafruit motorshield v2.3 stacked onto the arduino. Has a separate 12V 5A (60W) power supply for the stepper motor. The motor shield is powered by the arduino.
You can also use 6 AA batteries for a total of 9V to power the stepper motor
** Adafruit nema17 bipolar stepper motor 200steps/revolution. 12V 0.35A per phase. The wiring from the Left is: red,yellow, greed,grey.
Important to make sure the stepper motor, motor shield, and power supply all match up as my usongshine 3.5V 1.5A per phase was pulling too much current and tripping the power supply. For the usongshine, wiring is red,blue, green,black but the rotation is reversed between the two.
** 16x2 LCD display
Step4:
This is the first prototype and it works. the dog loved it. I was successfully able to push some cheerios through the tube. Next step is to build the hopper and calculate the number of turns needed to fill one cup.
Also, at this time there is a pulsed reverse however this does not have a motor.release so when the reverse is used the stepper is held in place until either full or half are called. this wastes energy.
While this is happening I kept the system online to troubleshoot drops from the wifi netowrk, a problem which has plagued the AutoMatic Blinds.
Step5:
Updated the auger system with a thicker wire. I think it is an 8AWG so it is really stiff. I wound it around a 1inch pipe from putting in the sprinkler system 6 or 7 years ago. Then attached it to a dremmel bit w a cardboard/hotglue screen/cap. This attached to a nema17 stepper motor courtesy of Adafruit Industries via a 5mm coupler. I did all the torque testing when i did the blinds so i was confident it would be strong enough to propel the fuel. The dremmel bit was a touch small so i had to tape it. Probably should have measured this and bought a 5mm to 4mm coupler.
Step6:
Initially started out with an elaborate cloth connection between the hopper and feeder. I considered this approach because I wanted something that tapered up and out but it was a mess so instead i used some more of the 1.5in sink plumbing pipe and more math to cut it at a 20° angle.
step7:
I wanted direct access to the insides so i used foam padding to secure the top to the frame. Thus it doesn't need to be nailed down. Then I cut a cool 'u' into a 2x6 to create a brace for the hopper. I want the hopper to be free floating as well so that i can slide it back to cut off the flow in the event that repairs are needed while it is full.
step8 - Trouble:
Got the first prototype up and running and ran into some snags.
1. I made several different auger shapes to see which would turn the easiest
2. the usongshine stepper motor was pulling too much current for the 12V5A power supply and adafruit shield so it cut out mid turn
3. I had made the auger slightly more narrow figuring that it was causing drag against the inside of the tube. Turns out that this allowed for feed to get wedged between the auger and the side wall which caused more jamming and stalling
step9 - Wiring the control panel
I initially wired the panel using pulldown logic for the buttons but then changed all the buttons (and associated code) to PULLUPs. At the same time I removed the soldered board and used screw connectors to joint everything.
June 12, 2024: Got the first prototype up and running
It has been well over a year since the start and today we have the first prototype of the automatic dogfeeder up and running. The only issue is that the dog hasn't wanted to eat all day. Her fasting started before the install. She did come over and gave it all a good sniff. So the skip is already in use.
Once this is up and running smoothly for a bit the next step will be to 3d print the feeder shute which is currently cardboard. Will also likely have to adjust the amount of feed given as it seems a bit much. And finally I may remove some of the pauses in the code as the buttons are not triggering as easily as i would like which is suspect is due to excessive pausing in the void loop()
The Code
/* Arduino Uno R4wifi: Feed the dog 3 times a day automaticautomatically with additional web and manual controls, dimmable LCD screen and startup/reset LED.
NTP and timezone libraries asynronously keep track of time (accounting for daylight savings time) and trigger feeds at 6, 12, 1800 hours.
5 pushbuttons provide manual control (full feed, half feed, skip next feed, pulse reverse, Reset).
These buttons use the internal pullup meaning that when not pressed they are HIGH and when pressed they are LOW.
WebPage IP is printed to LCD at startup and also controls the buttons (reverse and reset not included in webpage).
Reset button is connected to board reset pin (which has internal power and 10K resistor).
Pressing the button connects reset pin to ground so it is a pullup (initially it is pulled up to HIGH and pressing grounds it LOW). no code needed for reset.
* NTP is a timer library that returns getEpochTime which is the number of seconds since 1/1/1970 in UCT. This is updated once a second and keeps time asyncronously.
* timezone.h then converts NTPs getEpochTime to local (PST/PDT) time as hrs, min, and sec and corrects for daylight savings time (DST).
The code then compares current time to the feed times and triggers a feed at the top of the 6, 12, 1800 hours.
LOGIC:
*nextFeedHour is defined on startup via setNextFeedStartup() but feed is not allowed. Then void Loop() runs checkIfFeedTime() Followed by nextFeedTime(). The latter performs the following:
*when skip is off: nextFeedHour is set based on what time it is.
*if skip is on: timeToBeSkipped is defined
*then nextFeedHour is set based on timeToBeSkipped
*timeToBeSkipped can only be defined when it is = 0. So it is reset to 0 whenever the skip toggle button is pressed or when a feed occurs.
*checkIfFeedTime() evaluates currentHour againse nextFeedHour and triggers feed when equal
*skipNextFeedPinState is the button state of the pin that toggles the value skipNextFeedToggle
*checkIfFeedTime() runs first in void Loop() so that once the top of the hour hits a feed is triggered before nextFeedTime() updates nextFeedHour to the next feedTime
**for troubleshooting: this sketch counts the number of times that the wifi connection was reset without powering off. This is for tracking the wifi disconnections.
**webpage as been set to static 10.0.0.158.
**HARDWARE
** Arduino uno R4 wifi
** Adafruit motorshield v2.3
** Adafruit nema17 bipolar stepper motor 200steps/revolution. 12V 0.35A per phase. The wiring from the Left is: red,yellow, greed,grey.
for the usongshine, wiring is red,blue, green,black but the rotation is reversed between the two. also the usongshine pulls too much current from the 12V 5A powersupply
** 12V 5A (60W) power supply
** 16x2 LCD display with library compatible with the Hitachi HD44780 driver.
The LCD circuit:
* LCD RS pin to digital pin 12
* LCD Enable pin to digital pin 11
* LCD D4 pin to digital pin 5
* LCD D5 pin to digital pin 4
* LCD D6 pin to digital pin 3
* LCD D7 pin to digital pin 2
* LCD R/W pin to ground
* LCD VSS pin to ground
* LCD VCC pin to 5V
* 220K resistor:
* ends to +5V and ground
* potentiometer to LCD VO pin (pin 3)
9/2023 tja
*/
#include
#include // adjusts NTP for daylight savings time https://github.com/JChristensen/Timezone
#include //for unoR4wifi boards
//#include // for unorev2 and ?mkr1010wifi boards
#include
#include "arduino_secrets.h"
#include
#include
char ssid[] = SECRET_SSID; // the network SSID (name)
char password[] = SECRET_PASS;
int status = WL_IDLE_STATUS; // board wifi module
//const int ledPin = LED_BUILTIN; // board LED pin
const int ledPin = 10; // wired LED pin. I added an LED pin but w the LCD display it really isn't needed. oh well.
const int buttonFullPin = 6;
const int buttonHalfPin = 7;
const int buttonSkipFeedPin = 8;
const int buttonReversePin = 9;
int fullFeedState = 0; // 0=LOW. 1 = HIGH = button is pressed
int halfFeedState = 0; // 0=LOW. 1 = HIGH = button is pressed
int reverseFeedState = 0; // 0=LOW. 1 = HIGH = button is pressed
int skipNextFeedPinState = 0;
int feedTime1 = 6; // ** SET THE FEED TIMES HERE ** in military time. could define these on the webpage
int feedTime2 = 12; // ** SET THE FEED TIMES HERE ** in military time
int feedTime3 = 18; // ** SET THE FEED TIMES HERE ** in military time
int timeToBeSkipped = 0; // used to set nextFeedHour when skip is on. set to 0 on startup and when skip hasn't been set (as in at the time of skipNextFeedToggle toggle)
int nextFeedHour = 0;
bool resetDone = false; //allows resetting the day from NTP time
bool skipNextFeedToggle = false; // this is the VARIABLE that changes when the skipNextFeedPinState is changed
int NTPOffset = 0; // variable in NTPClient timeClient. 0 returns UTC
int updateInterval = 60000; // variable in NTPClient timeClient
int currentHour = 0;
int currentTime = 0;
int currentMinutes = 0;
int currentSeconds = 0;
float dailyFeedCount = 0;
int nextDay = 1; //NO LONGER NEEDED
int wifiLost = 0; //for debugging dropped wifi
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2; // associateLCD interface pins with the arduino pin number
int revPerCup = 16; // **SET DISPENSE AMOUNT HERE ** this whole number is multiplied by either 100steps (half) or 200steps (full) below. 100steps=180° and 200steps=360°
int stepperSpeed = 50; //sets the speed of the stepper motor
LiquidCrystal lcd(rs, en, d4, d5, d6, d7); //initialize the LCD library
WiFiServer server(80); //create server object
WiFiClient client = server.available(); //create client object globally
WiFiUDP ntpUDP;
TimeChangeRule myDST = {"PDT", Second, Sun, Mar, 2, -420}; // Daylight time = UTC - 7 hours(-240) 7*60 = 420... or make this 0 if using the PDT correction factor of -25200 for NTP offset below
TimeChangeRule mySTD = {"PST", First, Sun, Nov, 2, -480}; // Standard time = UTC - 8 hours(300) 8*60 = 480... or make this 1*6=60 if using the PDT correction factor of -25200 for NTP offset below
Timezone myTZ(myDST, mySTD);
TimeChangeRule *tcr; // pointer to the time change rule, use to get TZ abbrev
// By default 'pool.ntp.org' is used with 60 seconds update interval and no offset. You can specify the time server pool and the offset (in seconds).additionally you can specify the update interval (in milliseconds).
NTPClient timeClient(ntpUDP, "3.us.pool.ntp.org", NTPOffset, updateInterval); // offset is set to zero because timezone.h adjusts this to 8hrs during PST
//NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", -25200, 60000); //-252000 subtracts 7hrs from europe time = PST
Adafruit_MotorShield AFMS = Adafruit_MotorShield(); // Create the motor shield object with the default I2C address
Adafruit_StepperMotor *myMotor = AFMS.getStepper(200, 2); // Connect a stepper motor with 200 steps per revolution (1.8 degree) to motor port #2 (M3 and M4)
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(buttonFullPin, INPUT_PULLUP);
pinMode(buttonHalfPin, INPUT_PULLUP);
pinMode(buttonReversePin, INPUT_PULLUP);
pinMode(buttonSkipFeedPin, INPUT_PULLUP);
Serial.begin(9600);
digitalWrite(ledPin,HIGH); //LED is red until the system is running. turned off inside the void loop() while. turned on outside the void loop() while.
//while (!Serial) {;} // wait for serial port to connect. Needed for native USB port only
lcd.begin(16, 2); // set up the LCD's number of columns and rows. they start w 0,0
lcd.print("WELCOME-HotBod's");
lcd.setCursor(0, 1);
lcd.print("DogRefueler Kay9");
delay(2000); // time for users to read the LCD screen title splash
connectToWifi(); // establish connection to local wifi After the LCD is up and running so the IP address can be printed to it
server.begin(); // start the web server on port 80
timeClient.begin(); // start NTP
timeClient.update(); //getting the updated epochTime value here first as the first one is always erroneous. then called again in void loop() .
timeClient.update(); //now the correct epochTime is returned
unsigned long epochTime = timeClient.getEpochTime(); //tell NTP to return the current number of seconds since 1/1/1970
time_t utc = epochTime; // defines utc for timezone.h as epochTime
time_t local = myTZ.toLocal(utc, &tcr); //set local as epochTime of (timezone and DST adjusted UTC)
createDateTimeVariables(local, tcr -> abbrev); // uses timezone.h to convert epochTime to currentHour, currentMin, currentSec as integers
Serial.println();
Serial.println("Current uploaded sketch can be found at: ");
Serial.println(__FILE__);
printWifiStatus(); // you're connected now, so print the status to serial and LCD
setNextFeedStartup(); // if system restart on the feed hour, this function sets nextFeedHour to that hour
//if (!AFMS.begin(1000)) { // to change to a different frequency, say 1KHz
if (!AFMS.begin()) { // calls AFMS.begin which is the adafruit motorshield
Serial.println("Could not find Motor Shield. Check wiring.");
while (1);
}
myMotor->setSpeed(stepperSpeed); // defined above in rpm
}
void loop() {
while (WiFi.status() == WL_CONNECTED){ //if the connection to the local wifi is dropped leave this and reset the connection
digitalWrite(ledPin,LOW);
myMotor->release(); // make sure the motor isn't being held
client = server.available(); // listen for incoming clients
timeClient.update(); // get the updated time
unsigned long epochTime = timeClient.getEpochTime(); //tell NTP to return the current number of seconds since 1/1/1970
time_t utc = epochTime; // defines utc for timezone.h as epochTime
time_t local = myTZ.toLocal(utc, &tcr); //set local as epochTime of (timezone and DST adjusted UTC)
createDateTimeVariables(local, tcr -> abbrev); //convert timezone.h epochTimes to hr, min, sec and store them as int
checkIfFeedTime(); // triggers feeding if currentTime=nextFeedHour
nextFeedTime(); // calculates nextFeedHour. Must run after checkIfFeedTime()
lcdUpdate(); // refresh/print to the lcd screen
fullFeedState = digitalRead(buttonFullPin);
halfFeedState = digitalRead(buttonHalfPin);
reverseFeedState = digitalRead(buttonReversePin);
skipNextFeedPinState = digitalRead(buttonSkipFeedPin);
if (fullFeedState == LOW) {fullFeed();} //read button press triggers feed
if (halfFeedState == LOW) {halfFeed();} //read button press triggers feed
if (reverseFeedState == LOW) {reverseFeed();} //read button press triggers feed
if (skipNextFeedPinState == LOW) {skipFeed();} //read button press toggles skipfeed
if (client) { // if you get a client connection to the server call the printwebpaeg function
printWebpage();
}
if (currentHour == 23){ //at 11pm allow reseting of the day once it hits midnight
resetDone = false;
}
if ((currentHour == 0) && (resetDone == false)){ //at midnight reset the day
resetDone = true;
resetDay();
}
delay(1000); //slow void loop() to once a second
}
digitalWrite(ledPin,HIGH); //no longer connected to local wifi so turn on the LED and attempt to reset connection
lcd.clear();
lcd.setCursor(0, 0); //column,row
lcd.print("lost wifi");
lcd.setCursor(0, 1); //column,row
lcd.print("reconnecting");
connectToWifi();
}
void connectToWifi() { //connects to board wifi module, checks firmware, connects to wifi network
if (WiFi.status() == WL_NO_MODULE) { //check for board wifi module
lcd.print("WiFi module fail");
Serial.println("Communication with WiFi module failed!");
while (true); // don't continue
}
else {Serial.println("wifi module connected.");}
String fv = WiFi.firmwareVersion(); //check firmware
if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
Serial.println("Please upgrade the firmware");
}
while (status != WL_CONNECTED) { //connect to local wifi
status = WiFi.begin(ssid, password); // update the value of status while looping. Connect to WPA/WPA2 network. Change this line if using open or WEP network:
wifiLost += 1;
lcd.clear();
lcd.setCursor(0, 0); //column,row
lcd.print("connecting wifi");
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
delay(1000);
}
}
void printWifiStatus() { //just prints wifi status to serial and LCD. No actions performed
IPAddress ip = WiFi.localIP();
lcd.clear();
lcd.setCursor(0, 0); //column,row
lcd.print("IP = ");
lcd.setCursor(6,0);
lcd.print(ip);
Serial.print("Connected to local wifi network SSID: ");
Serial.println(WiFi.SSID());
Serial.print("Using local IP address: ");
Serial.println(ip);
delay(1000);
lcd.clear();
}
time_t compileTime(){ //part of timezone.h which converts NTP time to PST/PDT
const time_t FUDGE(10); // fudge factor to allow for compile time (seconds, YMMV)
const char *compDate = __DATE__, *compTime = __TIME__, *months = "JanFebMarAprMayJunJulAugSepOctNovDec";
char chMon[4], *m;
tmElements_t tm;
strncpy(chMon, compDate, 3); //copy 3 numbers from compDate to chMon
chMon[3] = '\0'; //adds a null to the end of the above
m = strstr(months, chMon); //sets m as a pointer of chMon in months
tm.Month = ((m - months) / 3 + 1);
tm.Day = atoi(compDate + 4);
tm.Year = atoi(compDate + 7) - 1970;
tm.Hour = atoi(compTime);
tm.Minute = atoi(compTime + 3);
tm.Second = atoi(compTime + 6);
time_t t = makeTime(tm); //is local epochTime
return t + FUDGE; // add fudge factor to allow for compile time
}
void createDateTimeVariables(time_t t, const char *tz){ // part of timezone.h which formats and print a time_t value, with a time zone appended.
String currentTime = "";
char buf[32];
char m[4]; // temporary storage for month string (DateStrings.cpp uses shared buffer)
strcpy(m, monthShortStr(month(t)));
sprintf(buf, "%.2d:%.2d:%.2d %s %.2d %s %d %s",
hour(t), minute(t), second(t), dayShortStr(weekday(t)), day(t), m, year(t), tz);
currentHour = hour(t);
currentMinutes = minute(t);
currentSeconds = second(t);
Serial.print("The time is ");
Serial.print(currentHour);
Serial.print(":");
Serial.print(currentMinutes);
Serial.print(":");
Serial.println(currentSeconds); //prints hr:min:sec
//Serial.println(buf); //prints hr:min:sec DOW date month year tz
}
void printWebpage(){
if (client) {
//currentHour = timeClient.getHours();
String currentLine = ""; // make a String to hold incoming data from the client
while (client.connected()) { // loop while the client's connected
if (client.available()) { // if there's bytes to read from the client,
char c = client.read(); // read a byte, then
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
client.println(""); //web page is made using HTML/CSS
client.println("");
client.println("");
client.println("");
client.println("UnoR4-wifi - Dog Feeder Automation ");
client.println("");
client.println("");
client.println("");
//client.print("HotBodd Industries
");
client.print("");
client.print("HotBodd Industries");
client.println("");
client.print("presents
");
client.print("Antenucci's DogRefueler kay9
");
client.print("Seymour, feed the dog at
6:00, 12:00, and 18:00 hours
");
//client.print("A HotBod Industries Product - \(c)\ 2023
");
client.println("");
client.print("The current military time is: ");
if(currentHour == 0){client.print("0"); client.print(currentHour);}
if(currentHour > 0 && currentHour < 10){client.print("0"); client.print(currentHour);}
if(currentHour >= 10){client.print(currentHour);}
client.print(":");
if(currentMinutes == 0){client.print("0"); client.print(currentMinutes);}
if(currentMinutes > 0 && currentMinutes <10){client.print("0"); client.print(currentMinutes);}
if(currentMinutes >= 10){client.print(currentMinutes);}
client.print("
");
client.println("");
client.print("
");
client.print("");
client.print("");
client.print("");
//client.print("");
client.print("");}
if(skipNextFeedToggle == 1){client.print("ON
");}
client.print("");
//client.print("
");
//client.print("");
client.print("");
client.print("
");
client.print("So Far Today there have been ");
client.print("
");
client.print(dailyFeedCount);
client.print(" Total feeds");
client.print("
");
client.print("
");
client.print("The next feed will be at ");
client.print("
");
client.print(nextFeedHour);
client.print(":00 hours");
client.print("");
client.println("");
client.print("A HotBodd Industries Product... \(c)\ 2024. ");
client.println("");
client.println(" ");
client.println("");
client.println("");
client.println(); // The HTTP response ends with another blank line:
// break out of the while loop:
break;
}
else { // if you got a newline, then clear currentLine:
currentLine = "";
}
}
else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
if (currentLine.endsWith("GET /H")) { //here H calls full feed however the buttons are pullups so they are opposite
fullFeed();
}
if (currentLine.endsWith("GET /L")) {
halfFeed();
}
if (currentLine.endsWith("GET /R")) {
skipFeed();
delay(1000);
}
}
}
client.stop(); // close the connection
//Serial.println("client disconnected");
}
}
void setNextFeedStartup(){ //define nextfeedhour on startup but don't let nextFeedHour be = to the currentHour. This prevents feeding upon startup in case of reset during the hour.
//this prevents a feed from occuring if system resets at midnight at which point currentHour = 0 and nextFeedHour = 0 when evaluated by checkIfFeedTime().
if (currentHour < feedTime1){
nextFeedHour = feedTime1;
}
if ((currentHour >= feedTime1) && (currentHour < feedTime2)) {
nextFeedHour = feedTime2;
}
if ((currentHour >= feedTime2) && (currentHour < feedTime3)) {
nextFeedHour = feedTime3;
}
if (currentHour >= feedTime3){
nextFeedHour = feedTime1;
}
}
void checkIfFeedTime(){ //runs in void loop() before nextFeedTime() thereby triggering feed before nextFeedHour is updated at top of the hour. Resets skip and timeToBeSkipped and triggers feed.
if (currentHour == nextFeedHour){
skipNextFeedToggle = false;
timeToBeSkipped = 0;
fullFeed();
}
}
void nextFeedTime(){ //runs in void loop() after checkIfFeedTime() and calculates nextFeedHour
//define nextFeedHour w skip off
if ((currentHour < feedTime1)&& (skipNextFeedToggle == false)){
nextFeedHour = feedTime1;
}
if ((currentHour >= feedTime1) && (currentHour < feedTime2) && (skipNextFeedToggle == false)) {
nextFeedHour = feedTime2;
}
if ((currentHour >= feedTime2) && (currentHour < feedTime3) && (skipNextFeedToggle == false)) {
nextFeedHour = feedTime3;
}
if ((currentHour >= feedTime3) && (skipNextFeedToggle == false)){
nextFeedHour = feedTime1;
}
//define timeToBeSkipped if skip is on. Only adjust timeToBeSkipped if it has been set/reset to 0 (which is its state when skip is off). This prevents changing timeToBeSkipped when it is already set.
if ((currentHour < feedTime1) && (skipNextFeedToggle == true) && (timeToBeSkipped == 0)){
timeToBeSkipped = feedTime1;
}
if ((currentHour >= feedTime1) && (currentHour < feedTime2) && (skipNextFeedToggle == true) && (timeToBeSkipped == 0)) {
timeToBeSkipped = feedTime2;
}
if ((currentHour >= feedTime2) && (currentHour < feedTime3) && (skipNextFeedToggle == true) && (timeToBeSkipped == 0)) {
timeToBeSkipped = feedTime3;
}
if ((currentHour >= feedTime3) && (skipNextFeedToggle == true) && (timeToBeSkipped == 0)) {
timeToBeSkipped = feedTime1;
}
/*
//And for below, why can’t I just say if TTBS = feedTime1 then nextfeedHour = feedtime2?
//define nextFeedHour based on timeToBeSkipped when skip is On. Once timeToBeSkipped has passed then turn off skipNextFeedToggle (unnecessary because SNFT is set to false when feed occurs).
//At this point nextFeedHour w skip off runs
if ((timeToBeSkipped == feedTime1) && (skipNextFeedToggle == true) && (currentHour <= timeToBeSkipped)){
nextFeedHour = feedTime2;
skipNextFeedToggle = true; //leave skip on until timeToBeSkipped has passed
}
if ((timeToBeSkipped == feedTime1) && (skipNextFeedToggle == true) && (currentHour > timeToBeSkipped) && (currentHour < feedTime3)){ //the last part is needed to account for when time goes to 0 at midnight
nextFeedHour = feedTime2;
skipNextFeedToggle = false; //timeToBeSkipped has passed so turn off skip because skipping has been completed. now currentHour is > feedTime1 so nextFeedHour = feedTime2
}
if ((timeToBeSkipped == feedTime1) && (skipNextFeedToggle == true) && (currentHour > timeToBeSkipped) && (currentHour >= feedTime3)){
nextFeedHour = feedTime2;
skipNextFeedToggle = true; // this keeps the skipNextFeedToggle On until after midnight.
}
if ((timeToBeSkipped == feedTime2) && (skipNextFeedToggle == true) && (currentHour <= timeToBeSkipped)){
nextFeedHour = feedTime3;
skipNextFeedToggle = true;
}
if ((timeToBeSkipped == feedTime2) && (skipNextFeedToggle == true) && (currentHour > timeToBeSkipped)){
nextFeedHour = feedTime3;
skipNextFeedToggle = false;
}
if ((timeToBeSkipped == feedTime3) && (skipNextFeedToggle == true) && (currentHour <= timeToBeSkipped)){
nextFeedHour = feedTime1;
skipNextFeedToggle = true;
}
if ((timeToBeSkipped == feedTime3) && (skipNextFeedToggle == true) && (currentHour > timeToBeSkipped)){
nextFeedHour = feedTime1;
skipNextFeedToggle = false;
}
*/
//define nextFeedHour when skip is On. TTBS is set to 0 when skip is Off so these won't evaluate. And when skip is on TTBS is defined above so the below sets nextFeedHour only when skip is on and TTBS has been defined
if (timeToBeSkipped == feedTime1){
nextFeedHour = feedTime2;
}
if (timeToBeSkipped == feedTime2){
nextFeedHour = feedTime3;
}
if (timeToBeSkipped == feedTime3){
nextFeedHour = feedTime1;
}
// watch the following scenario: when time resets to 0 currentTime satisfies 'if currentHour < feedTime1'and set nextFeedHour to feedTime1. had to add && (nextFeedHour <= feedTime1)
lcd.clear();
lcdUpdate();
}
void lcdUpdate(){ //called by the loop to format the LCD screen
lcd.setCursor(0, 0); //column,row. 0-15
lcd.print("Feeds=");
lcd.setCursor(6,0);
lcd.print(dailyFeedCount,1); //1 makes it 1 decimal point only
lcd.setCursor(10,0); //column,row
lcd.print("Skip:");
lcd.setCursor(15,0);
lcd.print(skipNextFeedToggle);
lcd.setCursor(0, 1); //column,row
lcd.print("Next Feed:");
lcd.setCursor(10,1);
lcd.print(nextFeedHour); //THIS IS THE PROBLEM RIGHT HERE I THINK
lcd.setCursor(14,1);
lcd.print(wifiLost); //for troubleshooting dropped wifi
//lcd.setCursor(0, 1); // set the cursor to column 0, line 1. line 1 is the second row, since counting begins with 0
//lcd.print(millis() / 1000); // print the number of seconds since reset:
}
void fullFeed(){ // advances dailyfeedcount by 1.0 and adjusts lcd
dailyFeedCount = dailyFeedCount + 1;
lcd.setCursor(6,0);
lcd.print(dailyFeedCount,1);
lcd.setCursor(0,1);
lcd.print("Full Feed Now");
stepperFull();
delay (10); // pause for a second so the button doesn't multiclick and to display msg
lcd.clear();
}
void halfFeed(){ // advances dailyfeedcount by 0.5 and adjusts lcd
dailyFeedCount = dailyFeedCount + 0.5;
lcd.setCursor(6,0);
lcd.print(dailyFeedCount,1);
lcd.setCursor(0,1);
lcd.print("Half Feed Now");
stepperHalf();
delay (10); // pause for a second to the button doesn't multiclick and to display msg
lcd.clear();
}
void skipFeed(){ // called by web/button press. toggle the skip feed on and off. then send it to nextfeedtime for updating nextfeedhour and then update the LCD
skipNextFeedToggle = !skipNextFeedToggle;
timeToBeSkipped = 0; // needs to be zeroed here so that timeToBeSkipped is updatable in nextFeedTime()
lcd.setCursor(15, 0);
lcd.print(skipNextFeedToggle); //toggles skip between 0 and 1 at column 15
delay (1000); //prevents multiclicking w button press
nextFeedTime(); //call here so the webpage updates nextFeedHour when skip is clicked. Otherwise nextFeedHour doesn't update before the webpage loads
lcd.clear();
lcdUpdate();
}
void reverseFeed(){
digitalWrite(ledPin, HIGH); //temporary till motor is hooked up
lcd.clear();
lcd.setCursor(0,1);
lcd.print("REVERSE");
stepperReverse();
delay (10); // pause for half a second so the button doesn't multiclick and to display msg
lcd.clear();
digitalWrite(ledPin, LOW);
}
void resetDay(){
dailyFeedCount = 0;
}
void stepperFull(){ //command to the motor. not sure why i separated this out from fullFeed()
//myMotor->step(200*revPerCup, BACKWARD, DOUBLE); //1.8° per step. so 50steps rotates 90°. 100steps rotates 180°. 200steps = 360°. ADAFRUIT
myMotor->step(200*revPerCup, FORWARD, DOUBLE); //1.8° per step. so 50steps rotates 90°. 100steps rotates 180°. 200steps = 360°. unsongshine
delay(10);
myMotor->release();
/*
myMotor->step(100, BACKWARD, SINGLE);
Serial.println("Double coil steps");
myMotor->step(100, FORWARD, DOUBLE);
myMotor->step(100, BACKWARD, DOUBLE);
Serial.println("Interleave coil steps");
myMotor->step(100, FORWARD, INTERLEAVE);
myMotor->step(100, BACKWARD, INTERLEAVE);
Serial.println("Microstep steps");
myMotor->step(50, FORWARD, MICROSTEP);
myMotor->step(50, BACKWARD, MICROSTEP);
*/
}
void stepperHalf(){ //command to the motor
//myMotor->step(100*revPerCup, BACKWARD, DOUBLE); //1.8° per step. so 50steps rotates 90°. 100steps rotates 180°. ADAFRUIT
myMotor->step(100*revPerCup, FORWARD, DOUBLE); //1.8° per step. so 50steps rotates 90°. 100steps rotates 180°. unsongshine
delay(10);
myMotor->release();
}
void stepperReverse(){ //command to the motor
//myMotor->step(25*revPerCup, FORWARD, DOUBLE); //1.8° per step. so 50steps rotates 90°. 100steps rotates 180°. ADAFRUIT
myMotor->step(5*revPerCup, BACKWARD, DOUBLE); //1.8° per step. so 50steps rotates 90°. 100steps rotates 180°. unsongshine
delay(10);
myMotor->release();
}
BeeStory:
This morning while waking up in the sun I was sitting watching the bees feast on the oregano flowers when one bee became stuck. it looked like it had a leaf trapped under its wing. I tried to free it with a stick but it was really stuck. Then it came into focus! The bee was trapped in the grasp of a praying mantis that was eating it alive. I sat and watched in awe as it consumed every bit of the bee. It even got down on the wing that remained in it's right claw. Then it cleaned itself off just like a dog and went to sleep in the sun right in the middle of our oregano.
A shot of the team.
Well not in its entirty.
Well not in its entirty.
9/10/23 - HotBod Dog Refueler kay9
Boards: Arduino UnoR4wifi, Adafruit motor shield v2.
Hitachi LCD display with onboard Hitachi HD44780 driver
Nema 17 stepper motor
5 push buttons, potentiometer, 220Ohm resister. 5x 10K resistors
USB power source for uno and separate 5-12V power supply for the stepper motor
Objective:
To create a dog feeder which automatically feeds the dog each day at 3 preset times. The system will also have manual full feed and half feed buttons as well as ability to skip the next feed which can be toggled on and off. Finally the system will also be available locally for manipulation via a client webpage.
Targets:
- • Automatically feed the dog at 3 preset times (currently 6, 12, 18:00)
- • Provide a full feed based on a manual button click
- • Provide a half feed based on a manual button click
- • Provide a button that toggles on an off the skip of the next feed.
- • When the system powers on I do not want it to trigger a feed even if it is 1minute into the feed hour.
- • I want the ability to skip the next morning feed
- • I do not want the manual feeds to interfere w the automatic feeds e.g., if I manually feed the dog 1minute before a scheduled feed I still want the scheduled feed to occur.
- • I want a counter that counts the total number of feeds each day (both automatic and manual)
- • I want to be able to display which feed is next (which will show a future feed if the next feed is to be skipped)
Steps:
- 1. Get the NTP board timer working
- 2. Set up the web server to serve a web page which displays
- a. Time
- b. Number of feeds
- c. Next feed scheduled
- d. Manual full feed button
- e. Manual half feed button
- f. Manual skip feed toggle button
- 3. Create the logic
- 4. Add the stepper motor
- 5. Include a board reset botton (manual only. Not web based)
- 6. Include a pulsed reverse button in case of jams (manual only. Not web based)
- 7. Create the feeder system
- 8. Determine the number of revolutions per feed and half feed
- 9. Feed the dog