MCU & FPGA C,C++ Usando JSON e cJSON em Microcontroladores

Usando JSON e cJSON em Microcontroladores


Interpretando arrays recebidos em JSON

Agora vamos interpretar um JSON recebido do servidor contendo um array. Esse caso é muito comum quando o servidor precisa enviar uma lista de comandos, uma tabela simples de configuração, uma lista de canais ativos ou parâmetros de calibração.

Imagine que o servidor responda com uma lista de GPIOs que devem ser configurados pelo microcontrolador:

{
  "status": "ok",
  "command": {
    "relay": true,
    "sample_interval_ms": 5000
  },
  "gpio_outputs": [
    {
      "pin": 5,
      "state": true
    },
    {
      "pin": 12,
      "state": false
    },
    {
      "pin": 18,
      "state": true
    }
  ]
}

Aqui temos um array chamado gpio_outputs. Cada item desse array é um objeto com dois campos: pin e state.

Visualmente:

root
├── status
├── command
│   ├── relay
│   └── sample_interval_ms
└── gpio_outputs
    ├── item 0
    │   ├── pin
    │   └── state
    ├── item 1
    │   ├── pin
    │   └── state
    └── item 2
        ├── pin
        └── state

Em firmware, não devemos deixar um array recebido crescer sem limite. Se o servidor enviar cem comandos, mas o microcontrolador só suporta oito, precisamos limitar a leitura. Por isso, vamos criar uma constante:

#define APP_MAX_GPIO_COMMANDS 8

Agora vamos ampliar a estrutura AppConfig.

app_config.h

#ifndef APP_CONFIG_H
#define APP_CONFIG_H

#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>

#define APP_MAX_GPIO_COMMANDS 8

/**
 * @brief Representa um comando para configurar uma saída GPIO.
 */
typedef struct
{
    uint8_t pin;
    bool state;
} GpioCommand;

/**
 * @brief Representa comandos e configurações recebidas do servidor.
 */
typedef struct
{
    bool relay_enabled;
    uint32_t sample_interval_ms;
    float temperature_max;
    float voltage_min;

    GpioCommand gpio_commands[APP_MAX_GPIO_COMMANDS];
    size_t gpio_command_count;
} AppConfig;

#endif

Agora vamos atualizar o servidor simulado para retornar esse array.

web_mock.c

#include <stdio.h>
#include "web_mock.h"

/**
 * @brief Simula um servidor web recebendo JSON e retornando comandos.
 *
 * @param request_json JSON enviado pelo firmware.
 * @return Resposta JSON simulada.
 */
const char *web_mock_post(const char *request_json)
{
    printf("[WEB MOCK] Requisicao recebida:\n%s\n", request_json);

    return "{"
           "\"status\":\"ok\","
           "\"command\":{"
               "\"relay\":true,"
               "\"sample_interval_ms\":5000"
           "},"
           "\"thresholds\":{"
               "\"temperature_max\":60.0,"
               "\"voltage_min\":3.0"
           "},"
           "\"gpio_outputs\":["
               "{"
                   "\"pin\":5,"
                   "\"state\":true"
               "},"
               "{"
                   "\"pin\":12,"
                   "\"state\":false"
               "},"
               "{"
                   "\"pin\":18,"
                   "\"state\":true"
               "}"
           "]"
           "}";
}

Agora vem a parte mais importante: interpretar o array. A cJSON permite percorrer um array usando a macro:

cJSON_ArrayForEach(item, array)
{
    /* processa item */
}

Vamos atualizar o parser.

json_parser.c com leitura de array

#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>

#include "cJSON.h"
#include "json_parser.h"

/**
 * @brief Interpreta uma resposta JSON enviada pelo servidor.
 *
 * @param response_json String JSON recebida.
 * @param config Estrutura de destino.
 * @return true em caso de sucesso, false em caso de erro.
 */
bool json_parser_parse_server_response(
    const char *response_json,
    AppConfig *config
)
{
    if (response_json == NULL || config == NULL)
    {
        return false;
    }

    cJSON *root = cJSON_Parse(response_json);
    if (root == NULL)
    {
        return false;
    }

    cJSON *status = cJSON_GetObjectItemCaseSensitive(root, "status");
    if (!cJSON_IsString(status) || status->valuestring == NULL)
    {
        cJSON_Delete(root);
        return false;
    }

    if (strcmp(status->valuestring, "ok") != 0)
    {
        cJSON_Delete(root);
        return false;
    }

    cJSON *command = cJSON_GetObjectItemCaseSensitive(root, "command");
    if (!cJSON_IsObject(command))
    {
        cJSON_Delete(root);
        return false;
    }

    cJSON *relay = cJSON_GetObjectItemCaseSensitive(command, "relay");
    if (!cJSON_IsBool(relay))
    {
        cJSON_Delete(root);
        return false;
    }

    cJSON *sample_interval = cJSON_GetObjectItemCaseSensitive(
        command,
        "sample_interval_ms"
    );

    if (!cJSON_IsNumber(sample_interval))
    {
        cJSON_Delete(root);
        return false;
    }

    cJSON *thresholds = cJSON_GetObjectItemCaseSensitive(root, "thresholds");
    if (!cJSON_IsObject(thresholds))
    {
        cJSON_Delete(root);
        return false;
    }

    cJSON *temperature_max = cJSON_GetObjectItemCaseSensitive(
        thresholds,
        "temperature_max"
    );

    if (!cJSON_IsNumber(temperature_max))
    {
        cJSON_Delete(root);
        return false;
    }

    cJSON *voltage_min = cJSON_GetObjectItemCaseSensitive(
        thresholds,
        "voltage_min"
    );

    if (!cJSON_IsNumber(voltage_min))
    {
        cJSON_Delete(root);
        return false;
    }

    config->relay_enabled = cJSON_IsTrue(relay);
    config->sample_interval_ms = (uint32_t)sample_interval->valuedouble;
    config->temperature_max = (float)temperature_max->valuedouble;
    config->voltage_min = (float)voltage_min->valuedouble;
    config->gpio_command_count = 0;

    cJSON *gpio_outputs = cJSON_GetObjectItemCaseSensitive(
        root,
        "gpio_outputs"
    );

    if (cJSON_IsArray(gpio_outputs))
    {
        cJSON *gpio_item = NULL;

        cJSON_ArrayForEach(gpio_item, gpio_outputs)
        {
            if (config->gpio_command_count >= APP_MAX_GPIO_COMMANDS)
            {
                break;
            }

            if (!cJSON_IsObject(gpio_item))
            {
                continue;
            }

            cJSON *pin = cJSON_GetObjectItemCaseSensitive(gpio_item, "pin");
            cJSON *state = cJSON_GetObjectItemCaseSensitive(gpio_item, "state");

            if (!cJSON_IsNumber(pin) || !cJSON_IsBool(state))
            {
                continue;
            }

            if (pin->valuedouble < 0 || pin->valuedouble > 255)
            {
                continue;
            }

            size_t index = config->gpio_command_count;

            config->gpio_commands[index].pin = (uint8_t)pin->valuedouble;
            config->gpio_commands[index].state = cJSON_IsTrue(state);

            config->gpio_command_count++;
        }
    }

    cJSON_Delete(root);

    return true;
}

Perceba que o array gpio_outputs foi tratado como opcional. Se ele não existir, o parser ainda retorna true, apenas deixando gpio_command_count igual a zero. Essa é uma estratégia útil quando queremos permitir que o servidor envie apenas alguns campos, sem obrigar todos os comandos em toda resposta.

Também usamos continue quando um item está malformado. Isso significa que, se um comando GPIO específico vier errado, ele será ignorado, mas os outros comandos válidos continuarão sendo processados. Em alguns sistemas, essa é a melhor abordagem. Em outros, principalmente sistemas críticos, pode ser melhor rejeitar a mensagem inteira ao encontrar qualquer erro.

Agora vamos atualizar o main.c para exibir os comandos GPIO recebidos.

main.c

#include <stdio.h>
#include <stdlib.h>

#include "json_builder.h"
#include "web_mock.h"
#include "json_parser.h"
#include "app_config.h"

int main(void)
{
    char *request = json_builder_create_telemetry_message();

    if (request == NULL)
    {
        printf("[APP] Erro ao criar JSON de telemetria.\n");
        return 1;
    }

    const char *response = web_mock_post(request);

    AppConfig config = {0};

    bool parsed = json_parser_parse_server_response(response, &config);

    if (!parsed)
    {
        printf("[APP] Erro ao interpretar resposta do servidor.\n");
        free(request);
        return 1;
    }

    printf("[APP] Resposta interpretada com sucesso.\n");
    printf("[APP] Relay: %s\n", config.relay_enabled ? "ligado" : "desligado");
    printf("[APP] Intervalo de amostragem: %lu ms\n",
           (unsigned long)config.sample_interval_ms);
    printf("[APP] Temperatura maxima: %.2f C\n", config.temperature_max);
    printf("[APP] Tensao minima: %.2f V\n", config.voltage_min);

    printf("[APP] Comandos GPIO recebidos: %lu\n",
           (unsigned long)config.gpio_command_count);

    for (size_t i = 0; i < config.gpio_command_count; i++)
    {
        printf("[APP] GPIO %u => %s\n",
               config.gpio_commands[i].pin,
               config.gpio_commands[i].state ? "HIGH" : "LOW");
    }

    free(request);

    return 0;
}

A saída esperada será semelhante a:

[WEB MOCK] Requisicao recebida:
{"device":{"id":"mcu-001","model":"generic-mcu","firmware":"1.0.0"},"telemetry":{"temperature":28.75,"voltage":3.29,"uptime":15240,"adc_samples":[1023,1040,1035,1051,1062]}}
[APP] Resposta interpretada com sucesso.
[APP] Relay: ligado
[APP] Intervalo de amostragem: 5000 ms
[APP] Temperatura maxima: 60.00 C
[APP] Tensao minima: 3.00 V
[APP] Comandos GPIO recebidos: 3
[APP] GPIO 5 => HIGH
[APP] GPIO 12 => LOW
[APP] GPIO 18 => HIGH

Em um microcontrolador real, o trecho:

printf("[APP] GPIO %u => %s\n",
       config.gpio_commands[i].pin,
       config.gpio_commands[i].state ? "HIGH" : "LOW");

poderia ser substituído por algo como:

gpio_write(
    config.gpio_commands[i].pin,
    config.gpio_commands[i].state
);

A função gpio_write() seria uma abstração de hardware. Em STM32, ela poderia chamar HAL_GPIO_WritePin(). Em ESP32, poderia chamar gpio_set_level(). Em RP2040, poderia chamar gpio_put(). Como queremos manter o tutorial genérico, deixamos a operação representada de forma abstrata.

Um detalhe fundamental é que arrays em JSON podem crescer rapidamente. Por isso, em firmware, sempre devemos aplicar limites explícitos. Neste exemplo, usamos:

#define APP_MAX_GPIO_COMMANDS 8

Essa constante protege o sistema contra mensagens grandes demais. Mesmo que o servidor envie cinquenta comandos, o firmware só aceitará os oito primeiros válidos. Essa escolha evita estouro de buffer e mantém o uso de memória previsível.

Nesta seção, vimos como ler um array de objetos. Esse é um dos usos mais importantes de JSON em IoT, porque permite que um servidor envie listas de instruções para um dispositivo embarcado.

0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x