ArduFarmBot: Part 2 – “Estação Remota” – IoT

Este post é uma continuação do ArduFarmBot: Controlando um tomateiro com a ajuda de um Arduino e Internet das coisas (IoT)

Na primeira parte do projeto, criamos uma estação local de controle e captura de informações, tais como temperatura, umidade relativa do ar, luminosidade e umidade do solo. Com base nesses dados, o ArduFarmBot decidia automaticamente a quantidade certa (e quando) o tomateiro deveria receber calor e água. A estação local de controle desenvolvida na Parte 1, também permitia a intervenção manual de um operador a fim de controlar tanto a bomba de água quanto a lâmpada elétrica. Nesta  segunda parte, implementaremos uma abordagem “IoT” onde a”intervenção manual” também poderá ser feita remotamente via Internet. O diagrama de blocos mostra como faremos isso:

ardufarmbot_gal_block_diagram

Note-se que os dados capturados serão enviados a um “serviço de armazenamento na nuvem” (no nosso caso Thinkspeak.com). Além disso, um website dedicado, o “Remote Control Page” mostrado no diagrama,  irá monitorar e exibir esses dados em tempo quase real. Esta página também permitirá a ativação remota da bomba e  da lâmpada.

2.1: Lista de materiais

2.2: Completando o Hardware

Para conectar-se o ArduFarmBot à Internet, utilizaremos o ESP8266, um módulo simples, barato e fácil de programar para os projectos envolvendo a Internet das coisas (IoT). A partir da estação local desenvolvida na Parte 1, o único HW adicional necessário é o próprio ESP8266. O diagrama de blocos abaixo, mostra todas as conexões com os pinos do Arduino e dos componentes principais.

ardufarmbot_pin_diagram

O único cuidado que você deve ter é relacionado com o nível de tensão do pino RX do ESP8266, pois ele funciona com 3.3V. Assim, o Pin Rx não deve ser conectado diretamente ao pino Tx do Nano (D3). Um nívelador de tensão deve ser usado. No nosso caso, vamos construir um divisor de tensão para ser usado como um “conversor de nível de tensão”.

Voltage level Converter.png

O esquema eléctrico abaixo, mostra com mais detalhes como se conectar o ESP8266.

ardufarmbot_complete_eletr_diagram

Se você quiser saber mais sobre o ESP8266, consulte meus posts anteriores aqui no Blog:

  1. O ESP8266 parte 1 – Serial WIFI Module
  2. O ESP8266 parte 2 – Web Server
  3. O ESP8266 parte 3 – Acionando LEDs remotamente

Note que estamos usando o ESP8266 conectado aos pinos 2 (Tx) e 3 (Rx) do Nano, utilizando-se da biblioteca “SoftSerial”. Se você quiser “livrar” os pinos digitais, poderá alternadamente usar os pinos 0 e 1 do Nano (Serial por HW). Apenas lembre-se que você deve desligá-los ao enviar o código para Nano.

NOTA: Se você deseja se conectar o “Buzzer”, você deve fazê-lo no pino D17 (o mesmo que o pino A3). É bom ter um alarme sonoro quando ocorra um erro de comunicação. Eu o usei durante a fase de teste, deixando de fora no HW do projecto final (mas o código está preparado para isso). Cabe a você decidir se o insta-la ou não.

O código abaixo pode ser usado para testar e / ou configurar o  ESP8266:

2.3: Conectando o  ESP8266  a internet

Uma vez que o módulo ESP8266 está instalado, o próximo passo é aplicar um “Reset” no pino CH-PD.

/***************************************************
* Reset funtion to accept communication
****************************************************/
void reset8266(void)
{
  pinMode(CH_PD, OUTPUT);
  digitalWrite(CH_PD, LOW);

  delay(300);
  digitalWrite(CH_PD, HIGH);
  Serial.print("8266 reset OK");
  lcd.clear();
  lcd.println("8266 reset OK       ");
}

Após o reset, vamos conectá-lo à rede local  (alterar o no código: username e password, por suas credenciais ) e iniciar o módulo como “Modo Station” (CWMODE = 1):

/***************************************************
* Connect WiFi
****************************************************/
void connectWiFi(void)
{
  sendData("AT+RST\r\n", 2000, DEBUG); // reset
  sendData("AT+CWJAP=\"USERNAME\",\"PASSWORD\"\r\n", 2000, DEBUG); //Connect network
  delay(3000);
  sendData("AT+CWMODE=1\r\n", 1000, DEBUG);
  sendData("AT+CIFSR\r\n", 1000, DEBUG); // Show IP Adress
  lcd.clear();
  lcd.print("8266 Connected");
  Serial.println("8266 Connected");
}

Para enviar dados ao ESP8266, utilizamos a função sendData():

/***************************************************
* Send AT commands to module
****************************************************/

String sendData(String command, const int timeout, boolean debug)
{
  String response = "";
  esp8266.print(command);
  long int time = millis();
  while ( (time + timeout) > millis())
  {
    while (esp8266.available())
    {
      // The esp has data so display its output to the serial window
      char c = esp8266.read(); // read the next character.
      response += c;
    }
  }
  if (debug)
  {
    Serial.print(response);
  }
  return response;
}

As funções acima serão chamadas durante a “fase de setup” de nosso Código. Se tudo foi feito corretamente, você deve ver pelo monitor serial, mensagens semelhantes como as de abaixo:

Picture of The ESP8266 connection

2.4: Data Storage Cloud: O ThinkSpeak.com

Todos os dados capturados pelo ArduFarmBot serão enviados para a nuvem, utilizando-se um serviço gratuito proporcionado pelo “ThinkSpeak.com“.

No “Loop ()”, depois de capturar os dados com readSensors (),  chamaremos uma função específica para carregar os dados capturados: updateDataThingSpeak ();

/***************************************************
* Transmit data to thingspeak.com
****************************************************/
void updateDataThingSpeak(void)
{
  startThingSpeakCmd ();
  
  cmd = msg ;
  cmd += "&field1=";     //field 1 for DHT temperature
  cmd += tempDHT;
  cmd += "&field2=";    //field 2 for DHT humidity
  cmd += humDHT;
  cmd += "&field3=";    //field 3 for LDR luminosity
  cmd += lumen;
  cmd += "&field4=";    //field 4 for Soil Moisture data 
  cmd += soilMoist;
  cmd += "&field5=";    //field 5 for PUMP Status
  cmd += pumpStatus;
  cmd += "&field6=";    //field 6 for LAMP Status
  cmd += lampStatus;
  cmd += "\r\n";

  sendThingSpeakCmd();
}

A fim de enviar esses dados, a primeira coisa a fazer é iniciar a comunicação com o ThingSpeak.com. Faremos isso, usando-se da função:startThingSpeakCmd ();

/***************************************************
* Start communication with ThingSpeak.com
****************************************************/
void startThingSpeakCmd(void)
{
  cmd = "AT+CIPSTART=\"TCP\",\"";
  cmd += IP;
  cmd += "\",80";
  esp8266.println(cmd);
  delay(2000);
  if(esp8266.find("Error"))
  {
    Serial.println("ESP8266 START ERROR");
    return;
  }
  Serial.println("Thinkspeak Comm Started");
  cmd ="";
}

Uma vez que o canal esteja aberto com o ThingSpeak e o string “cmd”  esteja montado com os dados, é hora de fazer o upload de tudo isso no canal correspondente do ThingSpeak usando-se da função:sendThingSpeakCmd();

/***************************************************    
* Update channel ThingSpeak.com
****************************************************/
String sendThingSpeakCmd(void)
{
  esp8266.print("AT+CIPSEND=");
  esp8266.println(cmd.length());
  if(esp8266.find(">")){
    esp8266.print(cmd);
    Serial.println("");
    Serial.println("");
    Serial.println(cmd);
    delay(500);
   
    String messageBody = "";
    while (esp8266.available()) 
    {
      String line = esp8266.readStringUntil('\n');
      if (line.length() == 1) 
      { //actual content starts after empty line (that has length 1)
        messageBody = esp8266.readStringUntil('\n');
        Serial.print("Message received: ");
        Serial.println(messageBody);
      }
    }
    return messageBody;
  }
  else{
    esp8266.println("AT+CIPCLOSE");
    Serial.println("ESP8266 CIPSEND ERROR: RESENDING"); //Resend...
    error=1;
    return "error";
  }
}

As funções acima foram baseadas em um tutorial muito bom desenvolvido por Michalis Vasilakis. Para mais detalhes, consulte:

Arduino IOT: Temperature and Humidity ( with ESP8266 WiFi).

A foto abaixo mostra o canal ArduFarmBot no site do ThingSpeak.com:

Picture of Cloud Storage Data: The ThinkSpeak.com

2.5: Comandando os atuadores a partir da web

Neste momento, estamos subindo (“Upload”) e armazenando todos os dados coletados na nuvem. Isto é muito útil para a monitoração remota, mas o que aconteceria se com base nesses dados gostaríamos de acionar a bomba ou a Lâmpada, independente do programa automático local? Para fazer isso, além do “Upload”, também faremos um “Download” de dados a partir da nuvem, informando ao controlador como agir basedo nesses dados (comandos).

Criemos para isso, campos específicos em nosso canal ThinkSpeak para comandar os atuadores:

  • Field 7:
    • Data = 1 ==> PUMP should be Turn ON
    • Data = 0 ==> PUMP should be Turn OFF
  • Field 8:
    • Data = 1 ==> LAMP should be Turn ON
    • Data = 0 ==> LAMP should be Turn OFF

OK, mas como configurar esses campos diretamente no ThingSpeak? Podemos fazê-lo, por exemplo, escrevendo um “plugin” diretamente para ThinksPeak, ou podemos usar um site externo para fazê-lo (esta será a nossa escolha). De qualquer forma, em ambos os casos, você deverá usar um comando como o de abaixo:

api.thingspeak.com/update?key=YOUR_WRITE_KEY&field7=1

Com o comando acima, por exemplo (e usando a sua chave de escrita de canal), você vai escrever “1” no Field 7, o que significa que a bomba deve ser ligada. Você pode facilmente testá-lo, escrevendo a linha de comando acima no seu navegador, o campo correspondente no seu canal será alterado. Como um retorno, o navegador irá mostrar uma página em branco com um único número no canto superior esquerdo, correspondente à entrada de dados sequencial no seu canal.

Até agora, 50% do trabalho está feito! Agora você deverá ler este “comando” (conteúdo  do campo), na estação local do ArduFarmBot .

O comando para fazer isso é mostrado abaixo. Ele vai obter o último dado que foi escrito no campo específico (que em nosso caso será um “comando”).

api.thingspeak.com/channels/CHANNEL_ID/fields/7/last

Da mesma forma como fizemos antes, você poderá testar a linha de comando, usando seu navegador. Neste caso, o navegador irá mostrar-lhe o dado armazenado esse campo específico. Veja abaixo:

Picture of Commanding the actuators from the web

Voltando à “terra”, escreveremos uma função que lerá este “último campo”:

/***************************************************
* Read data from field7 of thingspeak.com
****************************************************/
int readLastDataField7()
{
  startThingSpeakCmd ();

  // "GET /channels/CHANNEL_ID/fields/7/last";
  cmd = msgReadLastDataField7; 
  cmd += "\r\n";

  String messageDown = sendThingSpeakCmd();
  Serial.print("Command received: ");
  Serial.println(messageDown[7]);
  
  int command = messageDown[7]-48; 
  return command;
}

A função acima retornará o dado contido no Field 7 (“1” ou “0”). Uma função semelhante deve ser escrita para o Field 8.

Uma vez que já possuímos o conteúdo de ambos os campos, devemos utilizá-los em uma função que comandará os atuadores de maneira semelhante ao que fizemos com a “função de comando manual”:

/***************************************************
* Receive Commands from thingSpeak.com
****************************************************/
void receiveCommands()
{
  field7Data = readLastDataField7();
  if (field7Data == 1) 
  {
    digitalWrite(PUMP_PIN, HIGH);
    pumpStatus = 1;
    showDataLCD();
  }
  if (field7Data == 0) 
  {
    digitalWrite(PUMP_PIN, LOW);
    pumpStatus = 0;
    showDataLCD();
  }

  delay (500); 
  
  field8Data = readLastDataField8();
  if (field8Data == 1) 
  {
    digitalWrite(LAMP_PIN, HIGH);
    lampStatus = 1;
    showDataLCD();
  }
  if (field8Data == 0) 
  {
    digitalWrite(LAMP_PIN, LOW);
    lampStatus = 0;
    showDataLCD();
  }
  delay (500); 
}

Assim, a partir de agora você pode usar a linha de comando no seu navegador para ligar / desligar a bomba e a lâmpada remotamente. Abaixo, se pode ver como o comando recebido aparecerá no monitor serial:

Download command.png

Outra consideração importante é a “coordenação” entre o comando local e o remoto. Teremos de alterar a função readLocalCmd () para que também atualizemos os Fields7  e 8 do Thinkspeak respectivamente,  como o estado da bomba e da lâmpada:

field7Data = pumpStatus;

field8Data = lampStatus;

Agora “filed7Data” e “field8Data” estão em sincronia com os comandos da página web e também com as ações de comando locais quando você pressiona um botão. Então, vamos atualizar a função aplyCmd (), que é a responsável para ligar / desligar os atuadores:

/***************************************************
* Receive Commands and act on actuators
****************************************************/
void aplyCmd()
{
  if (field7Data == 1) digitalWrite(PUMP_PIN, HIGH);
  if (field7Data == 0) digitalWrite(PUMP_PIN, LOW);
  
  if (field8Data == 1) digitalWrite(LAMP_PIN, HIGH);
  if (field8Data == 0) digitalWrite(LAMP_PIN, LOW);
}

Quando você começar seus testes,  perceberá que qualquer comando executado manualmente no local ou via web, será superado pelas ações automáticas definidas pela função autoControlPlantation(). Neste ponto, você deve considerar quem será o “chefe” (ou patroa…), o qual terá a última palavra! Em nosso caso definiremos o seguinte:

  • Em cada ciclo de loop, que é quase “sempre”, verificamos se um botão local é pressionado
  • Aproximadamente a cada minuto, faremos um “pooling” no ThinkSpeak e verificamos se existe um nova ordem manual desde lá.
  • Aproximadamente  a cada 10 minutos, leremos os sensores, atualizando os dados no ThinkSpeak e mais importante,  executamos as medidas automáticas, que serão tomadas independente das que foram selecionados manualmente e será aquela que irá ser mantido.

Você pode alterar a lógica da maneira que melhor se adapte ao seu projeto. Essa é a coisa boa sobre o uso de um processador programável, como o Arduino para controlar coisas!

Assim, 2 temporizadores serão utilizados agora, um para agrupar os comandos remotos e outro para ler os sensores (o mesmo que foi utilizado anteriormente):

long sampleTimingSeconds = 75; // ==> ******** Define Sample time in seconds to read sensores *********
int reverseElapsedTimeSeconds = 0;
long startTiming = 0;
long elapsedTime = 0;
long poolingRemoteCmdSeconds = 20; // ==> ******** Define Pooling time in seconds for new ThingSpeak commands *********
long startRemoteCmdTiming = 0;  
long elapsedRemoteCmdTime = 0;

A função loop () deverá ser reescrita da seguinte forma:

void loop() 
{
  elapsedRemoteCmdTime = millis()-startRemoteCmdTiming;   // Start timer for pooling remote commands
  elapsedTime = millis()-startTiming;   // Start timer for measurements
  reverseElapsedTimeSeconds = round (sampleTimingSeconds - elapsedTime/1000);

  readLocalCmd(); //Read local button status
  showDataLCD();

  if (elapsedRemoteCmdTime > (poolingRemoteCmdSeconds*1000)) 
  {
    receiveCommands();
    updateDataThingSpeak();
    startRemoteCmdTiming = millis();
  }
  
  if (elapsedTime > (sampleTimingSeconds*1000)) 
  {
    readSensors();
    autoControlPlantation();
    updateDataThingSpeak();
    startTiming = millis();
  }
}

2.6: Implementação de uma página Web dedicada

Neste ponto o nosso ArduFarmBot está operacional e pode ser controlado a partir da web. Você pode monitorar os dados no site do Thinkspeak e também enviar comandos utilizando-se de um navegador, mas é claro que esta “solução web” não pode ser considerado “elegante”! A melhor maneira de implementar uma solução real para IoT, é desenvolver uma página web completa, a qual além de exibir todos os dados, também possuirá botões para ativação dos atuadores.

Para hospedar nossa página web, escolhí o Byethost, um site gratuito, muito fácil e simples para lidar com suas páginas. Também aproveitei a oportunidade de aplicar neste projecto, o que eu aprendi em uma fantástica especialização on-line no Coursera / Universidade de Michigan:Learn to Design and Create Websites (Construindo um portfólio web ágil e acessível usando HTML5, CSS3 e JavaScript ).

webpage

Não entrarei em detalhes como desenvolver esse tipo de página, uma vez que este não é o propósito central deste tutorial, mas incluirei aqui o HTML, CSS e códigos fonte do JavaScript que desenvolvi exclusivamente para minha página. Uma vez que alguém tenha interesse em como cheguei ao projeto final, podemos discuti-lo utilizando-se do Painel de Comentários no final do post.

É importante reforçar que essa página não trabalha diretamente com a Estação de Controle ArduFarmBot Local. O que ela está fazendo realmente é, interagir com o canal do ThinkSpeak  como descrito abaixo:

  1. Ler dados de sensores dos Fields 1, 2, 3, 4
  2. Ler estado do atuadores  (“Echo”) dos Fields  5 e 6
  3. Armazenar dados (comandos) nos Fields 7 e 8
  4. Ler dados meteorológicos locais (Yahoo)

O item 4 acima não é realmente crucial para o projeto, mas é importante ter dados adicionais disponíveis no caso de você querer tomar algumas ações remotas independente do que está acontecendo localmente com plantação do seu tomate. Outra consideração é que você pode, por exemplo, armazenar esses dados em outro canal ThingSpeak e transferir-los para o Arduino, mostrando dados meteorológicos no display LCD local por exemplo (eu desenvolvi isto em outro projeto que funcionou bem! Deixo aqui como uma sugestão para você).

2.7: A Matriz Sensor-Atuador

Picture of Return to the brain. A Sensor-Actuator Matrix approach

Na primeira parte deste projeto, definimos algumas considerações preliminares sobre como os atuadores devem agir em função da leitura dos sensores. Naquele caso, fizemos unicamente opções simples, mas o que vai acontecer se tivermos uma situação mais complexa? Várias condições diferentes? O que iremos desenvolver aqui é uma nova abordagem que chamaremos de “Matriz Sensor – Atuador”.

Em uma planilha foram definidas para cada sensor, as diferentes condições possíveis  e como deveria ser a respectiva saída dos atuadores. O resultado pode ser observado na planilha Excel abaixo.

Matrix.png

Na planilha Excel, existem duas Tabs. Uma com filtro como mostrado acima e outra que além da seleção, te permite obter o “codigo”binário que deverá ser carregado na varíavel da Matriz.

New Matrix Setup.png

Uma vez definida, a matriz deverá ser carregada em nosso código. Um array de 18 linhas e 10 colunas foi criado para este propósito:

//                       +---SOIL----+-LIGHT-+---TEMP---+---ACTUAT----+
 //                        SL  SM  SH  LL  LH  TL  TM  TH   Pump   Lamp
boolean SDf [18] [10] = {{  1,  0,  0,  0,  1,  0,  0,  1,    1,    0 }, 
                          { 1,  0,  0,  0,  1,  0,  1,  0,    1,    0 }, 
                          { 1,  0,  0,  0,  1,  1,  0,  0,    1,    1 }, 
                          { 1,  0,  0,  1,  0,  0,  0,  1,    1,    0 }, 
                          { 1,  0,  0,  1,  0,  0,  1,  0,    1,    0 }, 
                          { 1,  0,  0,  1,  0,  1,  0,  0,    0,    1 }, 
                          { 0,  1,  0,  0,  1,  0,  0,  1,    0,    0 }, 
                          { 0,  1,  0,  0,  1,  0,  1,  0,    0,    0 }, 
                          { 0,  1,  0,  0,  1,  1,  0,  0,    0,    1 }, 
                          { 0,  1,  0,  1,  0,  0,  0,  1,    0,    0 }, 
                          { 0,  1,  0,  1,  0,  0,  1,  0,    0,    1 }, 
                          { 0,  1,  0,  1,  0,  1,  0,  0,    0,    1 }, 
                          { 0,  0,  1,  0,  1,  0,  0,  1,    0,    0 }, 
                          { 0,  0,  1,  0,  1,  0,  1,  0,    0,    0 },
                          { 0,  0,  1,  0,  1,  1,  0,  0,    0,    1 },
                          { 0,  0,  1,  1,  0,  0,  0,  1,    0,    0 },
                          { 0,  0,  1,  1,  0,  0,  1,  0,    0,    1 },
                          { 0,  0,  1,  1,  0,  1,  0,  0,    0,    1 },
                         };

Para trabalhar com a Matrix, criou-se a função defSensorStatus (). Esta função testa para cada linha se a condição das 8 primeiras colunas são TRUE. Se sim, as condições definidas nas 2 últimas colunas são executadas.

por exemplo:

if (1 and 0 and 0 and 0 and 1 and 0 and 0 and 1) { pumpStatus = 1; lampStatus = 0}

else if (1 and 0 and 0 and 0 and 1 and 0 and 1 and 0) { pumpStatus = 1; lampStatus = 0}

e assim por diante …

Dentro da função acima outra array é criada com o estado de cada leitura do sensor:

boolean snsSts[8]={0, 0, 0, 0, 0, 0, 0, 0}; // SL, SM, SH, LL, LH, TL, TM, TH

Esta array variável será também utilizado para o registo do LOG.

Segue abaixo a planilha excel:

ARDUFARMBOT SENSOR ACTUATOR MATRIX NEW.xlsx ARDUFARMBOT SENSOR ACTUATOR MATRIX NEW.xlsx

2.8: Otimizando o código

Durante o processo de desenvolvimento do ArduFarmBot percebemos que algumas mudanças na especificação original deveria ser feita:

Display LCD:

O display LCD deve estar apagado por default e a qualquer momento que uma leitura de sensores é necessária, podemos acender-lo. Esta condição foi implementada no código e o botão de leitura de sensores deve ser utilizado no modo “toggle” para ligar / desligar o LCD a qualquer momento. Acender ou apagar o LCD irá automaticamente atualizar as leituras de sensores para a exibição, mas estas leituras não serão utilizadas pelo ArduFarmBot em suas funções regulares.

Configuração inicial:

Quando o ArduFarmBot é ligado (ou depois de um Reset), o LCD irá exibirá configuração  do controlador “Initial Set-up”. Para se iniciar a execução do programa, o botão “Sensores” deve ser pressionado novamante (o LCD se apagará). A informação inicial disponível na tela inicial do LCD será:

  • COLD Temperature (i.e. 12oC)
  • DRY Soil Humidity (i.e. 30%)
  • WET Soil Humidity (i.e. 60%)
  • DARK light (i.e. 40%)
  • P_ON Pump time be ON (i.e. 10s)
  • SCAN Time to read sensors (i.e. 600s)
  • SW_Vertion (i.e. 4.1)
img_0013
Registro do Log:

Para fins de auditoria, criamos um registro com as leituras e atuações do nosso Ardu FarmBot. Em cada ciclo de leitura, a função: storeDataLogEEPROM () é executada:

/***************************************************
* Storage of Log data at Arduino EEPROM
****************************************************/
void storeDataLogEEPROM(void)
{
  for (int i = 0; i<8; i++) 
  {
    logData = logData + (snsSts[i])<< 1;
  }
  EEPROM.write (memoAddr, logData);
  memoAddr++;
  logData = 0;
  logData = logData + pumpStatus;
  logData = logData << 1;
  logData = logData + lampStatus;
  EEPROM.write (memoAddr, logData);
  EEPROM.write (0, memoAddr+1);
  logData = 0; 
  if ((memoAddr+1) == 1023) memoAddr=1;
  else memoAddr++;
} 

Como comentado na última etapa, o que será armazenada na EEPROM do Arduino é o conteúdo, bit a bit do array snsSts[] adicionado do status da bomba e lâmpada. Você pode ver abaixo, o log no monitor serial:

serial-monitor-log

Como já visto na parte 1, para facilidade de entendimento e desenvolvimento, o código para o ArduFarmBot foi divididos em vários arquivos, criados por sua especialidade. Note que 2 novos arquivos foram adicionados nesta segunda parte:

  • communication.ino (ThingSpeak and ESP8266 specific funtions)
  • stationCredentials.h (ThinkSpeak Channel ID and specific Keys for writing on the channel)

Por último, uma vez que o código acabou ficando  com um tamanho razoável, decidi armazenar as constantes na memória flash (programa), em vez de SRAM. Para isso, usei a palavra-chave PROGMEM que é um modificador de variável. Por exemplo, em vez de utilizar:

#define DHTPIN 5, usaremos:

const PROGMEM byte DHTPIN = 5;

O PROGMEN informa ao compilador  para “colocar esta informação na memória flash”, em vez de em SRAM, onde iria normalmente. Você também deve incluir a biblioteca avr/pgmspace.h no arquivo principal do seu código.

Outro bom procedimento para reduzir o uso de SRAM é comentar (ou excluir) todas as linhas de Serial.print () que você usou para a depuração durante o desenvolvimento. Você perceberá que o código usado por exemplo para mostrar o log no monitor serial estará todo “comentado” no código final.

Abaixo você pode encontrará o código completo para o ArduFarmBot. Não se esqueça de alterar os dados no file credentials.h com seu Thinkspeak Channel Id e chave de escrita. Também no communication.ino, entre com seu nome de usuário e senha para conectar o ESP 8266 na Internet.

Abaixo testes do ArduFarmBot e instalação definitiva:fvc1gl0itchfzep-large

Preparando o reservatório de água:

reservatorio

Conectando a bomba dágua: bomba

Germinação (aproximadamente 45 dias):

germinacao 

Seleção melhores mudas e transplante :

transplante

 

2.9: Conclusão

Picture of Conclusion

Isso é tudo, pessoal! ……Por agora!!

Em breve publicaremos a terceira e última parte do nosso projeto que espero seja uma boa receita de pasta ao sugo, mas com molho de tomate orgânico. 😉 !

IMG_0014.JPG

A propósito, na foto abaixo vocês podem ver os primeiros suspiros de vida na plantação do Mauricio e seus tomates alguns meses depois

img_0515

Os arquivos e documentos podem ser encontrados aqui : ArduFarmBot GitHub

Como sempre, espero que este projeto ajude outras pessoas a encontrar seu caminho no apaixonante mundo da eletrônica, do IoT e dos robôs!

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebookcapa-ardufarmbot

Este projeto também está disponível em formato Kindle:

Saludos desde el sur del mundo! 😉

Um abraço e até o próximo post!

Obrigado

Marcelo e Maurício

ArduFarmBot: Controlando um tomateiro com a ajuda de um Arduino e Internet das coisas (IoT)

O objectivo deste projecto criado a 4 mãos juntamente com meu amigo Maurício Pinto, é o de se conseguir uma plantação de tomates totalmente automatizada, onde também se pode monitorar pela internet informações tais como temperatura, humidade do solo, luminosidade, etc.

(A versão em inglês desse post pode ser encontrada aqui em meu blog: ArduFamBot ou em sua versão original no site do Instructables.com)

img_0508Neste tutorial desenvolveremos um controlador eletrônico (o “ArduFarmBot”) que a partir da captura de dados provenientes de uma plantação de tomates (vulgo tomateiro), tais como temperatura, umidade relativa do ar, luminosidade e umidade do solo, decidirá autonomamente a quantidade certa (e quando) o plantio deve receber calor e irrigação (água + nutrientes). Além disso, o ArduFarmBot permitirá a intervenção manual de um operador (tanto de forma local quanto remotamente via Internet), a fim de controlar o acionamento de uma bomba de água e de uma lâmpada elétrica (esta última para ser usada na geração de calor para as plantas).

Em suma, o ArduFarmBot receberá como entrada:

  • Sensores (dados analógicos):
    • Temperatura
    • Umidade do ar
    • Luminosidade
    • Umidade do solo

E fornecerá como saídas:

  • Atuadores:
    • Relé para controle de bomba
    • Relé para controlo da lâmpada
  • Sinalização (dados digitais):
    • Visual e sonoro para indicação de status / erro
    • Visual de Estado da bomba
    • Visual para o estado da lâmpada
  • Display dos dados
    • Todos os dados analógicos e digitais devem estar disponíveis para avaliação instantânea
  • Armazenamento de dados
    • Dados históricos deve ser de armazenamento remotamente na internet e opcionalmente também localmente (via cartão de memória).

O diagrama de blocos abaixo mostra os principais componentes do projeto.

NewBlockDiagram

O vídeo abaixo descreve os principais elementos utilizados no primeiro protótipo de laboratório usado para testes:

Neste segundo vídeo, mostramos como os comandos funcionarão em modo local e remotamente via Web page:

PARTE 1 – ESTAÇÃO LOCAL

O projeto será dividido em 3 partes:

  1. Estação local
  2. Estação Remota ( ESP8266, Thingspeak, criação da página web em HTML/CSS/JS, etc)
  3. Uso e Acompanhamento do ArduFarmBot em uma plantação real. 

Aqui nesta primeira parte, vamos explorar a estação local, desenvolvendo o HW e o SW para trabalhar com os sensores, atuadores, aprendendo como exibir dados, etc.

Abaixo o diagrama em blocos simplificado da versão 1 da estação local:

LocalStationBlockDiagram

1.1: Lista de Materiais

fullComponents

Os principais componentes para o ArduFarmBot são (valores em USD):

Itens gerais (cerca de $ 37,00):

Opção 1 com Nano Shield (em torno de US $ 15,00):

Opção 2  sem o Shield para o  Nano- (cerca de US $ 4,00):

1.2: Instalação, programação e teste dos sensores

Sensor de Temperatura e Humidade: DHT 22 (or DHT11)

O primeiro sensor a ser testado e instalado é o DHT 22, um sensor digital de humidade relativa do ar e temperatura. Ele usa internamente um sensor capacitivo de humidade e um termistor para medir o ar circundante, gerando um sinal digital em sua saída de dados. 

De acordo com a sua folha de dados(Datasheet), o sensor deve ser alimentado entre 3.3V e 5V (algumas especificações falam em até 6V max). Ele trabalha a partir de -40  a + 80 graus centígraods (algumas especs falam em + 125 ° C) com uma precisão de +/- 0,5 ° C de temperatura e +/-2% de umidade relativa. É importante ter em mente que o seu (“sencing period”) é em média de dois segundo (tempo mínimo entre leituras).

O site da Adafruit fornece uma série de informações sobre ambos, DHT22 e seu irmão DHT11. Para mais detalhes, visite a página:  Tutorial DHT22 / 11 .

O DHT22 tem 4 pinos (de frente para o sensor, o pino 1 é o mais esquerda):

  1. VCC (3 a 5V)
  2. saída de dados
  3. Não conectado
  4. GND (Terra)

Uma vez que normalmente você usará o sensor em distâncias inferiores a 20m, um resistor de 10K deve ser conectado entre os pinos de dados e o VCC. O pino de saída deve ser conectado ao pino D5 do Arduino (veja o diagrama acima).

Uma vez que o sensor é instalado fisicamente no Arduino, baixe a biblioteca DHT a partir do repositório de programas:  Adafruit github e a instale junto as outras  bibliotecas de seu IDE (ambiente de desenvolvimento de programas do Arduino).

Uma vez que você recarregue o IDE, a biblioteca para o  sensor de DHT deverá aparecer como instalada. Execute o código abaixo para verificar se tudo está funcionando OK:

 
/****************************************************************
 * DHT Sensor - Setup and Test
 * Based on the original code written by ladyada, public domain
 * MJRoBot 21Aug16
 ****************************************************************/
// Include DHT Library
#include 

// Sensor defiitions
#define DHTPIN 5        // DHT data pin connected to Arduino pin 5
#define DHTTYPE DHT22   // DHT 22 (if your sensor is the DHT 11, only change this line by: #define DHTTYPE DHT11) 

// Variables to be used by Sensor
float tempDHT; // on the final program we will use int insteady of float. No need for high precision measurements 
float humDHT;
float hic; // only used here for testing purposes

// Initialize the DHT sensor
DHT dht(DHTPIN, DHTTYPE); 

void setup() 
{
  Serial.begin(9600); 
  Serial.println("DHT 22 Setup & Test");
  dht.begin();
}

void loop() 
{
  // Wait a few seconds between measurements.
  delay(2000);
  
  //Read temperature and humidity values from DHT sensor:
  tempDHT = dht.readTemperature();   
  humDHT = dht.readHumidity();

  // Check if any reads failed and exit early (to try again).
  if (isnan(humDHT) || isnan(tempDHT)) 
  {
    Serial.println("Failed to read from DHT sensor!");
    return;
  }

  // Compute heat index in Celsius (isFahreheit = false)
  float hic = dht.computeHeatIndex(tempDHT, humDHT, false);
  
  // Show measurements at Serial monitor:
  Serial.print("   Temp DHT ==> ");
  Serial.print(tempDHT);
  Serial.print("oC  Hum DHT ==> ");
  Serial.print(humDHT);
  Serial.print("%  Heat index: ");
  Serial.print(hic);
  Serial.println(" oC ");
}

Sensor de Luminosidade (LDR)

Com o DHT funcionando, é hora de  instalar e testar o sensor de luminosidade. Para isso, podemos usar um simples LDR (Resistor variável com luz). Basicamente, o que se deve fazer é criar um divisor de tensão, onde um dos resistores é o LDR e o outro um fixo, sendo o “ponto médio do divisor” conectado a uma entrada analógica do Arduino. Desta forma, variando-se a luz, a resistência LDR variará proporcionalmente e por conseguinte , a tensão no ponto do meio do divisor também vai variar.

Em nosso projeto, por facilidade de instalação, usaremos um módulo LDR barato (KY18) o qual já possui o divisor de tensão integrado. O módulo tem 3 pinos ( “S” para dados; “+” para VCC e “-” para GND). O pino “S” será ligado ao pino analógico Ao do Arduino.  O “+” e “-” pinos devem ser ligadas, respectivamente, a 5 V e GND. Se o consumo de energia é uma preocupação, o “+” pode ser conectado a uma das saídas digitais do Arduino,  que deverá  ser colocada em “HIGH” alguns milésimos de segundo antes de se ler a tensão no pino A0, retornando para “LOW” depois disso.

A função getLumen (LDR_PIN) lerá algumas vezes a saída do sensor (poderia ser de 3, 10 ou mais, você decide o que é melhor para o seu caso) calculando a média dessas leituras (isso para diminuir o risco que uma uma única leitura seja na verdade um ruído). Além disso, uma vez que a saída do conversor analógico digital (ADC) interno do Arduino será um número de 0 a 1023, deveremos “mapear” esses valores, a fim de obter os seguintes resultados como porcentagens:

  • “Dark Full” ==> saída ADC: 1023 ==> 0%
  • “Full Light” ==>  saída ADC: 0 ==> 100%

Abaixo a função em C++:

int getLumen(int anaPin)
{
  int anaValue = 0;
  for(int i = 0; i < 10; i++) // read sensor 10X ang get the average   {     anaValue += analogRead(anaPin);        delay(50);   }      anaValue = anaValue/10; //Light under 300; Dark over 800   anaValue = map(anaValue, 1023, 0, 0, 100); //LDRDark:0  ==> light 100%

  return anaValue;  
}

Sensor de Humidade de solo (Higrômetro)

Um sensor para testar a umidade do solo (ou higrômetro) é muito simples. Ele possui o mesmo princípio de funcionamento que o sensor de luminosidade. Um divisor de tensão será usado como ponto de entrada de uma das portas analógicas do Arduino, mas em vez de um resistor que varie com luz, usaremos um que varie com a humidade do solo. O circuito básico é muito simples e pode ser visto abaixo.

Picture of Installing, programing and testing the sensors

Infelizmente, a realidade é um pouco mais complexa do que isso (mas não muito). Um simples sensor como o descrito funcionaría bem, mas não por muito tempo. O problema é que ter uma corrente constante fluindo através dos eletrodos em uma única direção gerará corrosão sobre eles, devido ao efeito de eletrólise. Uma maneira de resolver isso é conectar-se os eletrodos não diretamente ao VCC e terra, mas à portas digitais do Arduino (por exemplo VCC a D7 e GND a D6 como mostrado acima). Ao fazer isso, em primeiro lugar o sensor seria “energizado” apenas quando a leitura realmente ocorresse e a direção da corrente sobre as sondas poderia ser alternada, eliminando o efeito da eletrólise.

Abaixo um código de teste simples,  baseada no post “How to: Soil Moisture Measurement?” :

/**************************************************** 
Soil Moisture Sensor Test
****************************************************/
#define SOIL_MOIST_PIN 1 // used for Soil Moisture Sensor Input
#define SMS_VCC 7
#define SMS_GND 6

int soilMoist; // analogical value obtained from sensor

void setup () 
{
  Serial.begin(9600);
  pinMode(SMS_VCC,OUTPUT);
  pinMode(SMS_GND,OUTPUT);

}

void loop (void) 
{
  soilMoist = getSoilMoisture(); 
  Serial.print("Soil Moisture: ")
  Serial.print(soilMoist)
  Serial.println(" %")
}

/***************************************************
* Capture soil Moisture data
****************************************************/
int getSoilMoisture()
{
  int anaValue;
  
  digitalWrite(SMS_VCC,LOW);   // drive a current through the divider in one direction
  digitalWrite(SMS_GND,HIGH);
  delay(1000);   // wait a moment for capacitance effects to settle
  anaValue=analogRead(SOIL_MOIST_PIN);
  
  digitalWrite(SMS_VCC,HIGH);   // reverse the current
  digitalWrite(SMS_GND,LOW);
  delay(1000);   // give as much time in 'reverse' as in 'forward'
  digitalWrite(SMS_VCC,LOW);   // stop the current
  
  anaValue = map(anaValue, 1023, 0, 0, 100);
  return anaValue;
}


Nos testes preliminares para o desenvolvimento do SW, usaremos um  potenciômetro de 10K ohms entre + 5V e GND, simulando a saída analógica do sensor de umidade. Por agora isso será suficiente, uma vez que iremos discutir este sensor mais profundamente no capítulo 1.5.

Agora que todas as rotinas para os sensores estão prontas e testadas individualmente, criaremos uma função específica onde todos os sensores serão lidos ao mesmo tempo:

void readSensors(void)
{
  tempDHT = dht.readTemperature();   //Read temperature and humidity values from DHT sensor:
  humDHT = dht.readHumidity();
  lumen = getLumen(LDR_PIN);
  soilMoist = getSoilMoist();
}

Procure fazer alguns testes com os sensores, como cobrir o LDR com um pano ou a mão, colocar o dedo diretamente no DHT, etc. , e veja o  range de variação dos dados  Use o Serial Monitor como saída para leitura dos dados. 

Sensors Output Serial

Abaixo o código completo:

1.3: Adicionando um Display LCD para monitoramento local

É claro que nem sempre será possível usar o monitor serial para analisar a leitura de nossos sensores. Assim, para monitorização local, será adicionado um LCD ao projecto. A escolha ficou com um módulo LCD de 4 linhas de 20 caracteres cada, que permite configuração de contraste através de um potenciômetro instalado em sua parte traseira e comunicação I2C.

fzxwqolirxthf88-smallPara comunicação serial do tipo I2C com o Arduino, o LCD possui 4 pinos:

  • GND
  • VCC
  • SDA
  • SCL

O pino SDA será ligado em nosso caso a entrada A4 do Arduino e o pino SCL ao  A5, como mostrado no diagrama acima.

Uma vez que os 4 fios são conectados, a próxima coisa a fazer é baixar e instalar a biblioteca I2C para o seu display LCD, a qual pode ser usada tanto  para um display do tipo 20 x 4 quanto para o  16 x 2 (2 linhas de 16 caracteres):

https://github.com/fdebrabander/Arduino-LiquidCrys…

Execute em seu Arduino o programa exemplo “Hello World” que vem incluído com a biblioteca, mudando o  set-up padrão (16 × 2) para o nosso 20 x 4. O  endereço default “0x27” funcionou bem em meu caso:

#include  
#include 

// Set the LCD address to 0x27 for a 20 chars and 4 line display
LiquidCrystal_I2C lcd(0x27, 20, 4);

void setup()
{
	// initialize the LCD
	lcd.begin();

	// Turn on the blacklight and print a message.
	lcd.backlight();
	lcd.print("Hello, world!");
}

void loop()
{
	// Do nothing here...
}

Nota: Se você não tem certeza sobre o seu endereço I2C de seu LCD, uma simples varredura em seu HW irá mostrar se existem dispositivos I2C conectados, se estão funcionando corretamente e em qual seu endereço. O código para fazer o scanner pode ser encontrado aqui: http://playground.arduino.cc/Main/I2cScanner

57ba02f2deafa4839d00151e.jpeg

Como exemplo, em meu caso ao executar o programa obtive no Monitor Serial :

   Scanning..

   I2C device found at address 0x27 !

  done

Vamos incorporar as funções, definições específicas, etc. referentes ao display em nosso último código, para que possamos assim ver as leituras de sensores em LCD:

1.4: Atuadores e botões para controle local

Atuadores

Até agora, pudemos obter os dados gerados pelos sensores e exibi-los no monitor serial e no LCD. É hora de fazer alguma coisa com esses dados. Vamos pensar sobre os atuadores!

Como discutido na introdução, nosso objetivo final aqui é cuidar de uma plantação de tomate. Com os dados fornecidos por sensores, saberemos a temperatura e umidade do ar, a luminosidade e o mais importante quão “seco” se encontra o solo onde está a plantação. Com esses dados em mãos, o nosso programa deverá decidir se seria necessário irrigar a plantação, ligando uma bomba elétrica para bombear água  ou  uma lâmpada elétrica para fornecer o calor necessário para a cultura. Para isso, usaremos pequenos Módulos de %V para relés para a ativação da bomba e lâmpada. O diagrama do Módulo de Relé pode ser visto abaixo.

Picture of Actuators and buttons for local control

Módulos de relés podem apresentar várias nomenclaturas para seus pinos de entrada, tais como: “G”, “V”, “S” ou “S”, “-“, “+” ou “In”, “Vcc”, ” GND “, etc.

Olhando para o diagrama, dependendo do seu módulo de relé, é necessário conectar:

  • Arduino 5V ==> “V” ou “+” ou “Vcc”
  • Arduino GND ==> “G” ou “-” ou “GND”
  • Arduino OUT ==> “S” ou “In” (em nosso caso o “Arduino OUT” deve ser D10 para Bomba e D8 para Lamp)

Normalmente você vai encontrará como saída, 3 pinos: “NA” (ou NO), “Ref”, e “NF ” (ou NC), que são:” Normal Open ou Aberto”, Referência e” Normal Closed ou Fechado “. Nós usaremos o par: NA e Ref (centro). No diagrama acima, “NA” é o terminal para conectar-se ao positivo da fonte de alimentação (12 ou 9VDC para Bomba e 127 ou 220VAC para a lâmpada, conforme seu caso). O “Ref” será conectado à bomba ou da lâmpada, como mostrado na figura acima. Para saber mais sobre relés, visite: “ Controlling Power with Arduino “.

Juntamente com os relés, opcionalmente 2 LEDs podem ser utilizados para mostrar se os relés estão ligados ou desligados:

  • LED vermelho: Bomba
  • LED verde: Lâmpada

Para o teste é ótimo se ter os LEDs instalados em seu BreadBord, mas para o projeto final você pode eliminar-los, reduzindo a carga na saída do pino do Arduino (mesmo porque os módulos de relés em geral possuem um pequeno LED vermelho incorporado). Trabalhando juntos, eles “puxarão” uma quantidade razoável de corrente do Arduino (você poderá perceber uma redução no brilho no LCD). Outra alternativa é utilizar saídas digitais diferentes para os LEDs e relés, o que claro reduz a disponibilidade de pinos para outros usos. 

 57bb216d4936d4e847000021.jpeg

Botões

Com base nas leituras dos sensores, um operador poderia decidir controlar manualmente a bomba e / ou lâmpada. Para isso, dois botões do tipo “push-button” serão incorporados ao projeto. Eles vão trabalhar em um modo de “alternância” (“Toggle”): Se um atuador está “ON”, pressionando-se o botão, ele passa a “OFF” e vice-versa. A lógica do botão será “normalmente fechada”, o que significa que a entrada do Arduino estará constantemente em “HIGH” (será usado um resistor de pull-up interno do Arduino). Pressionando-se o botão, uma estado “LOW” será aplicada ao pino do Arduino.

Da mesma maneira que fizemos com os sensores, a toda vez que executemos o loop () , uma função readLocalCmd () será executada. Esta função lerá o estado dos botões, atualizando o status de variáveis correspondentes aos atuadores (pumpSatus e lampStatus). Note que a função debounce(pin) é chamado ao invés de um digitalRead (PIN). Isso é para evitar falsas leituras do botão. Se você quiser saber mais sobre debouncing, consulte:  Debouncing Tutorial.

/****************************************************************
* Read local commands (Pump and Lamp buttons are normally "HIGH"):
****************************************************************/
void readLocalCmd() 
{  
  int digiValue = debounce(PUMP_ON);
  if (!digiValue) 
  {
    pumpStatus = !pumpStatus;
    showDataLCD();
    aplyCmd();
  }

  digiValue = debounce(LAMP_ON);
  if (!digiValue) 
  {
    lampStatus = !lampStatus;
    showDataLCD();
    aplyCmd();
  }
}

No caso em que um botão é pressionado, uma outra função será chamada: aplyCmd () . E como o nome diz, ela aplicará o comando correspondente, ligando ou desligando os atuadores:

/***************************************************
* Receive Commands and act on actuators
****************************************************/
void aplyCmd()
{
    if (pumpStatus == 1) digitalWrite(PUMP_PIN, HIGH);
    if (pumpStatus == 0) digitalWrite(PUMP_PIN, LOW);
  
    if (lampStatus == 1) digitalWrite(LAMP_PIN, HIGH);
    if (lampStatus == 0) digitalWrite(LAMP_PIN, LOW);
}

Considerações sobre o “Timming” do programa

Quando pensamos sobre as tarefas a serem executadas, podemos agrupar-las em: 

  1. Ler sensores
  2. ler botões (comando local)
  3. Atuar sobre Bomba / Lâmpada
  4. Exibir todos os dados

Percebemos que o “timming” de quando deveríamos realizar tais tarefas não são necessariamente os mesmos. Por exemplo, para ler os dados de temperatura e de umidade do DHT 22, temos de esperar pelo menos 2 segundos entre medidas, mas diferenças em minutos não farão grandes diferenças. Para o sensor de umidade do solo, quanto menos medições fizermos, melhor (evita corrosão das pontas de prova devido a electrolise) e por último mas não menos importante, a luz do dia não vai variar instantaneamente. Mas quando pensamos sobre os atuadores, logo que se pressione um botão, gostaríamos (e possivelmente precisaríamos) de uma reacção rápida.

Assim, a última instrução antes do final do setup () será a inicialização de um temporizador principal usando a função” millis () ” em vez de espalhar um monte de delays()’s pelo código:

startTiming = millis ();  // Iniciar o “relógio do programa”

Durante o loop(), a primeira instrução será a de incrementar a variável startTiming com uma temporização real.

elapsedTime = millis () – startTiming ;

Depois disso, leremos o estado dos botões usando a função readLocalCmd ( ). Esta leitura irá acontecer toda a vez que o programa execute o loop ().

readLocalCmd (); // Ler estado dos botões locais

Em relação aos sensores, fazemos a leitura a cada 5 segundos, por exemplo, e não em cada loop:

if (elapsedTime > (5000))

{

   readSensors();

   printData();

   startTiming = millis();

}

Abaixo o código completo para testar a nossa “Estação local”:

1.5: Aprofundando com o sensor de humidade do solo

Você poderá pular esta etapa se quiser, mas acho que seria interessante aprofundar-nos um pouco mais no estudo deste simples, mas crítico componente para o projeto. Como brevemente explicado anteriormente, o “Soil Moisture Sensor”, ou higrômetro, é um simples “divisor de tensão resistivo”.

Sabendo disso, podemos construir um sensor muito simples usando duas sondas metálicas como pregos galvanizados, pinos ou parafusos. Abaixo você poderá ver o que criei utilizando-se de materiais simples: dois parafusos ligados a dois fios (preto / vermelho). Depois em um segundo sensor, foram adicionados um terceiro fio e um resistor.

Picture of Playing with a real Soil Moisture Sensor

Conforme descrito no item 1.2, “R1” é a “resistência do solo” (não é o melhor termo científico, mas está OK). Utilizando-se de 3 amostras de solo em diferentes estados de umidade, podemos medir o valor esse valor R1 utilisando-se de um multímetro comum e corrente, como mostrado na foto abaixo:

SMS setup

  • Seco:         R1 =    >20 Kohm (aprox)
  • Húmido:  R1 =    4K para 6Kohm (aprox.) 
  • Molhado: R1 =    >1kohm (aprox)

    R2 é a resistência física usada para completar o Divisor de Tensão (Vamos começar com um potenciômetro 10K para set-up). Calculando o Vin na porta A1 do Arduino , proporcionalmente à VCC, chegaríamos a equação:

    Vin = R2 / (R1 + R2) * VCC ou Vin / VCC = 10K / (R1 + 10K) * 100 [%]

    Usando os valores reais medidos com o multímetro, podemos antecipar que os resultados devem ser:

  • Seco:         10K / 30K *100 ==>   <30%

  • Húmido:  10K / 15K * 100 ==>  ~ 67%
  • Molhado: 10K / 11K * 100 ==>  > 90%

SMS measuriments

Fazendo as conexões com o Arduino e executando o código desenvolvido até agora, teremos como resultado:

  • Seco:       13%
  • Húmido: 62%
  • Molhado: 85%

Obviamente devido a que mudei a posição dos sensores na terra R1 mudará um pouco, além de que o VCC também não é exatamente 5V, os valores práticos são um pouco diversos do teórico. Mas como o que realmente importa é o intervalo de variação e não o valor absoluto, o resultado foi muito satisfatório.

O sensor será utilizado em 3 estados distintos:

  • Molhado (Wet): Mais de 60% (não ligaremos a bomba de maneira nenhuma)
  • Húmido: Entre 40 e 60% (Onde queremos trabalhar)  e
  • Seco: Abaixo de 30% (precisaremos ligar a bomba para aumentar a umidade)

Como você pode observar, utilizando-se R2 como 10K ohm funcionou bem, o que nos leva a substituir o potenciômetro por um resistor fixo em nosso “Soil Moisture Sensor”.

Outra coisa que se percebe quando testamos os sensores, é que ao fazer as medições vai se acumulando um pequeno erro nas leituras. Isto porque o sensor também se comportará como um “capacitor”.  Ao se “energizar” o sensor para uma única captura de dados  precisamos esperar um tempo razoável, mesmo depois de cortar o fornecimento de energia para que o mesmo possa se “descarregar “. Revertendo a corrente ajudará, mas não é suficiente.

SoilMoisture Sensor Readings Comp

Os gráficos acima mostram 2 conjuntos de medições:

  1. Linha azul : Um ciclo de 10 medições a cada 1 segundo entre amostras e com 1 minuto entre os ciclos
  2. Linha Laranja : Um ciclo de 10 medições a cada 1 segundo entre amostras e com 5 minutos entre ciclos

Com intervalos de 1 segundo, cada nova amostra será aumentado significativamente. Esperar 1 minuto após desligar a energia irá diminuir o “efeito de tensão de armazenamento”, mas não vai eliminá-la por completo sendo que um valor residual será adicionado à próxima medição. O aumento do intervalo de ciclos para 5 minutos, por exemplo, praticamente eliminará o erro.

Com base nos resultados acima, o código final não deve tomar amostras com uma frequência de menos do que 10 min.

O vídeo abaixo, mostra os testes efetuados com o sensor:

1.6: Compatibilizando as operações automática e manual 

 ardufarmbot_local_station_eletr_diagram

Como pudemos ver na última etapa, precisaremos esperar tempos maiores entre as medições do sensor de umidade, por exemplo: 10minutos. Isto  está Ok para as nossas necessidades automáticas, mas para a operação manual não vamos querer “esperar” dezenas de minutos olhando para o ArduFarmBot para saber se um valor medido pelo sensor mudou. Para solucionar o problema, introduziremos um terceiro botão de controle em nosso projeto, o qual será usado para exibir os dados reais e instantâneos dos sensores a qualquer momento, independente das programações de leituras automáticas.

Picture of Changing the code to accommodate real measurements and final HW

Para o Push-Button, usaremos o pino digital D17  (o mesmo que está compartilhado com a entrada analógica A3). Introduziremos também um “LED de aviso” (o amarelo na foto) conectada ao pino 13. Este LED acenderá toda a vez em que os sensores estão sendo atualizados. Abaixo a nova função readLocalCmd() :

/****************************************************************
* Read local commands (Pump and Lamp buttons are normally "HIGH"):
****************************************************************/
void readLocalCmd() 
{  
  int digiValue = debounce(PUMP_ON);
  if (!digiValue) 
  {
    pumpStatus = !pumpStatus;
    showDataLCD();
    aplyCmd();
  }

  digiValue = debounce(LAMP_ON);
  if (!digiValue) 
  {
    lampStatus = !lampStatus;
    showDataLCD();
    aplyCmd();
  }

  digiValue = debounce(SENSORS_READ);
  if (!digiValue) 
  {
    digitalWrite(YELLOW_LED, HIGH); 
    lcd.setCursor (0,0);
    lcd.print("< Updating Sensors >");
    readSensors();
    digitalWrite(YELLOW_LED, LOW); 
  }
}

Outro ponto a considerar, é a introdução de um segundo “Soil Moisture Sensor” (higrômetro). Em nosso projeto final utilizaremos 2 sensores em pontos diferentes da plantação.  Nós usaremos a média das leitura para decidir quando se deve ligar a bomba, por exemplo.

O “VCC e GND” dos sensores serão o mesmo (D7 e D6 respectivamente) e usaremos a entrada A2 do Arduino para o segundo sensor. Se a area da plantação é pequena não justificando 2 sensores, somente o valor lido em A1 será considerado pelo SW. (Isto deve ser informado na variável correspondente na area de set-up do SW). 

// to be used by SM Sensor
int soilMoist;
int soilMoistAlert = 0;
int DRY_SOIL = 30;
int WET_SOIL = 60;
int numSM = 1; // “numSM” defines number of moisture sensors that are connected
int numSamplesSMS = 1; // “numSamplesSMS” defines number of samples of each reading cycle

O número de amostras de cada ciclo também será definido por uma variável específica: “numSamplesSMS”. Em princípio apenas uma leitura é suficiente, levando-se em consideração que as leituras que fazemos em um curto espaço de tempo introduzirão erros devido ao efeito de capacitância. Se você começar a ver erros na leitura, talvez sejam necessárias tomar amostras extras.

Abaixo a nova função para o  “Soil Moisture Sensor”:

/***************************************************
* Capture soil Moisture data
****************************************************/
int getSoilMoist()
{
  int i = 0;
  int anaValue1 = 0;
  int anaValue2 = 0;
  for(i = 0; i < numSamplesSMS; i++) // // "numSamplesSMS" defines number of samples of each reading cycle   {     digitalWrite(SMS_VCC,LOW);   // drive a current through the divider in one direction     digitalWrite(SMS_GND,HIGH);     delay(500);   // wait a moment for capacitance effects to settle     anaValue1 += analogRead(SOIL_MOIST_1_PIN);     delay(500);   // wait a moment for ADC settle-up     anaValue2 += analogRead(SOIL_MOIST_2_PIN);          digitalWrite(SMS_VCC,HIGH);   // reverse the current     digitalWrite(SMS_GND,LOW);     delay(1000);   // give as much time in 'reverse' as in 'forward'     digitalWrite(SMS_VCC,LOW);   // stop the current     //delay (3000);   }      anaValue1 = anaValue1/(i);   anaValue2 = anaValue2/(i);   if (numSM == 2) anaValue1 = (anaValue1+anaValue2)/2; // "numSM" variable, defines number of moisture sensors that are connected      anaValue1 = map(anaValue1, 1015, 3, 0, 100); //1015:0 (en el air) ==> 003:100% (poniendo un "short circuit) 
  Serial.println(anaValue1);
  return anaValue1;
}

O diagrama abaixo mostra as conexões completas para a estação local de controle

ardufarmbot_local_pin_diagram

1.7: It’s show time!

Picture of It's show time!

Neste ponto, já temos todo o HW no lugar e quase todos os elementos de SW desenvolvidos. O que falta agora é a “inteligência”,  que permitirá ao nosso sistema realmente executar a tarefa de irrigar a plantação automaticamente! Para isto, precisaremos adicionar alguns “neurônios” ao nosso cérebro!

Como discutido anteriormente, definiremos o intervalo inicial onde os sensores irão trabalhar. Esses valores deverão ser alterados mais tarde a partir de resultados de testes reias na plantação:

Umidade do solo:

  • Molhado (Wet): Mais de 60% (não ligaremos a bomba de maneira nenhuma)
  • Húmido: Entre 40 e 60% (Onde queremos trabalhar)  e
  • Seco: Abaixo de 30% (precisaremos ligar a bomba para aumentar a umidade)

Temperatura:

  • Frio: Abaixo de 15oC (Ligar Luz / Calor *)
  • Confortável: entre 20 oC e 25 oC
  • Calor: Mais que 25 ° C (não vire-a luz / calor)

Leve:

  • Escuro (noite): Abaixo de 40% (não ligar bomba)
  • Luz (dia): Mais de 40%

(*) Você poderá opcionalmente testar aqui as novas luzes LED para vegetais hidropônicos . Estas lâmpadas de LED podem ser utilizadas tanto para ajudar o crescimento mais rápido devido a  luzes de frequências especiais como também para fornecer calor em caso de baixa temperatura.

Você deverá ter em mente que cada tipo de semente possui uma faixa ideal de temperatura na qual ela crescerá mais rápidamente. Por exemplo, para os Tomates o tempo mínimo para as sementes germinarem será de 6 dias em temperaturas entre 20 e 25 oC, aumentando para temperaturas superiores ou inferiores a esta faixa:

  • Temperatura: graus (oC):             10    15   20  25  30  35
  • Tempo para germinação (dias):  43   14    8     6    6   9

Você pode verificar mais informações sobre esta relação (temp / dias de germinação) aqui: O efeito da temperatura do solo na germinação de sementes

Tendo em mãos estas 4 leitura (temperatura, umidade, Soil Moisture and luz), podemos criar uma matriz que definirá como queremos que os nossos tomates crescem:

Então, tomemos nossas variáveis e definamos algumas novas valores:

Para ser utilizado pelo sensor DHT:

  • int tempDHT;
  • int HOT_TEMP = 25;
  • int COLD_TEMP = 15;

Para ser usado por sensor LDR:

  • lumen int;
  • int DARK_LIGHT = 40;

Para ser usado pelo higrômetro:

  • int soilMoist;
  • int DRY_SOIL = 40;
  • int WET_SOIL = 60;

Com base nas definições acima, pensemos sobre algumas premissas-chave:

  1. Se é durante o dia (lumen  > DARK_LIGHT) e o solo está SECO (soilMoist < DRY_SOIL) ==>  BOMBA = ON 
  2. Se é durante a noite (lumen < DARK_LIGHT) e o solo está SECO (soilMoist < DRY_SOIL) ==>  BOMBA = OFF  (Tomates não gostam de receber água durante a noite)
  3. Se estiver frio (tempDHT <COLD_TEMP)  ==> LAMPADA = ON
  4. Se estiver frio (tempDHT <COLD_TEMP) e solo muito húmido (soilMoist > WET_SOIL) ==> LAMP = OFF (para proteger a raiz)

Nesta primeira parte do projeto, vamos mantê-lo simples e não exploraremos todas as combinações possíveis e o papel da humidade do ar na equação. Explorar uma combinação mais complexa de sensores na 3º parte deste projeto, quando aplicaremos o ArduFarmBot em uma plantação real.

O Código :

Criaremos uma nova função, que baseado na leitura dos sensores, lidará automaticamente com os atuadores (ligar / desligar a bomba e lâmpada): AutoControlPlantation (). Esta função mostrada abaixo, será chamada em cada ciclo de leituras dos sensores:

void loop() 
{
  // Start timer for measurements
  elapsedTime = millis()-startTiming; 

  readLocalCmd(); //Read local button status
  showDataLCD();
  
  if (elapsedTime > (sampleTimingSeconds*1000)) 
  {
    readSensors();
    autoControlPlantation();
    startTiming = millis();
  }
}

A função terá 2 tarefas principais:

  • Controle de bomba
  • Controle da lâmpada

O segmento de controle da bomba utilizará uma nova variável: ” soilMoistAlert “.

//--------------------------------- PUMP ------//
  if (soilMoist < DRY_SOIL && lumen > DARK_LIGHT) 
  {
    if (soilMoistAlert == HIGH)
    {
      soilMoistAlert = LOW; 
      turnPumpOn();
    }
    else soilMoistAlert = HIGH;
  }
  else soilMoistAlert = LOW;

Esta variável será utilizada para evitar um “falso verdadeiro”. Então por exemplo, se tivermos um verdadeiro no resultado do teste: soilMoist <DRY_SOIL e que não seja durante a noite (lumen> DARK_LIGHT), não ligaremos imediatamente na bomba, mas ao invés disso, esperaremos o próximo ciclo verificando se o ” solo está muito seco “. Se o resultado for um “sim” (obtendo um “verdadeiro” como resposta duas vezes), somente aí a função turnPumpOn() será chamada:

/***************************************************
* TurnPumOn 
****************************************************/
void turnPumpOn()
{
  digitalWrite(PUMP_PIN, HIGH);
  pumpStatus = 1;
  showDataLCD();
  delay (timePumpOn*1000);
  digitalWrite(PUMP_PIN, LOW);
  pumpStatus = 0;
  showDataLCD();
}

A bomba deverá permanecer ligada por um determinado período de tempo, definido pela variável: ” timePumpOn” em segundos.

Note que também mudaremos a função que exibe dados no LCD criando um 3o. estado:

  • “0”: Bomba OFF (pumpStatus = 0; e soilMoistAlert = 0;)
  • “X”: Bomba em alerta (pumpStatus = 0; e soilMoistAlert = 1;)
  • “1”: Bomba ON (pumpStatus = 1; e soilMoistAlert = 0;)
  lcd.print("Pump: ");
  if (soilMoistAlert  == 1) lcd.print ("X");
  else lcd.print(pumpStatus);

O mesmo princípio será aplicado ao controle da lâmpada, onde uma “baixa temperatura” será o “trigger” para acionar a lâmpada desde que o solo não esteja muito molhado”. Abaixoa função completa: autoControlPlantation ():

/***************************************************
* Automatically Control the Plantation based on sensors reading
****************************************************/
void autoControlPlantation()
{ 
//--------------------------------- PUMP ------//
  if (soilMoist < DRY_SOIL && lumen > DARK_LIGHT) 
  {
    if (soilMoistAlert == HIGH)
    {
      soilMoistAlert = LOW; 
      turnPumpOn();
    }
    else soilMoistAlert = HIGH;
  }
  else soilMoistAlert = LOW;

//--------------------------------- HEAT ------//
  if (tempDHT < COLD_TEMP && soilMoist < WET_SOIL) 
  {
    if (tempLowAlert == HIGH)
    {
      tempLowAlert = LOW; 
      digitalWrite(LAMP_PIN, HIGH);
      lampStatus = 1;
    }
    else tempLowAlert = HIGH;
  }
  else 
  {
    tempLowAlert = LOW;
    digitalWrite(LAMP_PIN, LOW);
    lampStatus = 0; 
  }
}

Neste ponto, o ArduFarmBot esta totalmente funcional em termos de HW e SW e já poderia ser instalado em uma plantação para testes. O código completo pode ser encontrado abaixo:

1.8: Small Form Factor (colocando tudo na “caixinha”)

FullSizeRender 20.jpg

Uma vez que possuímos nosso protótipo funcional, vamos montar-lo de uma forma melhor, usando-se um “Shield” para o Arduino Nano (no caso, um fabricado pela  “Funduino”) e uma caixa plástica, o que ajudará nos testes externos. A grande vantagem de se usar um shield para o Nano,  é que cada componente fica melhor montado reduzindo os maus contatos e ruídos, além da facilidade de se ter todos os componentes principais reunidos em uma pequena caixa de plástico.

IMG_4781.JPG

Se você estiver usando o DHT isolado (sem ser em um módulo), você adicione um resistor de 10K entre VCC e Signal. Se você estiver usando um módulo sensor, a resistência já se encontra incluído. Para este novo teste usarei o módulo DHT11 que tenho disponível. O resultado para o nosso propósito é o mesmo (apenas não se esqueça de alterar a linha no código para definir o sensor apropriado que você irá usar: #define DHTTYPE DHT11).

57c1b7c767400c7912000c37.jpeg

Faça 4 furos na caixa de plástico para a instalação do LCD (eu o deixei dentro da caixa).

Faça 57c1b7a045bcebf5270006ab.jpegfuros laterais na caixa para que que você possa acessar os sensores, ter acesso ao Nano (seja para a fonte de alimentação externa ou atualizações de SW) e ligar os atuadores (Bomba / Lampada) nas saídas de relés

Picture of Changing to a Small Form Factor

Note que para os botões de controle, usei aqui um teclado do tipo membrana de 4 teclas “1 × 4 Key Matrix Membrane Switch”. 

IMG_4788.JPG

Você pode decidir a melhor maneira de fixar os componentes na caixa. Eu pessoalmente, gosto desse fixador removível da 3M para facilitar a instalação (veja as foto).

1.9: Testes funcionais em laboratório

Uma vez que tudo está fixada em seu devido lugar e o SW carregado, vamos fazer alguns testes funcionais que simulem diferentes condições de sensores, a fim de se verificar que tudo esteja bem ligado:

Com luz  e temperatura normais, introduzir o higrômetro em um copo com amostra de solo húmido. Observe a foto 1 (Temp é 22oC;. Soil Hum é de 85% e luz é de 80%). Nada deve acontecer. Pump e Lamp devem ser OFF ( “0”).

Picture of Funcional tests

Mantendo-se a mesma luz e temperatura, passemos a sonda para o copo com amostra de solo seco. Na foto 2, você pode observar que a bomba foi ligada (Primeiro aparecerá o “X” e depois o “1” durante algum tempo, tal como definido no programa).

57c1e6574fbadef414000d35.jpeg

Agora, como mostrado na foto 3, o LDR foi coberto com um pano preto e a luminosidade decresceu para 19%. Neste caso, apesar do fato de que o solo esteja seco, a bomba não ligará, porque o ArduFarmBot entenderá que é à noite.

57c1e71ddeafa41a5b0007af.jpeg

Na foto 4, colocaremos gelo no fundo da nossa caixa, junto ao sensor DHT. A temperatura desceu a 12oC e a lâmpada foi ligada.

57c1e801deafa41a5b0007b5.jpeg

E por último, na foto 5 manteremos o gelo, mas mudaremos a sonda novamente para a amostra de solo húmido. Neste caso apesar do fato de estar frio, de acordo com a matriz, a lâmpada permanece desligada.

57c1e8f84fbadef21e00044f.jpeg

1.10: “Test Drive”: Regando um tomateiro com o ArduFarmBot

Picture of

Para os primeiros testes reias, liguei uma bomba elétrica que tinha disponível (no projeto final a bomba será uma pequena bomba DC de 9V). Este primeiro teste é só para ver como o projeto vai funcionar, então não se preocupam com as gotas que você vai ver no vídeo ao lado).

1.11: O ArduFarmBot em acção

Com base em tudo o que nós aprendemos aqui, o passo agora será colocar o ArduFarmBot para controlar sua plantação verdadeira de tomate. Com base nesta experiência real, calibraremos e definiremos melhor os sensores e parâmetros do projeto. As fotos aqui dão uma idéia da sequência da preparação da terra e a introdução das sementes. Essa parte do projeto está a cargo de nosso “Chef” Maurício! Todos os detalhes dos testes reais serão explorados em na 3a. e última parte do projeto.

MauFarmColagem

O filme abaixo mostra o ArduFarmBot em ação:

Agora é só torcer para que tudo de certo e possamos ter uma grande salada no final do projeto!!!! Salut! 😉

1.12: Parte 1 Conclusão

MJRoBot and ArduFarmBotSe você gostou do projeto, não deixe de ver a segunda parte de nosso projeto: ArduFarmBot: Part 2 – “Estação Remota” – IoT, que mostrará a conexão com a internet e sua aplicação na plantação de tomate.

Os arquivos e documentos podem ser encontrados aqui : ArduFarmBot GitHub

Como sempre, espero que este projeto ajude outras pessoas a encontrar seu caminho no apaixonante mundo da eletrônica, do IoT e dos robôs!

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebookcapa-ardufarmbot

Este projeto também está disponível em formato Kindle:

 

Saludos desde el sur del mundo! 😉

Um abraço e até o próximo post!

Obrigado

Marcelo e Maurício