Desenvolvimento do Projeto

Projeto individual


Resultados de aprendizagem:


Você respondeu a estas perguntas?


Desenvolvimento do Projeto


Estou fazendo esta tarefa com o projeto final já pronto e apresentado. Por isso, me parece sem sentido criar um plano imaginário para terminar uma coisa que já está feita. Então decidi mostrar as competências pedidas nesta tarefa na conclusão da minha Fab Academy como um todo.


Planejamento


Que tarefas foram concluídas e que tarefas faltam?


Todas as tarefas para concluir o meu projeto final estão feitas, e eu já apresentei o projeto para o Neil. Agora só faltam a documentação e os exercícios, que eu mostro na planilha abaixo. A planilha original do Excel está aqui.

Planilha dos exercícios


O que vai acontecer e quando?


Para concluir as tarefas acima, propus o cronograma abaixo. A planilha original do Excel está aqui.

Plano de conclusão


O que funcionou?



O que não funcionou?



Que questões precisam ser resolvidas?


Preciso melhorar os aspectos de implantação. Acredito que, se eu integrar uma tomada de 230 V para a bomba, a instalação fica mais simples para o usuário.


O que você aprendeu?



Abaixo documentei o meu processo de aprendizado, desde instalar a IDE PlatformIO no VS Code até melhorar as minhas habilidades de programação em C++.

Diário: datas no ambiente de teste antes da placa final

06/06/2020 07/06/2020 08/06/2020 09/06/2020 10/06/2020 13/06/2020 14/06/2020 15/06/2020 16/06/2020 17/06/2020

Daqui em diante perdi a noção do tempo

Projetando a placa
Fresando a placa
Depurando a placa
Gravando o firmware
Código de rede
Fresando a caixa
Fiação
Diagrama geral do sistema inteiro
Montando o ambiente de teste (estufa)
Hero shot

Diário de progresso do projeto final, de 6 a 17 de junho

6 de junho de 2020

Enquanto escrevo isto, não existem arquivos de design atuais, existe um esboço de código e existe um protótipo numa protoboard!

Por outro lado, existem requisitos para o hardware e para o software:

Requisitos e estágio do software

  1. Simplificar o código criando mais funções;
  2. Tentar separar a parte do HTML para ter um código mais limpo;
  3. Melhorar a aparência/interface do código;
  4. Adicionar as leituras ao HTML;
  5. Adicionar um registro de ocorrências, como sobrecorrente;
  6. Adicionar mais segurança para os equipamentos;
  7. Adicionar uma interface de celular (APP);
  8. Adicionar uma função para acertar a hora atual;
  9. Adicionar uma função para renomear cada relé, para poder relacionar o relé com a área de interesse, ou pelo menos renomear os relés com as áreas reais da fazenda.

Requisitos e situação do design

  1. Precisa ser pelo menos resistente à água;

7 de junho de 2020

Hoje, 7 de junho de 2020, o desenvolvimento está assim.

Montagem inicial com Arduino Uno Eu começo com um sistema que funciona num Uno...

O código que roda nessa montagem está no GitHub da Fabfarm: Autovalve

O Shakeel, um dos nossos voluntários, conseguiu criar uma versão que roda num ESP8266, que está no mesmo repositório, aqui. Por causa da pandemia ele teve que ir embora às pressas e não teve tempo de testar o código; então funcionava, mas não era estável, porque ele heroicamente tentou compartilhar o pino analógico com o WiFi, o que causava quedas no WiFi que deixavam o sistema quase impossível de usar. Foi com esse código que eu comecei.


8 de junho de 2020

Depois de muitas horas tentando entender o código, consegui portar o código do Shakeel para um ESP32 e também dividir o arquivo, com cada função num arquivo separado.

Neste vídeo mostro o progresso que fiz até agora juntando os códigos e fazendo tudo funcionar no ESP32.

Veja a versão em alta resolução no YouTube

Portar o código para o ESP32 pode ser simples para um programador, mas para mim levou muitas horas.

Passos:

  1. separei tudo o que já era função em arquivo próprio, para ficar mais legível;
  2. depois abri outra sessão da Arduino IDE e comecei a copiar cada parte do código, linha por linha, para a nova sessão; nessa sessão renomeei o arquivo principal para "esp32irrigation"
  3. remapeei os pinos para os pinos do ESP32
  4. rodei o beautify no HTML no Brackets
  5. organizei o código para os pinos ficarem no topo
  6. criei variáveis para os pinos do RTC
  7. comentei as partes do código que imprimem erros no monitor serial

O código é grande demais para mostrar no HTML, então vou deixar um link para o estado do código hoje de madrugada: código aqui.

Neste vídeo dá para ver a montagem de teste do sistema de irrigação atual da fazenda.

Veja a versão em alta resolução no YouTube


9 de junho de 2020

Hoje, depois de testar os relés, percebi que alguns relés funcionam e outros não. Depois de testar direto na fonte de bancada, com 5 volts aplicados no pino de sinal, acredito que isso seja por causa da tensão baixa fornecida pelo ESP32 ou da tensão do sinal.

Aí assisti ao vídeo do GreatScott! no YouTube sobre relés e optoacopladores.

Vou garantir que os relés tenham uma alimentação de pelo menos 5 volts, que é a tensão exigida segundo o datasheet. O sinal deve funcionar bem com 3,3 V. Vai ser testado no local.

Tabela de dados da bobina do relé SRD com a versão de 5 V destacada

O outro ponto é a leitura de corrente, que está completamente errada. Para ter o sistema de irrigação funcionando, vou ter que rodar sem a medição de corrente até depurar isso.


10 de junho de 2020

Depois dos testes, a conclusão sobre o problema de tensão é que o ESP32 não deve alimentar o relé; isso deve vir de uma fonte externa, ajustada em 5 volts. O sinal pode vir do ESP32 sem mudança, mas o ESP32 tem que compartilhar o mesmo terra.

Mais mudanças no código:

  1. aumentei o intervalo em que a hora é mostrada de 3000 para 5000 ms (5 s), na variável RTCtimeInterval
  2. aumentei o intervalo em que a configuração é mostrada de 10000 para 30000 ms (30 s), na variável configTimeInterval
  3. coloquei nomes reconhecíveis na interface HTML dos relés
  4. limpei mais um pouco o monitor serial, tirando os segundos da exibição das configurações

Hoje de manhã vou colocar a montagem para trabalhar de novo, programar os horários de cada irrigação e testar em campo o módulo de irrigação com ESP32.

Estranhamente, o relógio RTC externo não está marcando a hora certa. Vou ver como usar o relógio interno com uma bateria externa para o caso de falta de energia.

Segundo esta discussão encerrada no repositório do ESP no GitHub e este tópico sobre a precisão do ESP, o relógio interno vai atrasar/adiantar: "Internal RTC clock frequency error is about 5%" (o erro de frequência do relógio RTC interno é de cerca de 5%). Isso pode ser resolvido com um cristal externo de 32,768 kHz montado conforme o esquema abaixo:

Outra solução seria deixar o ESP sempre conectado à Internet, pegando a hora da Internet para compensar a imprecisão.

Esquema do cristal externo de 32,768 kHz para o RTC do ESP32

Atualização depois de acordar

Hoje fui instalar a montagem de teste temporária. Resolvi alguns problemas.

  1. A montagem não funcionava quando desconectada do computador. Solução: a ligação da alimentação não estava certa; acabei ligando 2 cabos USB, um para o ESP32 e outro para os relés, e os dois compartilham o mesmo terra. Talvez eu também tenha resolvido o problema do relógio RTC com a mesma abordagem.

É assim que o protótipo está agora:

Protótipo: ESP32 dev kit numa protoboard com quatro módulos de relé Ainda falta ligar a bomba e as válvulas solenoides.

Protótipo instalado embaixo das telhas, ao lado de uma casa de vespas Espero que as vespas não se importem com a invasão

Ligando o sistema Ligando o sistema

testando...testando...

Depois dos testes voltei para o código para personalizar mais.

Primeiro assisti a uma série de vídeos no YouTube sobre funções, criando funções de verdade, e depois comecei a criar ainda mais funções.

Basicamente fui ver como reduzir a repetição de código e como mandar um valor para uma função.

A partir do código que eu tinha, criei esta função:

void turnOffRelay(int valveHere){
  // wait then turn valve relay OFF
  Serial.print("Waiting ");
  Serial.print(waitTimeValveOff / 1000);
  Serial.println("s before deactivating Valve Relay 3.");
  delay(waitTimeValveOff);
  digitalWrite(valveHere, LOW);
  valveHere = 0;
  Serial.print("*** Valve Relay ");
  Serial.print(valveHere);
  Serial.println("3 turned OFF ***");
}

Atenção: valveHere = 0; roda antes de Serial.print(valveHere);, então a mensagem sai sempre "Valve Relay 03 turned OFF", seja qual for a válvula. O "3" também está fixo nas duas mensagens. Imprima valveHere antes de zerar (ou tire essa linha) e tire o "3" fixo dos textos.

Toda vez que quero chamar esta função, uso:

      {
        int valveHere = valveRelay3;
        turnOffRelay (valveHere);
      }

O motivo de eu usar as {} é que eu tinha que declarar a variável local valveHere; assim eu podia substituir só aquele pedaço de código cada vez que chamava a função.

Tecnicamente, chamar essa função seria só ter este código aqui: turnOffRelay (valveHere); mas isso não funcionaria sozinho, porque estou sempre substituindo o pino do relé da válvula com int valveHere = valveRelay3;

Atenção: as {} e a variável local não são necessárias: o pino já vai como argumento, então só turnOffRelay(valveRelay3); faz a mesma coisa.

Ainda não testei o comportamento, mas pelo menos compila :-)


11 de junho de 2020

Gravei no ESP32 e o comportamento é inesperado. A função fica imprimindo sem parar. Acho que preciso devolver o estado de uma variável e não fiz isso. Vou tentar usar o código return variable; para fazer isso e depois testar, mas antes vou assistir de novo ao vídeo sobre funções para entender melhor.


13 de junho de 2020

Argh... OK, faz tempo que não atualizo. Bom, o último código que subi funciona e tenho algumas funções, mas ainda não estou satisfeito, porque funciona no Arduino mas não no PlatformIO. O que aprendi até agora é que a Arduino IDE faz muita coisa "por baixo dos panos", e no PlatformIO você precisa configurar tudo com mais detalhe.

Leia o seguinte:

No fim consegui gravar um arquivo no SPIFFS usando o PlatformIO. Eu já conseguia fazer isso com o Arduino, mas sem entender direito como. Agora acho que talvez tenha entendido. Para fazer isso, tive que configurar o arquivo platformio.ini e a árvore de diretórios seguindo uma série de passos, então vamos ver:

  1. Criei um novo arquivo de projeto na área da aba "projects" do PlatformIO
  2. Depois criei uma pasta data no mesmo nível da pasta src. Outra opção é colocar no arquivo platformio.ini um caminho diferente para a pasta data.
  3. Depois, com a placa conectada, rodei pio run -t uploadfs
  4. Tive que tirar a biblioteca Time do PlatformIO, porque ela estava em conflito com a ESPAsyncWebServer, e foi aqui que encontrei a informação.

Funcionou.

Com isso comecei a entender como eu não sabia o que estava fazendo. O código com que comecei tinha o HTML dentro do sketch do Arduino. Isso me incomodava. Agora, com esse novo entendimento, consigo deixar o HTML separado do arquivo do sketch.

Depois encontrei este tutorial: ESP32 Web Server using SPIFFS

E este, caso eu queira usar o plugin da Arduino IDE para o upload.

Queria muito ter entendido isso melhor antes! Passei tantos dias nisso!

O código C++ até agora.


                    /****************************************************************************
                    *                                  Aknowledments                           * 
                    *                                  by LucioPGN                             * 
                    ****************************************************************************/
                   /*  Up to this date: 07th of June 2020 I don't consider myself a programer 
                    *  so I need to stand on top of giants sholders for my programing projects:
                    *  A Portion of this code was based on Rui Santos Code;
                    *  A Portion of this code was based on losely based Shakeels code for ESP8266;
                    *  A Portion of this code was based on several websites I lost track of....!
                    *  My contributions: 
                    *     -So far I made it work on platformio :), that took me quite a lot of time
                    *     -That means:
                    *        +created a new project;
                    *        +created a folder named data under the main folder (fabfarm_irrigation)
                    *        +linked the platformio.ini to the folder of the project + the data folder
                    *        +linked the needed libraries to their github repo in the platformio.ini
                    *        +found a conflict with time library and ESPAsyncWebserverLibrary can be solved by renaming time library or by removing it
                    *        +Made it work with a separate HTML file under the data folder using SPIFFS
                    *        +I can load the data to Spiffs inside platformio
                    *  Things I still want to program for my final project:
                    *    -so far I ported Shakeels code into ESP32;
                    *    -simplify the code creating functions rather than repeating the code;
                    *    -try to separate the HTML part for a cleaner code (this applies to maybe mix up the other previous code to this);
                    *    -Improve the appearance/Interface of the code
                    *    -Add readings to HTML
                    *    -Add a log of occurrences like over current
                    *    -Add more safety for the equipment
                    *    -Add a phone interface (APP)
                    *    -Add function to set current time
                    *    -Add renaming function to each relay so one can relate the relay to the area of interest or at least rename relays to actual areas of the farm.
                    * 
                    ****************************************************************************/
                    
                   
                   //Required Libraries
                   #include "WiFi.h"
                   #include "ESPAsyncWebServer.h"
                   #include "SPIFFS.h"
                   #include <AsyncTCP.h>
                   
                   // Network Credentials
                   const char* ssid = "rato";
                   const char* password = "imakestuff";
                   
                   //Start the Async Web Server listening on port 80
                   AsyncWebServer server(80);
                   
                   // Set to true to define Relay as Normally Open (NO)
                   #define RELAY_NO false
                   
                   // Set number of relays, will be used in the array
                   #define NUM_RELAYS  4
                   
                   // Assign each GPIO to a relay
                   int relayGPIOs[NUM_RELAYS] = {26, 25, 33, 32};
                   
                   //
                   const char* PARAM_INPUT_1 = "relay";  
                   const char* PARAM_INPUT_2 = "state";
                   
                   // Replaces placeholder with button section in your web page
                   String processor(const String& var){
                     //Serial.println(var);
                     if(var == "BUTTONPLACEHOLDER"){
                       String buttons ="";
                       for(int i=1; i<=NUM_RELAYS; i++){
                         String relayStateValue = relayState(i);
                         //Here parts of the HTML will be parsed to index.html like Relay # followed by its value in variable for the GPIO numbers
                         buttons+= "<h4>Turn on water on " + String(i) + "</h4><h4>Valve (relay) #" + String(i) + " - GPIO " + relayGPIOs[i-1] + "</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"" + String(i) + "\" "+ relayStateValue +"><span class=\"slider\"></span></label>";
                       }
                       return buttons;
                     }
                     return String();
                   }
                   
                   String relayState(int valveRelayNum){
                     if(RELAY_NO){
                       if(digitalRead(relayGPIOs[valveRelayNum-1])){
                         return "";
                       }
                       else {
                         return "checked";
                       }
                     }
                     else {
                       if(digitalRead(relayGPIOs[valveRelayNum-1])){
                         return "checked";
                       }
                       else {
                         return "";
                       }
                     }
                     return "";
                   }
                   
                   void setup(){
                     // Serial port for debugging purposes
                     Serial.begin(9600);
                     // Initialize SPIFFS
                     if(!SPIFFS.begin(true)){
                       Serial.println("An Error has occurred while mounting SPIFFS");
                       return;
                     }
                     // Set all relays to off when the program starts - if set to Normally Open (NO), the relay is off when you set the relay to HIGH
                     for(int i=1; i<=NUM_RELAYS; i++){
                       pinMode(relayGPIOs[i-1], OUTPUT);
                       if(RELAY_NO){
                         digitalWrite(relayGPIOs[i-1], HIGH);
                       }
                       else{
                         digitalWrite(relayGPIOs[i-1], LOW);
                       }
                     }
                     
                     // Connect the ESP to the Wi-Fi using the credentials entered before
                     WiFi.begin(ssid, password);
                     while (WiFi.status() != WL_CONNECTED) {
                       delay(1000);
                       Serial.println("Connecting to WiFi..");
                     }
                   
                     // Print ESP32 Local IP Address
                     Serial.println(WiFi.localIP());
                   
                   /*
                   *Now we are going to configure the route where server will be listening for incoming HTTP requests 
                   and a function that will be executed when a request is received on that route.
                   We specify this by calling the "on" method on the server object. With server.on(){}; 
                   As first input, this method receives a string with the path where it will be listening. 
                   We are going to set it to listen for requests on the "/" route. This could be anything. 
                   It is basically what you write after the ip adress when in the browser or an APP.
                   This website has a great explanation of the ESP32 Arduino: Asynchronous HTTP web server
                   https://techtutorialsx.com/2017/12/01/esp32-arduino-asynchronous-http-webserver/
                   So... 
                   - First parameter here is: "/" thats the root directory.
                   - Second parameter is HTTP_GET thats an enum of type WebRequestMethod a method defined in the library here --> https://github.com/me-no-dev/ESPAsyncWebServer/blob/63b5303880023f17e1bca517ac593d8a33955e94/src/ESPAsyncWebServer.h
                   - Third parameter is a the function AsyncWebServerRequest
                   So there is this c++ lambda function used here. My litle understanding is that they are locally declared unamed function this means they dont have a name and are declared locally :-)
                    I don't grasp the concept fully haha.
                    the syntax is [captures](params){body} where in here [] is empity
                   */
                   
                     // Route for root / web page
                     server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
                       request->send(SPIFFS, "/index.html", String(), false, processor);
                     });
                   
            // Send a GET request to <ESP_IP>/update?relay=<inputMessage>&state=<inputMessage2>
             server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
               String inputMessage;
               String inputParam;
               String inputMessage2;
               String inputParam2;
               // GET input1 value on <ESP_IP>/update?relay=<inputMessage>
               if (request->hasParam(PARAM_INPUT_1) & request->hasParam(PARAM_INPUT_2)) {
                 inputMessage = request->getParam(PARAM_INPUT_1)->value();
                 inputParam = PARAM_INPUT_1;
                 inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
                 inputParam2 = PARAM_INPUT_2;
                 if(RELAY_NO){
                   Serial.print("NO ");
                   digitalWrite(relayGPIOs[inputMessage.toInt()-1], !inputMessage2.toInt());
                 }
                 else{
                   Serial.print("NC ");
                   digitalWrite(relayGPIOs[inputMessage.toInt()-1], inputMessage2.toInt());
                 }
               }
               else {
                 inputMessage = "No message sent";
                 inputParam = "none";
               }
               Serial.println(inputMessage + inputMessage2);
           
               //This is the last part of the lambda function. 
               //This method receives as first input the HTTP response code, which will be 200 in our case.  This is the HTTP response code for "OK".
               request->send_P(200, "text/plain", "OK");
             });
           
             // Start server here
             server.begin();
           }
           
           void loop(){
           
           }
        

Atenção: hasParam(PARAM_INPUT_1) & hasParam(PARAM_INPUT_2) usa o & bit a bit; o E lógico é &&. Com dois bool o resultado acaba sendo o mesmo, mas não faz curto-circuito, e o operador certo é &&.

Versão atual deste código (E-SPlash no GitHub)

O código HTML até agora.


<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 3.0rem;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
    .switch input {display: none}
    .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
    input:checked+.slider {background-color: #2196F3}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
  </style>
</head>
<body>
  <h2>Fab Farm Irrigation System</h2>
  %BUTTONPLACEHOLDER%
<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?relay="+element.id+"&state=1", true); }
  else { xhr.open("GET", "/update?relay="+element.id+"&state=0", true); }
  xhr.send();
}</script>
</body>
</html>
                

O arquivo HTML precisa ficar numa pasta chamada "data", ou em outro endereço, se especificado no platformio.ini.

Versão atual deste código (E-SPlash no GitHub)

Ele está assim agora:

Interface web com os botões dos relés

A aparência e o código agora são bem parecidos com os do site Random Nerd Tutorials, sobre relés controlados com o ESP32.

A principal diferença em relação ao código original é que ele roda no PlatformIO para VS Code e também usa uma estrutura de pastas separada para o arquivo HTML, que é gravado no SPIFFS do ESP.


14 de junho de 2020

Depois de toda essa descoberta pessoal, decidi que preciso começar o código do zero e usar só o algoritmo geral do sistema de irrigação atual instalado na fazenda. O motivo é que o código está bagunçado e repetitivo demais; as abordagens que encontrei nas fontes de ontem me fizeram perceber que é mais rápido adicionar as funcionalidades ao último código, uma por uma, do que recodificar tudo a partir do código que roda hoje na fazenda.

Hoje vou tentar adicionar informações atualizadas, como temperatura e umidade. Estou me baseando de novo no trabalho do Rui Santos, nesta página sobre uma estação meteorológica. Com essa abordagem acredito que também vou conseguir incluir a hora do dia usando um relógio RTC.

O esquema para ligar o DHT22 é do site dele:

Ligação do ESP32 com o DHT22 numa protoboard

Ele usa um resistor de 4,7k Ohm como pull-up.

Depois de um pouco de trabalho, a página está assim:

Página web mostrando temperatura e umidade

Acredito que a temperatura agora está negativa porque o sensor de temperatura e umidade está com defeito ou a minha fiação está meio frouxa. Acho que, quando eu fresar a minha própria placa e soldar tudo em vez de espetar numa protoboard, vai ficar mais confiável. De qualquer forma, estou recebendo os valores e consigo mostrá-los na interface web.

No monitor serial já consigo mostrar a hora da Internet e as informações da rede; agora preciso descobrir um jeito de pegar o valor da hora e mostrar na interface web. No momento estou mandando pelo código a palavra TESTE, e é ela que aparece no lugar da hora.

Monitor serial mostrando a hora da Internet e as informações da rede


15 de junho de 2020

Hoje vou me concentrar em mostrar a hora na interface web e talvez criar uma função para ligar o relé num horário definido.

Atualização das 20:44:

- Depois de passar o dia inteiro tentando, consegui mostrar a hora da máquina e a hora do sistema de irrigação (ESP32).

Agora a interface está assim:

Interface web mostrando a hora da máquina e a hora do ESP32

Quando eu crescer quero ser programador :-) e não passar um dia inteiro numa coisa que alguém resolve em poucas linhas!

O meu sofrimento acabou graças a um santo, com esta resposta no Stack Exchange.

Aqui está a função original postada lá:


void printLocalTime()
{
  time_t rawtime;
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo))
  {
    Serial.println("Failed to obtain time");
   return;
  }
  char timeStringBuff[50]; //50 chars should be enough
  strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
  //print like "const char*"
  Serial.println(timeStringBuff);

  //Optional: Construct String object 
  String asString(timeStringBuff);
}

Com pequenas modificações, mudando de uma função void para uma função String e adicionando um return que chamei de "timeAsAString":


//this function was found here https://arduino.stackexchange.com/questions/52676/how-do-you-convert-a-formatted-print-statement-into-a-string-variable
//I did a minor change so instead of a void function it now returns a string to be used to show time in the webinterface
String printFarmTime()
{
  time_t rawtime;
  struct tm timeinfo;
  getLocalTime(&timeinfo);
  char timeStringBuff[50]; //50 chars should be enough
  strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
  //print like "const char*"
  Serial.println(timeStringBuff);

  //Construct to create the String object
  String timeAsAString(timeStringBuff);
  return timeAsAString;
}

Atenção: esta versão tirou a verificação if(!getLocalTime(&timeinfo)), então, se o NTP ainda não respondeu, ela formata uma hora vazia (lixo). Mantenha a verificação e devolva algo como "time not set" quando falhar.

Então, com isso resolvido, vou tentar criar as funções para ligar e desligar os relés de acordo com o horário programado. Vamos ver se consigo terminar hoje...


16 de junho de 2020

Não, não trabalhei nisso, eu estava cansado, então hoje fiz alguns esboços.

Esboço da caixa e da interface web

Neste esboço mostro como vai ser o formato da caixa e como vai ser a interface web também.

Também estou lendo sobre String e sobre a sua manipulação.

Eu poderia seguir a abordagem de dividir as datas como String e convertê-las para outros tipos de valor para as operações que pretendo fazer. Outra possibilidade é pegar os dados já no tipo de que preciso desde o começo; embora eu prefira a segunda, ainda não encontrei uma solução, porque ainda não entendo bem a função getLocalTime(). Então vou ler mais sobre ela agora.

Peraí... essa foi rápida! Encontrei um post que resolve o que eu preciso. Aqui está:

Post no fórum do Arduino sobre getLocalTime Post no Fórum do Arduino do usuário Abhay

Então me baseei no post acima para criar a função abaixo:


          
      int gimeTime(char what) {

          int OnlyYear;
          int onlyMonth;
          int onlyDay;
          int onlyHour;
          int onlyMin;
          int onlySec;
      
          struct tm timeinfo;
          if(!getLocalTime(&timeinfo)){
            Serial.println("Failed to obtain time");
            //return;
            }
          onlyHour = timeinfo.tm_hour;
          onlyMin  = timeinfo.tm_min;
          onlySec  = timeinfo.tm_sec;
          onlyDay = timeinfo.tm_mday;
          onlyMonth = timeinfo.tm_mon + 1;
          OnlyYear = timeinfo.tm_year +1900;
      
          switch (what) {
            case 1:
            return onlyHour;
            break;
            case 2:
            return onlyMin;
            break;
            case 3:
            return onlySec;
            break;
            default:
          // if nothing else matches, do the default
          // default is optional
          break;
          }
      }
    
    

Atenção: o default termina sem return, então a função devolve um valor indefinido para qualquer coisa que não seja 1, 2 ou 3 (ponha return -1; ali). O mesmo quando o getLocalTime() falha: com o return comentado, ela segue lendo um timeinfo sem hora válida.

É isso, agora finalmente posso trabalhar no código de agendamento dos timers. Aliás, passei quase o dia inteiro tentando um jeito mais simples de pegar os horários, mas simplesmente não consegui, porque estou penando para entender o tipo (struct). Bom, talvez eu volte a isso no futuro, mas por enquanto está bom.


17 de junho de 2020

Atualização de madrugada:

Estou muito frustrado; preciso passar para os átomos e fechar a parte dos bits do meu projeto.

Tarde da noite, o máximo que avancei foi fazer uma pequena função para desligar os relés; aqui está:


        int zoneStartStop (int zone, int startStop){
        int waitTime = 1000;
        if (startStop == 1){
        digitalWrite(relayGPIOs[zone], startStop);
        Serial.print("*** Zone Valve");
        Serial.print(zone);
        Serial.println("ON ***");
        delay(waitTime);
        digitalWrite(pumpGpio, startStop);
        Serial.print("*** Pump turned ON ***");
        }
        else{
        digitalWrite(pumpGpio, startStop);
        Serial.println("*** Pump turned OFF ***");
        delay(waitTime);
        digitalWrite(relayGPIOs[zone], startStop);
        Serial.print("*** Zone Valve");
        Serial.print(zone);
        Serial.println("OFF ***");
        }
        }

(Código corrigido depois: ao parar, agora a bomba desliga antes de a válvula fechar, para nunca empurrar contra a válvula fechada, e a mensagem diz OFF.)

É um começo, mas ainda não chegou lá, e eu ainda não testei.

Atualização da manhã:

Então agora vou olhar outro exemplo para programar um timer, de novo do Rui Santos; está aqui. Talvez eu consiga mudar um pouco, integrar ao meu código e adicionar um horário de início, e depois usar o slider para definir quantos minutos vai rodar. Isso já seria suficiente por enquanto!

Até agora deixei o código compatível com o PlatformIO e também separei o arquivo index.html do código C++ e coloquei numa pasta própria, para ser gravado pela rotina de gravação no SPIFFS que descrevi em 13/06/2020.


Depois desse dia passei para a semana de programação de interfaces e aplicações, onde mostro como projetei a placa final e também os toques finais na interface.


Projetando a placa do meu sistema de irrigação

Depois de brincar um tempo com um kit de desenvolvimento ESP32, eu já tinha uma ideia do que precisava na minha placa. Então vamos ver a lista de capacidades que eu quero nela:

  1. Controle:
    1. 3 relés de válvula;
    2. 1 relé da bomba;
  2. Sensoriamento:
    1. Sensor de temperatura e umidade;
    2. Header de expansão para amperímetro (da bomba);
    3. Carga/medição da bateria;
    4. Header de expansão ou pad de solda para relógio RTC;

Com os requisitos em mãos, fui procurar uma biblioteca do Eagle para o ESP-WROOM-32 e encontrei várias:

  1. MacroYau/MacroYau-EAGLE-Libraries
  2. lpodkalicki/eagle-libraries
  3. sparkfun/ESP32_Miscellany

Depois de avaliar todas, decidi usar a do MacroYau/MacroYau.

ESP-WROOM-32 da biblioteca MacroYau no Eagle

Como quero o máximo de compatibilidade com os dev kits de ESP32 com que eu estava acostumado, fui procurar e encontrei uma pinagem do ESP32 WROOM, que usei para nomear os pinos.

Pinagem do ESP-WROOM-32Encontrada de novo no site Random Nerd Tutorials :-)

O passo seguinte foi ligar fios de net em cada pino. Queria ter feito isso na ordem em que estou apresentando, porque teria me poupado de refazer muitos fios que liguei errado da primeira vez. Bom, a lição é que o número lógico do pino e o número físico do pino nem sempre são iguais; então, com isso em mente, desta vez liguei tudo certo.

Ligando os fios de net no Eagle

Este é o resultado final:

ESP32 com todos os fios de net ligados

O relé que encontrei na SnapEDA não é o mesmo modelo que eu tenho, mas o footprint é o mesmo.

O circuito do relé encontrei online, aqui, no web.archive; o esquema está abaixo.

Esquema do circuito do relé encontrado online

Com o esquema em mãos, desenhei o seguinte no Eagle:

Esquema dos relés no Eagle

Para o circuito da bateria segui um circuito da página do Random Nerd Tutorials.

nameCircuito do ESP32 alimentado por energia solar com monitoramento do nível da bateria

Como por enquanto não tenho o módulo TP4056, o meu circuito vai ter só a parte de medição.

Esquema da alimentação e da medição da bateria

Os resistores do divisor de tensão foram calculados com a calculadora online daqui.

Calculadora de divisor de tensão: 5 V, 1,2 MOhm e 1,5 MOhm dão 2,778 V

Para o futuro, encontrei a biblioteca do TP4056 aqui, graças a uma dica do Luis Carvão, o meu instrutor remoto.

A biblioteca do sensor de umidade está aqui; com ela em mãos, foi só ligar alguns fios de net ao pino 32 do ESP32.

Esquema do sensor de umidade e temperatura

Para o sensor de corrente escolhi usar o ACS712; a biblioteca encontrei neste GitHub. O esquema pronto está abaixo:

Esquema do sensor de corrente ACS712

Também peguei a biblioteca do relógio de tempo real na SnapEDA, mas por enquanto não vou usar, então o esquema completo que desenhei está abaixo:

Esquema completo, primeira versão

Depois de um tempo olhando para o esquema, percebi alguns erros: esqueci de ligar o resistor do LED dos relés ao VCC, também esqueci de incluir um botão de reset e um botão de programação, necessários para colocar o ESP em modo de programação, e também os headers para o modo serial e para expansão; e, por fim, decidi incluir um cristal para marcar melhor a hora.

Depois desenhei os botões de Reset e de Programação:

Esquema dos botões de Reset e de Programação

Aqui corrigi os relés, adicionando o VCC, e também coloquei um optoacoplador entre o sinal e os relés para evitar qualquer retorno vindo dos relés.

Esquema dos relés com optoacopladores

Aqui adicionei o header TX RX, um cristal para o RTC interno marcar melhor a hora e headers de compatibilidade com o ESP32 DevKit.

Esquema dos headers e do cristal

Então, espero que finalmente, o meu esquema final...

Esquema final

Agora vou começar o roteamento da placa. Tenho que dizer que estou um pouco ansioso, porque nunca fiz uma placa tão complexa!

Olha que animador o que eu vejo quando passo para o modo board no Eagle!!!

Placa sem roteamento no modo board do Eagle

A primeira coisa que faço é mover o footprint do DevKit para o centro junto com o ESP32.

Footprint do DevKit e ESP32 movidos para o centro

Depois de tentar um tempo ligar na mão, dei uma chance ao autorouter. Tive que parar depois de alguns minutos e o resultado é ridículo. Vou tentar de novo movendo o ESP32 para cima e, se não der, não vou tentar a compatibilidade com o ESP32 DevKit.

Resultado do autorouter

Então desisti da compatibilidade com o DevKit e decidi ir devagar desta vez. Fiz um backup do meu trabalho, que abri em outra janela do Eagle, e apaguei todos os componentes em que eu não estava trabalhando; o esquema e a placa ficaram assim. A minha ideia é copiar os componentes de volta do backup à medida que eu termino cada passo. Então abaixo está como ficou o passo um.

Esquema do passo um

Na placa dá para ver o #1, o relé, o #2, o circuito do sensor de corrente, e o #3, o LED da placa.

Placa do passo um: relé, sensor de corrente e LED

Então agora copiei o primeiro relé de válvula e, quando fui para a placa, o Eagle me mostrou os componentes colados. Acho que assim é bem mais fácil.

Placa com o primeiro relé de válvula colado

Até agora a placa está assim. Ainda não roteei nenhum fio de alimentação, incluindo GND, 3.3V e 5V. Vou tentar o autorouter agora para ter uma ideia e talvez rotear na mão. Todo o processo de roteamento está mais bem descrito na semana de dispositivos de entrada.

Placa antes de rotear os fios de alimentação

Depois de terminar o layout da placa, decidi adicionar o logo. Clique em File, Import e Bitmap (por enquanto o Eagle só aceita .bmp).

Menu File, Import, Bitmap do Eagle

Escolha a cor a ser importada.

Escolhendo a cor a importar

Depois selecione a unidade, mm no meu caso, e o tamanho, e OK.

Selecionando a unidade e o tamanho do logo

Como eu queria fresar o logo em vez de carimbar, tive que movê-lo para a camada top.

Logo movido para a camada top

O layout final da placa, com furos e contornos de fresagem, como descrito na semana de dispositivos de saída.

Layout final da placa com furos e contornos de fresagem

Fresagem

Aqui mostro a visualização das rotas do G-code, com o vermelho sendo a trilha mais perto do cobre que vai ficar.

Visualização das rotas do G-code

Como sempre, e de novo mais bem descrito na semana de dispositivos de entrada, aqui vai uma olhada no software de autolevel.

Software de autolevel

Fresando o contorno da placa

Soldagem e teste/depuração

Inspecionando a placa contra a luz.

Para verificar se as trilhas estão todas boas, coloco a luz embaixo da placa e reviso tudo, procurando curtos ou trilhas faltando.

Inspecionando a placa com uma luz embaixo

O lado de baixo sendo montado.

Coloco os componentes grandes por último, para não dificultar a soldagem dos componentes SMD pequenos.

Lado de baixo da placa sendo montado

Lendo o monitor serial/esquema para gravar o firmware

Depois de soldar tudo, conecto a placa ao meu adaptador serial. Abaixo está o esquema.

Esquema de ligação do adaptador serial para gravar o firmware

Colei a primeira saída do ESP32 com o firmware de fábrica. Ele dá boot!


            --- Available filters and text transformations: colorize, debug, default, direct, esp32_exception_decoder, hexlify, log2file, nocontrol, printable, send_on_enter, time
            --- More details at http://bit.ly/pio-monitor-filters
            --- Miniterm on COM5  115200,8,N,1 ---
            --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
            ets Jun  8 2016 00:22:57

            rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
            configsip: 0, SPIWP:0x00
            clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
            mode:DIO, clock div:2
            load:0x3fff0008,len:8
            load:0x3fff0010,len:3480
            load:0x40078000,len:7804
            ho 0 tail 12 room 4
            load:0x40080000,len:252
            entry 0x40080034
            ␛[0;32mI (45) boot: ESP-IDF v2.0-3-gbef9896 2nd stage bootloader␛[0m   
            ␛[0;32mI (45) boot: compile time 05:59:45␛[0m
            ␛[0;32mI (45) boot: Enabling RNG early entropy source...␛[0m
            ␛[0;32mI (64) boot: SPI Speed      : 40MHz␛[0m
            ␛[0;32mI (77) boot: SPI Mode       : DIO␛[0m
            ␛[0;32mI (89) boot: SPI Flash Size : 4MB␛[0m
            ␛[0;32mI (101) boot: Partition Table:␛[0m
            ␛[0;32mI (113) boot: ## Label            Usage          Type ST Offset   Length␛[0m
            ␛[0;32mI (136) boot:  0 phy_init         RF data          01 01 0000f000 00001000␛[0m
            ␛[0;32mI (159) boot:  1 otadata          OTA data         01 00 00010000 00002000␛[0m
            ␛[0;32mI (182) boot:  2 nvs              WiFi data        01 02 00012000 0000e000␛[0m
            ␛[0;32mI (205) boot:  3 at_customize     unknown          40 00 00020000 000e0000␛[0m
            ␛[0;32mI (228) boot:  4 ota_0            OTA app          00 10 00100000 00180000␛[0m
            ␛[0;32mI (252) boot:  5 ota_1            OTA app          00 11 00280000 00180000␛[0m
            ␛[0;32mI (275) boot: End of partition table␛[0m
            ␛[0;32mI (288) boot: Disabling RNG early entropy source...␛[0m
            ␛[0;32mI (305) boot: Loading app partition at offset 00100000␛[0m
            ␛[0;32mI (1481) boot: segment 0: paddr=0x00100018 vaddr=0x00000000 size=0x0ffe8 ( 65512) ␛[0m
            ␛[0;32mI (1481) boot: segment 1: paddr=0x00110008 vaddr=0x3f400010 size=0x1c5f0 (116208) map␛[0m
            ␛[0;32mI (1498) boot: segment 2: paddr=0x0012c600 vaddr=0x3ffb0000 size=0x0215c (  8540) load␛[0m
            ␛[0;32mI (1528) boot: segment 3: paddr=0x0012e764 vaddr=0x40080000 size=0x00400 (  1024) load␛[0m
            ␛[0;32mI (1551) boot: segment 4: paddr=0x0012eb6c vaddr=0x40080400 size=0x1b028 (110632) load␛[0m
            ␛[0;32mI (1630) boot: segment 5: paddr=0x00149b9c vaddr=0x400c0000 size=0x00034 (    52) load␛[0m
            ␛[0;32mI (1631) boot: segment 6: paddr=0x00149bd8 vaddr=0x00000000 size=0x06430 ( 25648) ␛[0m
            ␛[0;32mI (1648) boot: segment 7: paddr=0x00150010 vaddr=0x400d0018 size=0x7a56c (501100) map␛[0m
            ␛[0;32mI (1675) heap_alloc_caps: Initializing. RAM available for dynamic allocation:␛[0m
            ␛[0;32mI (1697) heap_alloc_caps: At 3FFBA6B8 len 00025948 (150 KiB): DRAM␛[0m
            ␛[0;32mI (1719) heap_alloc_caps: At 3FFE8000 len 00018000 (96 KiB): D/IRAM␛[0m
            ␛[0;32mI (1740) heap_alloc_caps: At 4009B428 len 00004BD8 (18 KiB): IRAM␛[0m
            ␛[0;32mI (1761) cpu_start: Pro cpu up.␛[0m
            ␛[0;32mI (1773) cpu_start: Single core mode␛[0m
            ␛[0;32mI (1786) cpu_start: Pro cpu start user code␛[0m
            ␛[0;32mI (1846) cpu_start: Starting scheduler on PRO CPU.␛[0m
            ␛[0;32mI (2085) uart: queue free spaces: 10␛[0m
            Bin version:0.10.0
            I (2087) wifi: wifi firmware version: c604573
            I (2087) wifi: config NVS flash: enabled
            I (2088) wifi: config nano formating: disabled
            I (2096) wifi: Init dynamic tx buffer num: 32
            I (2097) wifi: wifi driver task: 3ffc4eac, prio:23, stack:3584
            I (2102) wifi: Init static rx buffer num: 10
            I (2106) wifi: Init dynamic rx buffer num: 0
            I (2110) wifi: Init rx ampdu len mblock:7
            I (2114) wifi: Init lldesc rx ampdu entry mblock:4
            I (2118) wifi: wifi power manager task: 0x3ffca254 prio: 21 stack: 2560
            I (2125) wifi: wifi timer task: 3ffcb2d4, prio:22, stack:3584
            ␛[0;31mE (2130) phy_init: PHY data partition validated␛[0m
            ␛[0;32mI (2152) phy: phy_version: 329, Feb 22 2017, 15:58:07, 0, 0␛[0m
            I (2152) wifi: mode : softAP (3c:71:bf:10:66:e1)
            I (2155) wifi: mode : sta (3c:71:bf:10:66:e0) + softAP (3c:71:bf:10:66:e1)
            I (2159) wifi: mode : softAP (3c:71:bf:10:66:e1)
        

Gravando o firmware:

Para gravar o meu firmware uso o Microsoft VS Code com o PlatformIO.

Antes de começar a gravação, primeiro tive que configurar o ambiente do PlatformIO. Descrevo isso aqui, na semana de desenvolvimento do projeto.

Depois seguro o botão do GPIO0 e aperto o botão de reset para entrar no modo de gravação do firmware. Aí clico no ícone de gravar o firmware e ele grava. Sucesso!


Depurando a placa.

Acabei tirando todos os componentes para analisar os sinais produzidos diretamente pelo microprocessador.

O resultado da depuração foi que descobri que não havia nada de errado com o código, com a soldagem do microprocessador ou com as trilhas próximas.

O que descobri foi que eu precisava colocar resistores de 220 ohms entre o sinal e o anodo, o pino número 1, de cada optoacoplador. Acontece que o optoacoplador tem um LED interno, e a corrente usada para acioná-lo precisa ser limitada.

Como calcular o resistor do LED do optoacoplador do site Electronics Stack Exchange

Datasheet do optoacoplador EL817: tensão e corrente direta

Então R = (3,3 V - 1,2 V) / 20 mA = 105 ohms, ou

Calculadora de LED online recomendando um resistor de 120 ohms

Aí cortei as trilhas e soldei os resistores de 220 ohms, porque era o que eu tinha em mãos, e funcionou, mas o valor certo é 120 ohms, como a calculadora de resistores online mostra.

Resistor de 220 ohms soldado sobre a trilha cortada

E é assim que ficou o esquema do relé:

Esquema do circuito do relé com optoacoplador


Código de rede no firmware

Há dois tipos de comunicação acontecendo na minha placa que posso descrever:



Celular e o ESP32

A rede entre o celular e o ESP32 é estabelecida graças a algumas bibliotecas trabalhando juntas:

  1. WiFi.h

    Ela é descrita assim na sua documentação:

    "Enables network connection (local and Internet) using the ESP32 built-in WiFi. With this library you can initiate Servers, Clients and send/receive UDP packets through WiFi. The shield can connect either to open or encrypted networks (WEP, WPA). The IP address can be assigned statically or through a DHCP. The library can also manage DNS." (Permite conexão de rede, local e com a Internet, usando o WiFi embutido do ESP32. Com esta biblioteca você pode iniciar servidores e clientes e enviar/receber pacotes UDP pelo WiFi. O shield pode se conectar a redes abertas ou criptografadas (WEP, WPA). O endereço IP pode ser atribuído de forma estática ou por DHCP. A biblioteca também pode gerenciar DNS.)

  2. AsyncTCP.h

    Ela é descrita assim na sua documentação:

    "This is a fully asynchronous TCP library, aimed at enabling trouble-free, multi-connection network environment for Espressif's ESP32 MCUs. This library is the base for ESPAsyncWebServer" (Esta é uma biblioteca TCP totalmente assíncrona, feita para permitir um ambiente de rede sem problemas e com múltiplas conexões para os microcontroladores ESP32 da Espressif. Esta biblioteca é a base da ESPAsyncWebServer.)

  3. ESPAsyncWebServer.h

    Ela é descrita assim na sua documentação:

    "Async HTTP and WebSocket Server for ESP8266 Arduino" (Servidor HTTP e WebSocket assíncrono para ESP8266 Arduino)

    Então, basicamente, no meu projeto essas bibliotecas são responsáveis por estabelecer a conexão de rede com um roteador e também por criar um ponto de acesso.

    Elas também criam o servidor web e um servidor DHCP, e com isso ele fornece um endereço IP caso alguém se conecte pelo ponto de acesso criado com este código:

      //Soft Wifi Access point setup
      WiFi.softAP("softap", "password_here");
      IPAddress IP = WiFi.softAPIP();

    A conexão com o roteador é estabelecida com este código:

      //start wifi sessions as a client.
      //Wifi client setup
      const char* ssid = doc["data"]["ssid"];
      const char* password = doc["data"]["pass"];
      ssid = "fabfarm";
      password = "password_here";
      WiFi.begin(ssid, password);

    Atenção: o ssid e o password lidos do JSON são sobrescritos logo em seguida pelos valores fixos das duas linhas seguintes, então as credenciais do arquivo JSON nunca são usadas. Tire as duas atribuições fixas se quiser que os valores do JSON valham.

    O servidor web é iniciado com este código: AsyncWebServer server(81);


Ligando o C++ e o HTML com a ArduinoJson

JSON é o formato que escolhi para a comunicação entre a interface web e o microprocessador.

A ArduinoJson.h é descrita assim na sua documentação:

ArduinoJson is a C++ JSON library for Arduino and IoT (Internet Of Things) (ArduinoJson é uma biblioteca JSON em C++ para Arduino e IoT, a Internet das Coisas)

Uma ótima fonte de documentação e um ótimo assistente para criar o programa de parsing deserializeJson() e o programa de serialização serializeJson(doc, Serial) é o site da ArduinoJson.

Para usar a biblioteca, primeiro tive que criar um arquivo json. Esse arquivo vai ser usado pelo assistente para gerar o código em C++.

Com o arquivo JSON criado na mão, é hora de criar os programas em C++. O assistente então cria o código C++ para serializar e também fazer o parsing dos dados de e para o arquivo JSON que está dentro do sistema de arquivos.

Assistente da ArduinoJson, primeiro passo

Na caixa de diálogo da esquerda colei o conteúdo do arquivo json.

Arquivo JSON colado no assistente da ArduinoJson

O site gera o código automaticamente.

Código gerado pelo assistente da ArduinoJson

Abaixo mostro o código gerado pelo assistente.

Programa de parsing

const size_t capacity = JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(2) + 2*JSON_ARRAY_SIZE(3) + JSON_OBJECT_SIZE(2) + 6*JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + 3*JSON_OBJECT_SIZE(6) + 540;
DynamicJsonDocument doc(capacity);

const char* json = "{\"data\":{\"currentTime\":\"Wednesday, June 24 2020 23:49:23\",\"temperature\":\"22.30\",\"humidity\":\"74.00\",\"override\":0,\"ssid\":\"rato\",\"pass\":\"imakestuff\"},\"relays\":[{\"name\":\"Fruit Tree\",\"pin\":25,\"isRunning\":1,\"isEnabled\":0,\"times\":[{\"startTime\":\"01:01\",\"duration\":0,\"hour\":1,\"min\":1}]},{\"name\":\"Vegie Garden\",\"pin\":26,\"isRunning\":1,\"isEnabled\":0,\"status\":1,\"times\":[{\"startTime\":\"02:02\",\"duration\":32,\"hour\":2,\"min\":2},{\"startTime\":\"02:02\",\"duration\":32,\"hour\":2,\"min\":2}]},{\"name\":\"Cypress Hill\",\"pin\":33,\"isRunning\":1,\"isEnabled\":0,\"status\":1,\"times\":[{\"startTime\":\"03:03\",\"duration\":60,\"hour\":3,\"min\":3},{\"startTime\":\"03:03\",\"duration\":60,\"hour\":3,\"min\":3},{\"startTime\":\"03:03\",\"duration\":60,\"hour\":3,\"min\":3}]}]}";

deserializeJson(doc, json);

JsonObject data = doc["data"];
const char* data_currentTime = data["currentTime"]; // "Wednesday, June 24 2020 23:49:23"
const char* data_temperature = data["temperature"]; // "22.30"
const char* data_humidity = data["humidity"]; // "74.00"
int data_override = data["override"]; // 0
const char* data_ssid = data["ssid"]; // "rato"
const char* data_pass = data["pass"]; // "imakestuff"

JsonArray relays = doc["relays"];

JsonObject relays_0 = relays[0];
const char* relays_0_name = relays_0["name"]; // "Fruit Tree"
int relays_0_pin = relays_0["pin"]; // 25
int relays_0_isRunning = relays_0["isRunning"]; // 1
int relays_0_isEnabled = relays_0["isEnabled"]; // 0

JsonObject relays_0_times_0 = relays_0["times"][0];
const char* relays_0_times_0_startTime = relays_0_times_0["startTime"]; // "01:01"
int relays_0_times_0_duration = relays_0_times_0["duration"]; // 0
int relays_0_times_0_hour = relays_0_times_0["hour"]; // 1
int relays_0_times_0_min = relays_0_times_0["min"]; // 1

JsonObject relays_1 = relays[1];
const char* relays_1_name = relays_1["name"]; // "Vegie Garden"
int relays_1_pin = relays_1["pin"]; // 26
int relays_1_isRunning = relays_1["isRunning"]; // 1
int relays_1_isEnabled = relays_1["isEnabled"]; // 0
int relays_1_status = relays_1["status"]; // 1

JsonObject relays_1_times_0 = relays_1["times"][0];
const char* relays_1_times_0_startTime = relays_1_times_0["startTime"]; // "02:02"
int relays_1_times_0_duration = relays_1_times_0["duration"]; // 32
int relays_1_times_0_hour = relays_1_times_0["hour"]; // 2
int relays_1_times_0_min = relays_1_times_0["min"]; // 2

JsonObject relays_1_times_1 = relays_1["times"][1];
const char* relays_1_times_1_startTime = relays_1_times_1["startTime"]; // "02:02"
int relays_1_times_1_duration = relays_1_times_1["duration"]; // 32
int relays_1_times_1_hour = relays_1_times_1["hour"]; // 2
int relays_1_times_1_min = relays_1_times_1["min"]; // 2

JsonObject relays_2 = relays[2];
const char* relays_2_name = relays_2["name"]; // "Cypress Hill"
int relays_2_pin = relays_2["pin"]; // 33
int relays_2_isRunning = relays_2["isRunning"]; // 1
int relays_2_isEnabled = relays_2["isEnabled"]; // 0
int relays_2_status = relays_2["status"]; // 1

JsonArray relays_2_times = relays_2["times"];

JsonObject relays_2_times_0 = relays_2_times[0];
const char* relays_2_times_0_startTime = relays_2_times_0["startTime"]; // "03:03"
int relays_2_times_0_duration = relays_2_times_0["duration"]; // 60
int relays_2_times_0_hour = relays_2_times_0["hour"]; // 3
int relays_2_times_0_min = relays_2_times_0["min"]; // 3

JsonObject relays_2_times_1 = relays_2_times[1];
const char* relays_2_times_1_startTime = relays_2_times_1["startTime"]; // "03:03"
int relays_2_times_1_duration = relays_2_times_1["duration"]; // 60
int relays_2_times_1_hour = relays_2_times_1["hour"]; // 3
int relays_2_times_1_min = relays_2_times_1["min"]; // 3

JsonObject relays_2_times_2 = relays_2_times[2];
const char* relays_2_times_2_startTime = relays_2_times_2["startTime"]; // "03:03"
int relays_2_times_2_duration = relays_2_times_2["duration"]; // 60
int relays_2_times_2_hour = relays_2_times_2["hour"]; // 3
int relays_2_times_2_min = relays_2_times_2["min"]; // 3

Programa de serialização

const size_t capacity = JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(2) + 2*JSON_ARRAY_SIZE(3) + JSON_OBJECT_SIZE(2) + 6*JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + 3*JSON_OBJECT_SIZE(6);
DynamicJsonDocument doc(capacity);

JsonObject data = doc.createNestedObject("data");
data["currentTime"] = "Wednesday, June 24 2020 23:49:23";
data["temperature"] = "22.30";
data["humidity"] = "74.00";
data["override"] = 0;
data["ssid"] = "rato";
data["pass"] = "imakestuff";

JsonArray relays = doc.createNestedArray("relays");

JsonObject relays_0 = relays.createNestedObject();
relays_0["name"] = "Fruit Tree";
relays_0["pin"] = 25;
relays_0["isRunning"] = 1;
relays_0["isEnabled"] = 0;

JsonArray relays_0_times = relays_0.createNestedArray("times");

JsonObject relays_0_times_0 = relays_0_times.createNestedObject();
relays_0_times_0["startTime"] = "01:01";
relays_0_times_0["duration"] = 0;
relays_0_times_0["hour"] = 1;
relays_0_times_0["min"] = 1;

JsonObject relays_1 = relays.createNestedObject();
relays_1["name"] = "Vegie Garden";
relays_1["pin"] = 26;
relays_1["isRunning"] = 1;
relays_1["isEnabled"] = 0;
relays_1["status"] = 1;

JsonArray relays_1_times = relays_1.createNestedArray("times");

JsonObject relays_1_times_0 = relays_1_times.createNestedObject();
relays_1_times_0["startTime"] = "02:02";
relays_1_times_0["duration"] = 32;
relays_1_times_0["hour"] = 2;
relays_1_times_0["min"] = 2;

JsonObject relays_1_times_1 = relays_1_times.createNestedObject();
relays_1_times_1["startTime"] = "02:02";
relays_1_times_1["duration"] = 32;
relays_1_times_1["hour"] = 2;
relays_1_times_1["min"] = 2;

JsonObject relays_2 = relays.createNestedObject();
relays_2["name"] = "Cypress Hill";
relays_2["pin"] = 33;
relays_2["isRunning"] = 1;
relays_2["isEnabled"] = 0;
relays_2["status"] = 1;

JsonArray relays_2_times = relays_2.createNestedArray("times");

JsonObject relays_2_times_0 = relays_2_times.createNestedObject();
relays_2_times_0["startTime"] = "03:03";
relays_2_times_0["duration"] = 60;
relays_2_times_0["hour"] = 3;
relays_2_times_0["min"] = 3;

JsonObject relays_2_times_1 = relays_2_times.createNestedObject();
relays_2_times_1["startTime"] = "03:03";
relays_2_times_1["duration"] = 60;
relays_2_times_1["hour"] = 3;
relays_2_times_1["min"] = 3;

JsonObject relays_2_times_2 = relays_2_times.createNestedObject();
relays_2_times_2["startTime"] = "03:03";
relays_2_times_2["duration"] = 60;relays_2_times_2["hour"] = 3;
relays_2_times_2["min"] = 3;

serializeJson(doc, Serial);

Com a ajuda do meu amigo Jeff Knight, sem quem eu não teria conseguido, o código gerado foi simplificado para o meu programa. Depois criei algumas funções, e a imagem abaixo mostra o monitor serial imprimindo o resultado da desserialização do arquivo JSON lido.

Monitor serial imprimindo o arquivo JSON desserializado

Fiz este diagrama lógico para não esquecer como funciona:

Diagrama lógico do firmware do E-Splash


Fresando a caixa

Projetei a caixa no SolidWorks e animei a placa dentro dela, abaixo.

Aqui fresei o interior da caixa, com os furos dos parafusos e os espaçadores todos integrados.

Fresando o interior da caixa

Depois de virar a caixa dentro de um gabarito, consigo alinhar a caixa perfeitamente, então fresei o lado da frente.

Fresando a frente da caixa num gabarito

Aqui a placa e a caixa.

A placa e a caixa

Aqui testo o encaixe antes de ligar os fios na placa.

Teste de encaixe da placa na caixa


Ligando a placa dentro da caixa.

Uso uma passagem de fios à prova d'água para entrar na caixa.

Passagem de fios à prova d'água na caixa

Aqui testo o encaixe da placa dentro da caixa com os fios.

Teste de encaixe da placa com os fios

Aqui os fios estão presos e prontos para ser fechados dentro.

Fios presos dentro da caixa

Aqui a caixa está parafusada e pronta para ser ligada.

Caixa fechada e pronta para ser ligada


Aplicando epóxi.

Passei fita por fora e coloquei camadas de epóxi dentro dos furos, para a luz dos LEDs passar para fora mesmo eles não estando totalmente alinhados com os LEDs lá dentro.

Epóxi em camadas nos furos dos LEDs

Aqui o epóxi já curado, antes de lixar.

Epóxi curado antes de lixar


Diagrama de ligações

Aqui está o diagrama geral do sistema inteiro.

Diagrama de ligações do sistema de irrigação inteiro


Tubos na estufa

Depois montei alguns tubos para a demonstração.

Tubos de irrigação montados na estufa

E tudo funciona.

Irrigação funcionando na estufa

Demonstração da irrigação na estufa


Hero shot

No fim, funciona assim:

← (03 jun) Invenção, Propriedade Intelectual e Renda · todas as semanas · Projeto Final (10 jun) →