/*
Projeto HydroSmart, que visa facilitar a gestão de hortas de hidroponia
*/
// importação das bibliotecas utilizadas
#include <Adafruit_NeoPixel.h>
#include <Servo.h>
// pinos alocados aos slides de cada cor
#define slideVermelho A0
#define slideAzul A1
#define interruptor 2 // switch para o modo automático do ciclo de luzes
bool flagInterruptor = false; // flag referente ao estado do switch
// pinos dos leds
#define ledVermelho 3
#define ledAzul 5
#define neoLed 6
#define nPixels 16 // numero de pixels no anel de neoPixels
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(nPixels); // alocando variável de neoPixels já com numero certo de pixels
int azul; // variável que guarda a intensidade de azul
int vermelho; // variável que guarda a intensidade de vermelho
int mudarAzul; // variavel responsável por mostrar o valor de azul quando ele mudar
int mudarVermelho; // variável respondavel por mostrar o valor de vermelho quando ele mudar
bool neoPixelUpdate = false; // flag q indica se neoPixels precisam de update (pesquisando, descobri q o update de neopixels pode conflitar com o movimento do servo, isso reduz esse conflito e otimiza o display dos leds)
// variáveis para funcionamento das válvulas
int pino;
Servo servo;
int parteInput; // variável que rastreia em que ponto está o processo de entrada do usuário
//debug: necessário?// criando array para maior facilidade de inicialização
int input[] = {slideAzul, slideVermelho, interruptor};
int output[] = {ledAzul, ledVermelho, neoLed};
void setup()
{
iniciar();
delay(25); // delay para evitar que o display mostre valores enquanto leds se ajusta
}
void loop()
{
if (digitalRead(interruptor) == false) // se o interruptor está desligado:
{
flag(false);
manual();
}
else // se o interruptor está ligado:
{
flag(true);
automatico();
}
}
// função para realizar toda inicialização necessária
void iniciar()
{
Serial.begin(9600); // iniciando o monitor serial
while (!Serial)
;
Serial.println("Serial setup done.");
// loop para pinos de input
for (int n; n < 3; n++)
{
pinMode(input[n], INPUT);
}
// loop para pinos de output
for (int n; n < 3; n++)
{
pinMode(output[n], OUTPUT);
}
pixels.begin(); // iniciando pixels do neoled
Serial.println("Pressione 'Enter' no monitor serial a qualquer momento para mudar o valor de vazão das válvulas de nutrientes\n");
}
// função responsável por atualizar o estado dos neoPixels
void neoP()
{
if (neoPixelUpdate == true) // se é necessário atualizar os neoPixels:
{
// para cada pixel, definir nova cor, depois atualizar todos de uma vez
for (int pixel = 0; pixel < nPixels; pixel++)
{
pixels.setPixelColor(pixel, pixels.Color(vermelho, 0, azul));
}
pixels.show(); //debug: conflita com o servo (wtf)
neoPixelUpdate = false; // constata que não é mais necessário alterar as cores
}
}
// função responsável por mudar a cor dos leds atravéz dos slides
void manual()
{
valvula(); // a função valvula() é chamada para que seja possível ao usuário dar entrada a qualquer momento
// definindo valores das cores de acordo com os slides e mudando proporção para os leds
azul = map(analogRead(slideAzul), 0, 1023, 0, 255);
vermelho = map(analogRead(slideVermelho), 0, 1023, 0, 255);
// caso os valores de vermelho ou azul tenham mudado:
// atualizar leds, variáveis de indicação das cores e avisar mudança aos leds
if (map(mudarVermelho, 0, 255, 0, 100) != map(vermelho, 0, 255, 0, 100))
{
neoPixelUpdate = true;
analogWrite(ledVermelho, vermelho);
neoP();
}
if (map(mudarAzul, 0, 255, 0, 100) != map(azul, 0, 255, 0, 100))
{
neoPixelUpdate = true;
analogWrite(ledAzul, azul);
neoP();
}
display(); // chama a função de atualização dos valores na tela
}
// função responsável por mudar a cor dos leds automáticamente, como se simulando dia e noite
void automatico()
{
// resetando os valores das cores
azul = 0;
vermelho = 0;
// loop para ativar azul até o máximo
for (azul; azul <= 255; azul++)
{
valvula(); // = linha 103
if (digitalRead(interruptor) == true) // se o interuptor continua ligado:
{
// atualizar cores
neoPixelUpdate = true;
analogWrite(ledAzul, azul);
neoP();
display(); // atualizar valores na tela
}
}
// depois que azul chegar ao máximo, desligar
azul = 0;
analogWrite(ledAzul, azul);
neoP();
// colocar vermelho no máximo
vermelho = 255;
// loop para escurecer vermelho até o mínimo
for (vermelho; vermelho > 0; vermelho--)
{
valvula(); // = linha 103
if (digitalRead(interruptor) == true) // = linha 141
{
// = linha 143
neoPixelUpdate = true;
analogWrite(ledVermelho, vermelho);
neoP();
display(); // = linha 147
}
}
// depois que vermelho chegar ao mínimo, atualizar (desligar)
analogWrite(ledVermelho, vermelho);
neoP();
delay(1); // delay entre iterações
}
// função responsável por atualizar os valores na tela
void display()
{
// caso os valores de vermelho ou azul tenham mudado:
// atualizar valores na tela e variáveis de indicação das cores
if (map(mudarVermelho, 0, 255, 0, 100) != map(vermelho, 0, 255, 0, 100))
{
Serial.print("Luz Vermelha em: ");
Serial.print(map(vermelho, 0, 255, 0, 100));
Serial.println("%");
mudarVermelho = vermelho;
}
if (map(mudarAzul, 0, 255, 0, 100) != map(azul, 0, 255, 0, 100))
{
Serial.print("Luz Azul em: ");
Serial.print(map(azul, 0, 255, 0, 100));
Serial.println("%");
mudarAzul = azul;
}
}
// função responsável por avisar exclusivamente quando o modo dos leds mudar
void flag(bool esperado) // requer uma variável que diz oque esperar da boolean
{
if (flagInterruptor == esperado) // se a flag for igual ao esperado:
{
Serial.print("__Modo "); // printar que o modo alterou
if (digitalRead(interruptor) == 0) // se o interruptor está desligado:
{
Serial.println("Manual__"); // o modo é manual
}
else // do contrário
{
Serial.println("Automático__"); // o modo é automático
}
flagInterruptor = !esperado; // mudar flag para o oposto do estado atual
}
}
// função responsável por coletar entrada do usuário e mudar vazão da válvula de acordo
void valvula()
{
if (Serial.available() > 0) // se há entrada
{
const unsigned int MAX_MESSAGE_LENGTH = 4; // tamanho máximo da entrada
while (parteInput != 3) // enquanto processo não atingir estágio 3:
{
if (parteInput == 0) // se o processo está no estágio 0:
{
while (Serial.available() > 0) // enquanto há entrada:
{
Serial.read(); // limpar um index da entrada
if (Serial.available() == 0) // se index é 0:
{
Serial.println("Digite o número do pino do nutriente:");
}
}
parteInput = 1; // ir para estágio 1
}
else if (parteInput == 1) // se o processo está no estágio 1:
{
while (Serial.available() > 0) // = 241
{
static char message[MAX_MESSAGE_LENGTH]; // cria mensagem com index máximo definido
static unsigned int message_pos = 0; // cria numero para servir de index da mensagem
char inByte = Serial.read(); // caractere é igual ao que está sendo lido
if ( inByte != '\n' && message_pos - MAX_MESSAGE_LENGTH - 1) // se caractere não é New Line e a mensagem não ultrapassa o limite:
{
message[message_pos] = inByte; // adicionar caractere a mensagem
message_pos++; // aumentar index da mensagem
}
else // se caractere é New Line ou a mensagem ultrapassa o limite:
{
message[message_pos] = '\0'; // adicionar caractere terminal a mensagem
pino = atoi(message); // converter mensagem a um inteiro
Serial.print("Pino selecionado: ");
Serial.println(pino);
if (pino == 0 || pino == 1 || pino == 2 || pino == 3 || pino == 5 || pino == 6) // se pino está ocupado:
{
Serial.println("\nerro: Pino já ocupado");
Serial.println("Digite o número do pino do nutriente:");
}
else if (pino < 0 || pino > 13) // se pino não existe:
{
Serial.println("\nerro: Pino não encontrado");
Serial.println("Digite o número do pino do nutriente:");
}
else // se tudo certo:
{
parteInput = 2; // ir para estágio 2
Serial.println("Digite o valor percentual da vazão na válvula desse nutriente:");
}
message_pos = 0; // resetar index da mensagem
}
}
}
else if (parteInput == 2) // se o processo está no estágio 2:
{
while (Serial.available() > 0) // = linha 241
{
static char message[MAX_MESSAGE_LENGTH]; // = linha 258
static unsigned int message_pos = 0; // // = linha 259
char inByte = Serial.read(); // = 261
if ( inByte != '\n' && message_pos - MAX_MESSAGE_LENGTH - 1) // = linha 263
{
message[message_pos] = inByte; // = linha 265
message_pos++; // = linha 266
}
else // = linha 269
{
message[message_pos] = '\0'; // = linha 271
int angulo = atoi(message); // = linha 273
if (angulo > 100) // se o angulo digitado é maior que 100%
{
angulo = 100; // angulo vira 100%
}
else if (angulo < 0) // se o angulo digitado é menor que 0%
{
angulo = 0; // angulo vira 0%
}
Serial.print("Nutriente no pino '");
Serial.print(pino);
Serial.print("' com vazão em: '");
Serial.print(angulo);
Serial.println("%'");
message_pos = 0; // = linha 299
servo.attach(pino); // pino do servo = pino digitado
servo.write(angulo * 1.8); // colocar servo no angulo digitado
parteInput = 3; // ir para estágio 3
}
}
}
delay(1); // = linha 178
continue; // tenta de novo caso processo não atenda requisitos
}
parteInput = 0; // se processo atende todos os requisitos, reinicia
delay(2000); // delay para permitir a leitura do usuário
servo.detach(); // remove servo do pino (evita conflito entre o servo e o update dos neopixels)
}
}
/*
referências:
https://www.youtube.com/watch?v=nSGnCT080d8
https://wokwi.com/projects/365725351341855745
https://wokwi.com/projects/315150937737921088
*/
/*
Obs:
- Foi descoberto após o projeto que o método parseint() pode ser utilizado para verificar input do usuário mais facilmente
*/