Redes e Comunicação Embarcadas


Tarefa em grupo

Tarefa individual


Resultados de aprendizagem:


Você:


Software usado


Arquivos

  1. Página do projeto no GitLab Fabcloud, com a placa, a caixa e o firmware.
  2. Firmware
  3. Arquivos da placa
  4. Caixa

Tarefa em grupo

Veja a página da tarefa em grupo.


Índice


Diagrama da rede

Diagrama da rede

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 funções que eu quero nela:

  1. Controlar:
    1. 3 relés de válvula;
    2. 1 relé da bomba;
  2. Medir:
    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 o relógio RTC;

Com os requisitos em mãos, fui procurar uma biblioteca do Eagle para o ESP-WROOM-32 e achei 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.

Design da placa com ESP

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

Pinagem do ESP32

O passo seguinte foi ligar fios de net a 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. A lição é que os números lógicos e os números físicos dos pinos nem sempre são iguais; com isso em mente, desta vez liguei tudo certo.

Ligando os fios no Eagle

O resultado final é este:

Resultado final do ESP32

O relé que achei no snapeda não é o mesmo modelo do que eu tenho, mas o footprint é.

Achei o circuito do relé online aqui, no web.archive; o esquemático está abaixo.

Esquemático do circuito do relé

Com o esquemático em mãos, desenhei o seguinte no Eagle:

Esquemático do relé

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

Circuito do ESP32 alimentado por energia solar

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

Medição da alimentação

O resistor do divisor de tensão foi calculado com a calculadora online aqui.

Calculadora de divisor de tensão

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

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

Esquemático do DHT

Para o sensor de corrente, escolhi usar o ACS712. Achei a biblioteca neste GitHub. O esquemático pronto está abaixo:

Esquemático do sensor de corrente

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

Esquemático completo

Depois de ficar um tempo olhando para o esquemático, percebi alguns erros. Esqueci de ligar o resistor do LED dos relés ao VCC, e também esqueci de colocar um botão de reset e um de programação, necessários para colocar o ESP em modo de programação, além de headers para o modo serial e para expansão. Por fim, decidi incluir um cristal para marcar melhor o tempo.

Depois desenhei os botões de Reset e de Program:

Botões de Reset e Program

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 dos relés.

Relés com optoacoplador

Aqui adicionei um header TX RX, um cristal para o RTC interno marcar melhor o tempo e headers de compatibilidade com o ESP32DEVKIT.

Headers e cristal

Então, espero eu, finalmente meu esquemático final...

Esquemático final

Agora vou começar o roteamento da placa. Confesso que estou meio ansioso, porque nunca fiz uma placa tão complexa!

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

O animador modo board

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

Primeira coisa

Depois de tentar um tempo ligar à mão, dou uma chance ao autorouter. Tive que parar depois de alguns minutos, e o resultado é ridículo. Vou tentar de novo subindo o ESP32 e, se não der, vou desistir da compatibilidade com o ESP32devkit.

Dá para fabricar isso?

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

Passo 01a

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

Passo 01b

Aí copiei o relé da primeira válvula e, quando fui para a placa, o Eagle me mostrou os componentes colados. Acho que assim é bem mais fácil.

Passo 02b1

Até agora, a placa está assim. Ainda não roteei nenhum fio de alimentação, incluindo GND, 3.3V e 5V. Agora vou tentar o autorouter para ter uma ideia e talvez rotear à mão. O processo de roteamento completo está mais bem descrito na semana de Input Devices.

Passo 3b

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

Logo 01

Escolha a cor a ser importada.

Logo 02

Depois escolha a unidade, mm no meu caso, e o tamanho, e clique em OK.

Logo 03

Como eu queria fresar o logo em vez de carimbar, tive que mover para a camada top.

Logo 04

O layout final da placa com furos e contornos de fresagem, como descrito na semana de Output Devices.

Logo 05


Fresagem

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

Logo 06

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

Autolevel

Contorno


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.

Placa contra a luz

O lado de baixo sendo montado.

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

Lado de baixo

Lendo o monitor serial/esquemático para gravar o firmware

Depois de soldar tudo, ligo a placa ao meu adaptador serial. Abaixo está o esquemático.

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 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 Project Development.

Depois seguro o botão do GPIO0 e aperto o 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 revisar os sinais produzidos diretamente pelo microcontrolador.

Vídeo da depuração

O resultado da depuração foi que descobri que não havia nada errado com o código, com a solda do microcontrolador nem com as trilhas próximas.

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

Cálculo do resistor Do site Electronics Stack Exchange

Datasheet

Então R=(3.3V-1.2V)/20mA=105ohms, ou

Cálculo

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

Resistor

E é assim que fica o esquemático do relé:

Esquemático do relé


Código de rede no firmware

Tem dois tipos de comunicação acontecendo na minha placa que posso descrever.


Celular e ESP32

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

  1. WiFi.h

    A documentação dela descreve assim:

    "Permite a conexão de rede (local e Internet) usando o WiFi embutido do ESP32. Com esta biblioteca, você pode iniciar servidores, clientes e enviar/receber pacotes UDP via 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

    A documentação dela descreve assim:

    "Esta é uma biblioteca TCP totalmente assíncrona, feita para permitir um ambiente de rede sem problemas e com múltiplas conexões para os MCUs ESP32 da Espressif. Esta biblioteca é a base do ESPAsyncWebServer"

  3. ESPAsyncWebServer.h

    A documentação dela descreve assim:

    "Servidor HTTP e WebSocket assíncrono para ESP8266 Arduino"

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

    Elas também criam um servidor web e um servidor DHCP, e com isso ele entrega 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 é feita 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 o ArduinoJson

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

A documentação do ArduinoJson.h descreve assim:

O ArduinoJson é uma biblioteca JSON em C++ para Arduino e IoT (Internet das Coisas)

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

Para usar a biblioteca, primeiro tive que criar um arquivo json. O assistente vai usar esse arquivo para gerar o código para o C++.

Com o arquivo JSON criado à 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 fica no sistema de arquivos.

Assistente do ArduinoJson

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

Código no assistente do ArduinoJson

O site gera o código automaticamente.

Código JSON gerado

Abaixo mostro o código gerado no assistente.

Programa de parsing (desserializar/decodificar)

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 (codificar)

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 o qual eu não teria conseguido sozinho, o código gerado foi simplificado para o meu programa. Depois criei algumas funções, e a imagem a seguir mostra o monitor serial imprimindo o resultado da desserialização do arquivo JSON lido.

Saída da desserialização do JSON

Fiz este diagrama para não esquecer como funciona:

Diagrama do sistema


Hero shot

No fim, funciona assim:

Demonstração do sistema de irrigação

← (29 abr) Moldagem e Fundição · todas as semanas · Programação de Interfaces e Aplicações (13 mai) →