/**********************************************************************************************************************
███╗ ███╗ █████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗
████╗ ████║██╔══██╗██╔══██╗██╔════╝ ██╔══██╗╚██╗ ██╔╝
██╔████╔██║███████║██║ ██║█████╗ ██████╔╝ ╚████╔╝
██║╚██╔╝██║██╔══██║██║ ██║██╔══╝ ██╔══██╗ ╚██╔╝
██║ ╚═╝ ██║██║ ██║██████╔╝███████╗ ██████╔╝ ██║
╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝ ╚═╝
██████╗ ██████╗ ██████╗ █████╗ ███╗ ███╗███████╗████████╗ ██╗███╗ ██╗██████╗ ██████╗ ██████╗ ███╗ ███╗
██╔══██╗██╔═══██╗██╔══██╗██╔══██╗████╗ ████║██╔════╝╚══██╔══╝ ██║████╗ ██║██╔══██╗██╔══██╗██╔═══██╗████╗ ████║
██████╔╝██║ ██║██████╔╝███████║██╔████╔██║█████╗ ██║ ██║██╔██╗ ██║██████╔╝██████╔╝██║ ██║██╔████╔██║
██╔═══╝ ██║ ██║██╔══██╗██╔══██║██║╚██╔╝██║██╔══╝ ██║ ██║██║╚██╗██║██╔═══╝ ██╔══██╗██║ ██║██║╚██╔╝██║
██║ ╚██████╔╝██║ ██║██║ ██║██║ ╚═╝ ██║███████╗ ██║ ██║██║ ╚████║██║ ██║ ██║╚██████╔╝██║ ╚═╝ ██║
╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
***********************************************************************************************************************/
#define BLYNK_TEMPLATE_ID "TMPL6cu1h1f-4"
#define BLYNK_TEMPLATE_NAME "iot pet feeder"
#define BLYNK_AUTH_TOKEN "Q0ktURLm3vWrFBPkk7tPvvjAhDwaDY9Y"
#define BLYNK_FIRMWARE_VERSION "0.1.0"
#define BLYNK_PRINT Serial // Defines the object that is used for printing
//#define BLYNK_DEBUG // Optional, this enables more detailed prints
#include <SPI.h> //นำเข้าไลบรารี่ SPI
#include <Arduino.h> //นำเข้าไลบรารี่ Arduino
#include <WiFi.h> //นำเข้าไลบรารี่ WiFi
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h> //นำเข้าไลบรารี่ Blynk
#include <HardwareSerial.h> //นำเข้าไลบรารี่ HardwareSerial เพื่อใช้งาน Serial port ในการส่งข้อมูล
#include "soc/rtc.h"
#define V_FOOD1 V0
#define V_FOOD2 V1
#define V_FOOD3 V2
#define V_TIME1 V3
#define V_TIME2 V4
#define V_TIME3 V5
#define V_WEIGHT V6
#define V_STEPPER V7
/* ไลบรารี่สำหรับLoadcell */
#include "HX711.h"
const int LOADCELL_DOUT_PIN = 16;
const int LOADCELL_SCK_PIN = 17;
HX711 LoadCell;
//REPLACE WITH YOUR CALIBRATION FACTOR
#define CALIBRATION_FACTOR -471.497
/* ไลบรารี่สำหรับจอแสดงผล LCD I2C */
#include <Wire.h> // LCD I2C
#include <LiquidCrystal_I2C.h> // LCD I2C
LiquidCrystal_I2C lcd(0x27,20,4); // LCD I2C 0x27
/* ไลบรารี่สำหรับจอServo */
#include <ESP32Servo.h>
Servo servo;
int servoPin = 19; // Pin connected to the servo
int currentAngle = 174; // Initial angle
int minAngle = 0; // Minimum angle (in degrees)
int maxAngle = 180; // Maximum angle (in degrees)
int angle_s = 174; // Angle for 's' command
int angle_d = 150; // Angle for 'd' command
int repeatCount = 30; // Number of times to repeat the sequence
int feed_angle = 174; // Initial angle
int clean_angle = 150; // Angle for 'd' command
/* ไลบรารี่สำหรับ Stepper Motor*/
#include <AccelStepper.h>
#define stepPin 13 //stepPin
#define dirPin 12 //dirPin
#define enPin 14 //enPin
#define STEPS_PER_REV 200 // Steps per revolution of your stepper motor
#define RPM 120 // Desired revolutions per minute
bool stepperEnabled = false;
AccelStepper stepper(AccelStepper::DRIVER, stepPin, dirPin);
/* ประกาศขาสำหรับ Relay */
#define Relay 32
/* ประกาศขาสำหรับ Switch */
#define SW 15// Switch pin
int count = 0;
/* ไลบรารี่สำหรับบันทึกข้อมูลลง EEPROM */
#include <Preferences.h> //EEPROM
Preferences pref; //EEPROM
/* ประกาศขาสำหรับ Rotary Encoder Switch */
#define encoder_outputA 27
#define encoder_outputB 26
#define encoder_sw 25
int encoder_swState = 0;
int encoder_counter = 0;
int encoder_currenceState;
int encoder_lastState;
bool encoder_choice = false;
/* ไลบรารี่สำหรับโมดูล RTC */
#include "RTClib.h" //RTC I2C
RTC_DS3231 rtc; //RTC I2C
/* Void */
void rotary(); //Rotary Switch
void monitor(); //การแสดงผลของจอ LCD
void main_feeder(); //ระบบการให้อาหาร
void pref_eeprom(); //บันทึกข้อมูลลง EEPROM
void blynk_connected();
void cleanFunction();
void tareLoadCell();
void selectFood();
void feeding();
void stepper_motor();
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; //แปลงเป็นสัปดาห์
/* ตัวแปรเก็บค่าสำหรับบันทึกลงใน EEPROM */
int food1,food2,food3; //get data form blynk ปริมาณอาหาร
int mem_food1,mem_food2,mem_food3; //EEPROM data
String time1,time2,time3; //get data form blynk เวลาให้อาหารสัตว์
String mem_time1,mem_time2,mem_time3; //EEPROM data
bool bool_tare = false;
bool bool_feeding = false;
/* ตัวแปรสำหรับเก็บค่า Millis */
unsigned long timer_page = millis();
unsigned long timer_feeder = millis();
unsigned long timer_rotary = millis();
unsigned long timer_clean = millis();
unsigned long timer_stepper = millis();
/* boolean เก็บค่าเวลาเมื่อถึงเวลาทำความสะอาดและให้อาหาร */
bool bool_time1,bool_time2,bool_time3;
bool clean_feeder,clean_status;
bool clean_activate,tare_activate,feed_activate,food1_activate,food2_activate,food3_activate;
int value_food;
char auth[] = BLYNK_AUTH_TOKEN;
/* เก็บข้อมูลตัวแปร ไวไฟ และรหัส */
String send_ssid;
String send_pass;
char ssid[] = "Wokwi-GUEST";
char pass[] = "";
float Weight;
float blynk_weight;
//-----------------------------------------------------------------------------------------------------------//
void setup() {
// rtc_cpu_freq_config_t config;
// rtc_clk_cpu_freq_get_config(&config);
// rtc_clk_cpu_freq_to_config(RTC_CPU_FREQ_80M, &config);
// rtc_clk_cpu_freq_set_config_fast(&config);
/* Initial Servo pin */
servo.attach(servoPin);
servo.write(currentAngle); // Initialize servo to initial angle
/* Initial Relay Pin */
pinMode(Relay,OUTPUT);
count = 0;
/* Initial Feed protocal */
clean_activate = true;
tare_activate = false;
feed_activate = false;
food1_activate = false;
food2_activate = false;
food3_activate = false;
/* Initial Loadcell */
LoadCell.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
LoadCell.set_scale(CALIBRATION_FACTOR);
encoder_counter = 1;
// encoder_bt.begin(encoder_bt_pin);
pinMode(encoder_outputA,INPUT);
pinMode(encoder_outputB,INPUT);
pinMode(encoder_sw,INPUT);
//pinMode(encoder_sw,INPUT_PULLUP);
lcd.backlight();
lcd.init(); //LCD Begin
Wire.begin(); //I2C
Serial.begin(115200); //Serial Rate
WiFi.begin(ssid, pass);
/* Stepper Motor */
pinMode(SW, INPUT_PULLUP);
pinMode(enPin, OUTPUT);
digitalWrite(enPin, HIGH); // Disable stepper motor initially
stepper.setMaxSpeed(RPM * STEPS_PER_REV);
stepper.setAcceleration(1000); // You can adjust the acceleration value as needed
stepper.setSpeed(RPM * STEPS_PER_REV);
/* Pref Setup */
pref.begin("food_state", false); //Given namespace name from an NVS partition
//WiFi.begin(ssid, pass);
Blynk.config(auth);
lcd.setCursor(0,0);
lcd.print("IoT Cat Feeder");
lcd.setCursor(0,1);
lcd.print("Version 1.4.1");
lcd.setCursor(0,2);
lcd.print("Poramet Inprom");
lcd.setCursor(0,3);
lcd.print("SID : 6531280024");
delay(2000);
lcd.clear();
LoadCell.tare();
}
//-----------------------------------------------------------------------------------------------------------//
void loop() {
if (SW == LOW) {
count++;
delay(200); // Debouncing delay
}
if (count == 2) {
count = 0;
}
if (Serial.available() > 0) {
char command = Serial.read(); // Read the command from Serial Monitor
if (command == 's') {
servo.write(angle_s);
currentAngle = angle_s; // Update current angle
Serial.println("Angle set to 174.");
} else if (command == 'd') {
servo.write(angle_d);
currentAngle = angle_d; // Update current angle
Serial.println("Angle set to 150.");
} else {
Serial.println("Invalid command. Use 's' to set angle to 174, 'd' to set angle to 150, 'a' to execute sequence or input angle (0-180).");
}
}
if (digitalRead(SW) == LOW) { // If switch is pressed
Serial.println("Stepper On");
stepperEnabled = true;
}
else{
stepperEnabled = false;
}
if (stepperEnabled) {
digitalWrite(enPin, LOW); // Enable stepper motor
digitalWrite(dirPin, HIGH); // Set direction (clockwise)
// Step the motor
for (int i = 0; i < STEPS_PER_REV; i++) {
digitalWrite(stepPin, HIGH);
delayMicroseconds(500); // Adjust delay for desired speed
digitalWrite(stepPin, LOW);
delayMicroseconds(500); // Adjust delay for desired speed
}
}
rotary();
monitor();
main_feeder();
pref_eeprom(); //เก็บข้อมูลลง EEPROM
blynk_connected();
servo.write(feed_angle);
if (WiFi.status() == WL_CONNECTED)
{
Blynk.run();
}
}
//-----------------------------------------------------------------------------------------------------------//
BLYNK_WRITE(V_STEPPER)
{
int value = param.asInt(); // Get value as integer
if(value == 1){
stepper_motor();
}
else{
}
}
/*-----------------------------------*/
BLYNK_WRITE(V_FOOD1) { //รับค่าจาก Blynk
food1 = param.asInt();
pref.putInt("FOOD1",food1);
}
BLYNK_WRITE(V_FOOD2) { //รับค่าจาก Blynk
food2 = param.asInt();
pref.putInt("FOOD2",food2);
}
BLYNK_WRITE(V_FOOD3) { //รับค่าจาก Blynk
food3 = param.asInt();
pref.putInt("FOOD3",food3);
}
/*-----------------------------------*/
BLYNK_WRITE(V_TIME1) { //รับค่าจาก Blynk
time1 = param.asString();
pref.putString("Time1",time1);
}
BLYNK_WRITE(V_TIME2) { //รับค่าจาก Blynk
time2 = param.asString();
pref.putString("Time2",time2);
}
BLYNK_WRITE(V_TIME3) { //รับค่าจาก Blynk
time3 = param.asString();
pref.putString("Time3",time3);
}
/*-----------------------------------*/
BLYNK_WRITE(V_TARE) { //รับค่าจาก Blynk
bool_tare = param.asString();
//pref.putString("Bool_Tare",bool_tare);
}
void pref_eeprom() { //นำข้อมูลเก็บลงใน EEPROM
mem_food1 = pref.getInt("FOOD1",0);
mem_food2 = pref.getInt("FOOD2",0);
mem_food3 = pref.getInt("FOOD3",0);
mem_time1 = pref.getString("Time1",String());
mem_time2 = pref.getString("Time2",String());
mem_time3 = pref.getString("Time3",String());
}
void blynk_connected() {
}
void monitor() {
//เช็คโมดูล
if (!rtc.begin()) { //RTC I2C Check
Serial.println("Couldn't find RTC");
}
/* ตัวแปรแปลงเวลาจากวินาทีเป็น ชั่วโมง นาที */
int int_time1 = mem_time1.toInt();
int int_time2 = mem_time2.toInt();
int int_time3 = mem_time3.toInt();
int time1_hours, time1_minutes;
int time2_hours, time2_minutes;
int time3_hours, time3_minutes;
time1_hours = int_time1/3600;
time1_minutes = (int_time1 % 3600) / 60;
time2_hours = int_time2/3600;
time2_minutes = (int_time2 % 3600) / 60;
time3_hours = int_time3/3600;
time3_minutes = (int_time3 % 3600) / 60;
DateTime now = rtc.now(); //RTC I2C
bool time1_incoming,time2_incoming,time3_incoming;
int time1_compare,time2_compare,time3_compare;
if(time1_hours - now.hour() >= 0){
time1_compare = time1_hours - now.hour();
//Serial.println((String)"Time 1 : Incoming" +time1_compare);
}
else if(time1_hours - now.hour() < 0){
//Serial.println((String)"Time 1 : Past");
time1_incoming = false;
time1_compare = 99;
}
if(time2_hours - now.hour() >= 0){
time2_compare = time2_hours - now.hour();
//Serial.println((String)"Time 2 : Incoming" +time2_compare);
}
else if(time2_hours - now.hour() < 0){
//Serial.println((String)"Time 2 : Past");
time2_incoming = false;
time2_compare = 99;
}
if(time3_hours - now.hour() >= 0){
time3_compare = time3_hours - now.hour();
//Serial.println((String)"Time 3 : Incoming" +time3_compare);
}
else if(time3_hours - now.hour() < 0){
//Serial.println((String)"Time 3 : Past");
time3_incoming = false;
time3_compare = 99;
}
if(time1_compare < time2_compare && time1_compare <time3_compare){
time1_incoming = true;
}
else if(time2_compare < time1_compare && time2_compare < time3_compare){
time2_incoming = true;
}
else if(time3_compare < time1_compare && time3_compare < time2_compare){
time3_incoming = true;
}
//เปรียบเทียบเวลาที่ส่งมาจาก Blynk กับ RTC
if(now.hour() == time1_hours && now.minute() == time1_minutes && now.second() == 0){
// DEBUG_PRINT(String("Time 1"));
bool_time1 = true;
}
else{
bool_time1 = false;
}
if(now.hour() == time2_hours && now.minute() == time2_minutes && now.second() == 0){
// DEBUG_PRINT(String("Time 2"));
bool_time2 = true;
}
else{
bool_time2 = false;
}
if(now.hour() == time3_hours && now.minute() == time3_minutes && now.second() == 0){
// DEBUG_PRINT(String("Time 3"));
bool_time3 = true;
}
else{
bool_time3 = false;
}
if (millis() - timer_page >=100) { //Status
//Serial.println((String)"Position: "+encoder_counter);
/* Main Display */
if (count == 0) {
lcd.setCursor(19,3);
lcd.print("1");
bool isconnected = Blynk.connected();
if (isconnected == false) {
//lcd.clear();
lcd.setCursor(0,0);
lcd.print(" ");
lcd.setCursor(0,0);
lcd.print("Status : Offline");
//Serial.println("Blynk Not Connected");
}
if (isconnected == true) {
//lcd.clear();
lcd.setCursor(0,0);
lcd.print(" ");
lcd.setCursor(0,0);
lcd.print("Status : Online");
//Serial.println("Blynk Connected");
}
lcd.setCursor(0,1);
lcd.print(now.hour(), DEC); //hour ชั่วโมง
lcd.print(":");
lcd.print(now.minute(), DEC); //minute นาที
lcd.print(":");
lcd.print(now.second(), DEC); //secound วินาที
lcd.print(" ");
lcd.setCursor(9,1);
lcd.print(now.day(), DEC);
lcd.print("/");
lcd.print(now.month(), DEC);
lcd.print("/");
lcd.print(now.year(), DEC);
if(time1_incoming == true && bool_time1 == false){
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,2);
lcd.print((String)"Time 1 : "+time1_hours +":" +time1_minutes);
lcd.setCursor(0,3);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print((String)"Food Value : "+mem_food1 +"g");
}
else if(time2_incoming == true && bool_time2 == false){
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,2);
lcd.print((String)"Time 2 : "+time2_hours +":" +time2_minutes);
lcd.setCursor(0,3);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print((String)"Food Value : "+mem_food2 +"g");
}
else if(time3_incoming == true && bool_time3 == false){
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,2);
lcd.print((String)"Time 3 : "+time3_hours +":" +time3_minutes);
lcd.setCursor(0,3);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print((String)"Food Value : "+mem_food3 +"g");
}
if(time1_incoming == true && bool_time1 == true){
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,2);
lcd.print((String)"Feeding : ");
lcd.setCursor(0,3);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print((String)"Food Value : "+mem_food1 +"g");
}
else if(time2_incoming == true && bool_time2 == true){
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,2);
lcd.print((String)"Feeding : ");
lcd.setCursor(0,3);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print((String)"Food Value : "+mem_food2 +"g");
}
else if(time3_incoming == true && bool_time3 == true){
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,2);
lcd.print((String)"Feeding : ");
lcd.setCursor(0,3);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print((String)"Food Value : "+mem_food3 +"g");
}
}
if (count == 1) {
lcd.setCursor(0,0);
lcd.print("Feed Time Value");
lcd.setCursor(0,1);
lcd.print("*");
lcd.setCursor(1,1);
lcd.print("Feed1 : ");
lcd.setCursor(9,1);
lcd.print(time1_hours);
lcd.setCursor(11,1);
lcd.print(":");
lcd.setCursor(12,1);
lcd.print(time1_minutes);
lcd.setCursor(16,1);
lcd.print((String)mem_food1 +"g");
lcd.setCursor(0,2);
lcd.print("*");
lcd.setCursor(1,2);
lcd.print("Feed2 : ");
lcd.setCursor(9,2);
lcd.print(time2_hours);
lcd.setCursor(11,2);
lcd.print(":");
lcd.setCursor(12,2);
lcd.print(time2_minutes);
lcd.setCursor(16,2);
lcd.print((String)mem_food2 +"g");
lcd.setCursor(0,3);
lcd.print("*");
lcd.setCursor(1,3);
lcd.print("Feed3 : ");
lcd.setCursor(9,3);
lcd.print(time3_hours);
lcd.setCursor(11,3);
lcd.print(":");
lcd.setCursor(12,3);
lcd.print(time3_minutes);
lcd.setCursor(16,3);
lcd.print((String)mem_food3 +"g");
}
if (encoder_counter == 3 && encoder_choice == true) { //Edit Display
lcd.setCursor(1,0);
lcd.print("Food1 :");
lcd.setCursor(1,1);
lcd.print("Food2 :");
lcd.setCursor(0,2);
lcd.print("Food3 :");
}
timer_page += 100;
timer_page = millis();
}
}
void main_feeder() {
//
//Serial.println((String)"Weight : "+LoadCell.get_units());
if(bool_time1){
food1_activate = true;
}
else{
}
if(bool_time2){
food2_activate = true;
}
else{
}
if(bool_time3){
food3_activate = true;
}
else{
}
/*Check Feeding*/
if(bool_time1 || bool_time2 || bool_time3 ){
bool_feeding = true;
}
else{}
//servo.write(174);
if(bool_feeding == true){
feed_activate = true;
while (feed_activate)
{
Serial.println("Feeddddddd");
lcd.clear();
cleanFunction();
delay(200);
selectFood();
delay(200);
feeding();
stepper.stop();
delay(200);
feed_activate = false;
food1_activate = false;
food2_activate = false;
food3_activate = false;
encoder_counter = 1;
bool_feeding = false;
}
}
}
void rotary(){
encoder_currenceState = digitalRead(encoder_outputA);
if (encoder_currenceState != encoder_lastState){
if (digitalRead(encoder_outputB) != encoder_currenceState) {
encoder_counter ++;
lcd.clear();
}
else{
encoder_counter --;
lcd.clear();
}
}
encoder_lastState = encoder_currenceState;
if (encoder_counter >= 3){
encoder_counter = 1;
}
else if (encoder_counter <= 0){
encoder_counter = 2;
}
}
void cleanFunction() {
lcd.clear();
lcd.print((String)"Cleanning !! ");
servo.write(clean_angle);
delay(500);
digitalWrite(Relay,HIGH);
delay(2000);
digitalWrite(Relay,LOW);
delay(100);
// int i;
// for (i = 0; i < 10; i++) {
// lcd.setCursor(0,0);
// lcd.print((String)"Cleanning !! "+i);
// servo.write(feed_angle);
// delay(200);
// servo.write(clean_angle);
// delay(500);
// }
}
void tareLoadCell() {
servo.write(feed_angle);
lcd.clear();
LoadCell.tare();
lcd.print((String)"Tare !! ");
delay(500);
lcd.clear();
}
void selectFood() {
if (food1_activate == true) {
value_food = mem_food1;
} else if (food2_activate == true) {
value_food = mem_food2;
} else if (food3_activate == true) {
value_food = mem_food3;
}
}
void feeding() {
lcd.clear();
tareLoadCell();
float weight = LoadCell.get_units();
float current_weight = weight;
float real_weight;
delay(200);
while (real_weight <= value_food) {
servo.write(feed_angle);
lcd.setCursor(0,0);
lcd.print("Feeding");
stepper.runSpeed();
weight = LoadCell.get_units(); // Update weight reading
real_weight = -weight - -current_weight;
blynk_weight = real_weight;
Blynk.virtualWrite(V_WEIGHT, blynk_weight);
lcd.setCursor(0,1);
lcd.print((String)"Feeding : " +real_weight +" g");
}
lcd.clear();
}
void stepper_motor(){
stepper.runSpeed();
}
//-----------------------------------------------------------------------------------------------------------//