#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"
#include <HX711.h>
#include <SPI.h>
#include <MFRC522.h>
//
// Definicao de estruturas personalizadas
struct MenuItem
{
int id;
char text[20];
};
struct MenuScreen
{
int id;
char title[20];
MenuItem *options;
int numOptions;
};
//
// Pinagem
const int pinBtnUp = 0; // Botao pra cima
const int pinBtnDown = 1; // Botao pra baixo
const int pinBtnOk = 2; // Botao de confirmacao
const int pinBtnHome = 3; // Botao de voltar ao menu principal
const int pinEngine = 7; // Movimentar o carrinho
const int pinEngineReverse = 8; // Inverter o movimento do carrinho
const int pinMixer = 5; // Misturar a racao
const int pinFeeder = 6; // Alimentar o cocho
const int pinRefill = 4; // Recarregar o alimentador
const int pinScaleDT = A0; // Pino DT da balanca
const int pinScaleSCK = A1; // Pino SCK da balanca
const int pinFridSlaveSelect = 10; // Pino Slave Select do FRId
const int pinFridReset = 9; // Pino Reset do FRId
//
// Constantes importantes
const float scaleCalibration = 420.f; // Calibracao da balanca
const char fridCodeIndle[10] = "00"; // FRId de posicao de descanso
const char fridCodes[][10] = {"11", "22"}; // FRIds de cochos 1 e 2
const char fridCodesOne[][10] = {"11"}; // FRId de cocho 1
const char fridCodesTwo[][10] = {"22"}; // FRId de cocho 2
//
// Inicializacao de bibliotecas
LiquidCrystal_I2C lcd(0x27, 20, 4); // Inicializa o display LCD
MFRC522 mfrc522(pinFridSlaveSelect, pinFridReset); // Inicializa o Leitor FRId
RTC_DS1307 rtc; // Inicializa o modulo Real Time Clock
HX711 scale; // Inicializa o modulo da balanca
//
// Configuracao do Menu
const MenuItem menuHomeOptions[] = {
{1, "ALIMEN. MANUAL"},
{2, "ALIMEN. AGENDADA"},
{3, "RECARRE. MIXER"},
{4, "STATUS"},
{5, "FINALIZAR"}};
const MenuScreen menuHome = {0, "BOVINOMEC SENAI", menuHomeOptions, 5};
const MenuItem menuAlimManualOptions[] = {
{11, "ALIMEN. COCHO 1/2"},
{12, "ALIMEN. COCHO 1"},
{13, "ALIMEN. COCHO 2"}};
const MenuScreen menuAlimManual = {1, "ALIMEN. MANUAL", menuAlimManualOptions, 3};
const MenuItem menuAlimAutoOptions[] = {
{21, "VER AGENDAMENTO"},
{22, "NOVO AGENDAMENTO"},
{23, "EXCLUI AGENDAMENTO"}};
const MenuScreen menuAlimAuto = {2, "ALIMEN. AUTO", menuAlimAutoOptions, 3};
const MenuItem menuSeeShedulesOptions[] = {
{211, "AGEN. 16:50:00"},
{212, "PESO 010 KG"},
{213, "RACAO E SILAGEM"}};
const MenuScreen menuSeeShedules = {21, "VER AGENDAMENTO", menuSeeShedulesOptions, 3};
const MenuItem menuInsertShedulesOptions[] = {
{221, "HORA > 00:00:00"},
{222, "HORA > 00:00:00"},
{233, "PESO > 000 KG"}};
const MenuScreen menuInsertShedules = {22, "NOVO AGENDAMENTO", menuInsertShedulesOptions, 3};
const MenuItem menuDeleteShedulesOptions[] = {
{231, "AGEN. 16:50:00"},
{232, "AGEN. 10:30:00"},
{233, "EXCLUIR TODOS"}};
const MenuScreen menuDeleteShedules = {23, "EXCLUI AGENDAMENTO", menuDeleteShedulesOptions, 3};
const MenuItem menuStatusOptions[] = {
{41, "BATERIA 72,9%"},
{42, "PESO 07,3Kg"},
{43, "TEMPO LIGADO 04:24"},
{44, "RACAO GASTA 30,2KG"},
{45, "TEMPO MISTURA 10S"},
{46, "TEMPO ALIMEN. 15S"}};
const MenuScreen menuStatus = {4, "STATUS", menuStatusOptions, 6};
//
// Variaveis globais
char statusWork[20] = "OCIOSO"; // Status da maquina
bool showSplashScreen = true; // Flag para mostrar a tela de splash
int menuCurrentOption = 0; // Opcao atual do menu
unsigned long previousScaleMillis = 0; // Hora da ultima leitura da balanca
char scaleValue[20] = "0.0KG"; // Ultimo valor lido da balanca
unsigned long previousDateTimeMillis = 0; // Hora da ultima leitura do relogio
char dateTimeValue[20] = "00/00 00:00"; // Ultimo valor lido do relogio
MenuScreen menuCurrent = menuHome;
//
// Arduino Setup e Loop
void setup()
{
Serial.begin(9600);
// Configura os pinos
pinMode(pinBtnUp, INPUT_PULLUP);
pinMode(pinBtnDown, INPUT_PULLUP);
pinMode(pinBtnOk, INPUT_PULLUP);
pinMode(pinBtnHome, INPUT_PULLUP);
pinMode(pinEngine, OUTPUT);
pinMode(pinEngineReverse, OUTPUT);
pinMode(pinMixer, OUTPUT);
pinMode(pinFeeder, OUTPUT);
pinMode(pinRefill, OUTPUT);
// Inicia Modulo Relogio
rtc.begin();
// rtc.adjust(DateTime(2024, 9, 25, 16, 47, 30)); // Ajusta o tempo do RTC para a data e hora definida pelo usuario.
// Iniciar a balanca
scale.begin(pinScaleDT, pinScaleSCK);
scale.set_scale(scaleCalibration);
scale.tare();
// Inicia o leitor RFID
SPI.begin();
mfrc522.PCD_Init();
// Configura o display LCD
lcd.init();
lcd.backlight();
}
void loop()
{
updateDateTime();
updateScale();
if (showSplashScreen)
{
listenSplashScreen();
splashScreen();
}
else
{
listenMenuScreen();
menuScreen();
}
delay(100);
}
//
// Modo de Telas
void splashScreen()
{
lcd.clear();
// Linha 1: "BOVINOMEC"
lcd.setCursor(0, 0);
lcd.print("IHM BOVINOMEC SENAI");
// Linha 2 e 3: Status da operacao
lcd.setCursor(0, 1);
lcd.print("STATUS: ");
lcd.print(statusWork);
// Linha 4: Placeholder para data, hora e peso
lcd.setCursor(0, 3);
lcd.print(dateTimeValue);
lcd.setCursor(12, 3);
addSpacesBefore(scaleValue, 8);
lcd.print(scaleValue);
}
void menuScreen()
{
// Opcões do menu inicial
if (menuCurrent.id == 0)
{
menuCurrent = menuHome;
}
lcd.clear();
lcd.setCursor(0, 0);
// Titulo do menu
lcd.print(menuCurrent.title);
lcd.setCursor(0, 1);
// Selecionar opcões para mostrar
int optionsInit = 0;
int optionsEnd = menuCurrent.numOptions - 1;
// Se a opcao selecionada for maior que as opcões possiveis voltamos para o inicio
if (menuCurrentOption > menuCurrent.numOptions - 1)
{
menuCurrentOption = 0;
}
else if (menuCurrentOption < 0)
{
menuCurrentOption = menuCurrent.numOptions - 1;
}
// Se primeiro ou segundo menu selecionado, nao fazemos scroll
if (menuCurrentOption < 2)
{
optionsInit = 0;
optionsEnd = 2;
}
else
// Se for a ultima opcao mostramos os 3 ultimos resultados
if (menuCurrentOption == menuCurrent.numOptions - 1)
{
optionsInit = menuCurrent.numOptions - 3;
optionsEnd = menuCurrent.numOptions - 1;
}
// Senao mostramos um item antes e um depois do selecionado
else
{
optionsInit = menuCurrentOption - 1;
optionsEnd = menuCurrentOption + 1;
}
int line = 0;
for (int i = 0; i < menuCurrent.numOptions; i++)
{
if (i >= optionsInit && i <= optionsEnd)
{
line++;
lcd.setCursor(0, line);
lcd.print(i == menuCurrentOption ? "> " : " ");
lcd.print(menuCurrent.options[i].text);
}
}
}
//
// Acoes do Menu
void selectMenuOption()
{
MenuItem selectedItem = menuCurrent.options[menuCurrentOption];
menuCurrentOption = 0;
int selectedId = selectedItem.id;
switch (selectedId)
{
case 1:
menuCurrent = menuAlimManual;
break;
case 2:
menuCurrent = menuAlimAuto;
break;
case 3:
runRefiller();
break;
case 4:
menuCurrent = menuStatus;
break;
case 5:
strcpy(statusWork, "OCIOSO");
showSplashScreen = true;
break;
case 11:
Serial.println("Alimentando cocho 1 e 2...");
runFeeder(fridCodes, 2, "\nALIMENTANDO COCHO1/2");
break;
case 12:
Serial.println("Alimentando cocho 1...");
runFeeder(fridCodesOne, 1, "\nALIMENTANDO COCHO 1");
break;
case 13:
Serial.println("Alimentando cocho 2...");
runFeeder(fridCodesTwo, 1, "\nALIMENTANDO COCHO 2");
break;
case 21:
menuCurrent = menuSeeShedules;
break;
case 22:
menuCurrent = menuInsertShedules;
break;
case 23:
menuCurrent = menuDeleteShedules;
break;
default:
menuCurrent = menuHome;
break;
}
}
void goToHomeMenu()
{
menuCurrent = menuHome;
menuCurrentOption = 0;
showSplashScreen = false;
strcpy(statusWork, "OCIOSO");
lcd.clear();
}
//
// Listens
void listenSplashScreen()
{
if (digitalRead(pinBtnOk) == LOW)
{
goToHomeMenu();
waitHIGH(pinBtnOk);
}
}
void listenMenuScreen()
{
listenMenuScreenBtnUp();
listenMenuScreenBtnDown();
listenMenuScreenBtnOk();
listenMenuScreenBtnHome();
}
void listenMenuScreenBtnUp()
{
if (digitalRead(pinBtnUp) == LOW)
{
menuCurrentOption--;
waitHIGH(pinBtnUp);
}
}
void listenMenuScreenBtnDown()
{
if (digitalRead(pinBtnDown) == LOW)
{
menuCurrentOption++;
waitHIGH(pinBtnDown);
}
}
void listenMenuScreenBtnHome()
{
if (digitalRead(pinBtnHome) == LOW)
{
goToHomeMenu();
waitHIGH(pinBtnHome);
}
}
void listenMenuScreenBtnOk()
{
if (digitalRead(pinBtnOk) == LOW)
{
selectMenuOption();
waitHIGH(pinBtnOk);
}
}
//
// Funcoes de Bibliotecas
void updateDateTime()
{
unsigned long currentMillis = millis();
if (previousDateTimeMillis == 0 || currentMillis - previousDateTimeMillis >= 10000)
{
previousDateTimeMillis = currentMillis;
DateTime nowDateTime = rtc.now();
// Formata a data e hora como 03/09 16:47
char dateTime[20];
sprintf(dateTime, "%02d/%02d %02d:%02d", nowDateTime.day(), nowDateTime.month(), nowDateTime.hour(), nowDateTime.minute());
strcpy(dateTimeValue, dateTime);
}
}
void updateScale()
{
unsigned long currentMillis = millis();
if (previousScaleMillis == 0 || currentMillis - previousScaleMillis >= 5000)
{
previousScaleMillis = currentMillis;
char buffer[10];
float newScaleValue = scale.get_units();
dtostrf(newScaleValue, 5, 1, buffer);
// Atualiza o valor da balanca com o final "KG"
sprintf(scaleValue, "%sKG", buffer);
}
}
void getFRId(char *charVariable)
{
// Fake Read FRId pro Wolkwi
if (Serial.available() > 0)
{
String result = Serial.readString();
result.toUpperCase();
result.trim();
strcpy(charVariable, result.c_str());
}
}
void getFRId_OFF_NO_WOKWI(char *charVariable)
{
String result = "";
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial())
{
result = "";
for (byte i = 0; i < mfrc522.uid.size; i++)
{
result.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "));
result.concat(String(mfrc522.uid.uidByte[i], HEX));
}
result.toUpperCase();
result.trim();
mfrc522.PICC_HaltA();
strcpy(charVariable, result.c_str());
}
}
//
// Acoes Principais
void runRefiller()
{
// Define o status e volta para Splash
strcpy(statusWork, "RECARREGANDO");
showSplashScreen = true;
splashScreen();
// Andamos até a leitura do FRId
waitSeconds(5);
carGoToPosition(fridCodeIndle, carGoToBackwards);
waitSeconds(2);
// Recarregamos o alimentador e misturamos a racao
refillTheFeeder();
mixTheFood();
// Define o status e volta para Splash
strcpy(statusWork, "OCIOSO");
}
void runFeeder(char cattleCodes[][10], int arrayLength, char statusName[20])
{
bool isLast = false;
// Define o status e volta para Splash
strcpy(statusWork, statusName);
showSplashScreen = true;
splashScreen();
for (int i = 0; i < arrayLength; i++)
{
if (i == arrayLength - 1)
{
isLast = true;
}
// Andamos até a leitura do FRId
carGoToPosition(cattleCodes[i], carGoToForwards);
waitSeconds(5);
// Misturamos a racao
mixTheFood();
// Alimentamos o cocho
dumpTheFood();
// Se for o ultimo gado, voltamos para a posicao de descanso
if (isLast)
{
carGoToPosition(fridCodeIndle, carGoToBackwards);
}
}
// Define o status e volta para Splash
strcpy(statusWork, "OCIOSO");
showSplashScreen = true;
splashScreen();
}
//
// Car Actions
void carGoToPosition(char position[10], void (*moveCarFunction)())
{
// Faz o carrinho se mover
moveCarFunction();
// Variavel para armazenar o FRId lido
char fridReaded[10] = "";
while (strcmp(fridReaded, position) != 0)
{
// Fake Read FRId pro Wolkwi
getFRId(fridReaded);
// Real Read FRId
// getFRId_OFF_NO_WOKWI(fridReaded);
}
carStop();
}
void carGoToForwards()
{
Serial.println("Movendo o carrinho para frente...");
digitalWrite(pinEngine, HIGH);
}
void carGoToBackwards()
{
Serial.println("Movendo o carrinho para tras...");
digitalWrite(pinEngineReverse, HIGH);
digitalWrite(pinEngine, HIGH);
}
void carStop()
{
Serial.println("Parando o carrinho...");
digitalWrite(pinEngine, LOW);
digitalWrite(pinEngineReverse, LOW);
waitSeconds(2);
}
//
// Food Actions
void mixTheFood()
{
digitalWrite(pinMixer, HIGH);
Serial.println("Misturando a racao...");
waitSeconds(10);
digitalWrite(pinMixer, LOW);
waitSeconds(2);
}
void dumpTheFood()
{
digitalWrite(pinFeeder, HIGH);
Serial.println("Alimentando o cocho...");
waitSeconds(5);
digitalWrite(pinFeeder, LOW);
waitSeconds(2);
}
void refillTheFeeder()
{
digitalWrite(pinRefill, HIGH);
Serial.println("Recarregando o alimentador...");
waitSeconds(15);
digitalWrite(pinRefill, LOW);
waitSeconds(2);
}
//
//
// Helpers
void waitHIGH(int pin)
{
while (digitalRead(pin) == LOW)
{
}
}
void waitLOW(int pin)
{
while (digitalRead(pin) == HIGH)
{
}
}
void addSpacesBefore(char *str, int size)
{
int len = strlen(str);
if (len >= size)
{
return;
}
int spacesToAdd = size - len;
for (int i = len; i >= 0; i--)
{
str[i + spacesToAdd] = str[i];
}
for (int i = 0; i < spacesToAdd; i++)
{
str[i] = ' ';
}
}
void waitSeconds(int seconds)
{
int totalDelays = seconds * 2;
Serial.println("Aguardando " + String(seconds) + " segundos...");
for (int i = 0; i < totalDelays; i++)
{
updateDateTime();
updateScale();
splashScreen();
delay(500);
}
}