Interpretando uma resposta JSON recebida do servidor
Agora vamos fazer o caminho inverso: em vez de apenas criar uma mensagem JSON, vamos receber uma resposta em JSON e extrair seus campos usando a biblioteca cJSON.
Em um sistema embarcado real, essa resposta poderia vir de uma requisição HTTP, de uma mensagem MQTT, de uma comunicação UART, de BLE, LoRa, Ethernet ou modem celular. Neste tutorial, para manter tudo genérico, vamos simular o servidor com uma função mock.
Imagine que o microcontrolador enviou a telemetria e o servidor respondeu:
{
"status": "ok",
"command": {
"relay": true,
"sample_interval_ms": 5000
},
"thresholds": {
"temperature_max": 60.0,
"voltage_min": 3.0
}
}
Essa resposta também possui hierarquia de dois níveis. No primeiro nível temos status, command e thresholds. Dentro de command, temos os campos relay e sample_interval_ms. Dentro de thresholds, temos os limites de temperatura e tensão.
Visualmente:
root
├── status
├── command
│ ├── relay
│ └── sample_interval_ms
└── thresholds
├── temperature_max
└── voltage_min
Vamos começar criando um módulo mock para simular o serviço web.
web_mock.h
#ifndef WEB_MOCK_H
#define WEB_MOCK_H
/**
* @brief Simula o envio de uma mensagem JSON para um serviço web.
*
* Em um microcontrolador real, esta função poderia executar um HTTP POST,
* publicar em MQTT ou enviar dados por outro protocolo.
*
* @param request_json Mensagem JSON enviada pelo dispositivo.
* @return Ponteiro para uma string constante contendo a resposta JSON simulada.
*/
const char *web_mock_post(const char *request_json);
#endif
web_mock.c
#include <stdio.h>
#include "web_mock.h"
/**
* @brief Simula um servidor web recebendo JSON e retornando comandos.
*
* Esta função não faz comunicação real. Ela apenas imprime a requisição
* recebida e retorna uma resposta JSON fixa.
*
* @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"
"}"
"}";
}
Agora precisamos criar uma estrutura em C para armazenar os dados recebidos do servidor. Isso é importante porque o firmware não deve depender do objeto cJSON durante toda a execução. O ideal é interpretar o JSON, copiar os dados importantes para uma estrutura própria e liberar a árvore JSON.
app_config.h
#ifndef APP_CONFIG_H
#define APP_CONFIG_H
#include <stdbool.h>
#include <stdint.h>
/**
* @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;
} AppConfig;
#endif
Agora vamos criar um parser, ou seja, um módulo responsável por interpretar a resposta JSON.
json_parser.h
#ifndef JSON_PARSER_H
#define JSON_PARSER_H
#include <stdbool.h>
#include "app_config.h"
/**
* @brief Interpreta uma resposta JSON do servidor.
*
* @param response_json String contendo o JSON recebido.
* @param config Ponteiro para a estrutura onde os dados serão salvos.
* @return true se o JSON foi interpretado corretamente, false em caso de erro.
*/
bool json_parser_parse_server_response(
const char *response_json,
AppConfig *config
);
#endif
json_parser.c
#include <stdbool.h>
#include <stdint.h>
#include "cJSON.h"
#include "json_parser.h"
/**
* @brief Interpreta uma resposta JSON enviada pelo servidor.
*
* Exemplo esperado:
*
* {
* "status": "ok",
* "command": {
* "relay": true,
* "sample_interval_ms": 5000
* },
* "thresholds": {
* "temperature_max": 60.0,
* "voltage_min": 3.0
* }
* }
*
* @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 (status->valuestring[0] != 'o' || status->valuestring[1] != 'k')
{
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;
cJSON_Delete(root);
return true;
}
Esse código mostra uma prática fundamental: nunca confiar cegamente no JSON recebido. Antes de usar qualquer campo, verificamos se ele existe e se é do tipo esperado.
Por exemplo, antes de usar relay, verificamos:
if (!cJSON_IsBool(relay))
{
cJSON_Delete(root);
return false;
}
Antes de usar sample_interval_ms, verificamos:
if (!cJSON_IsNumber(sample_interval))
{
cJSON_Delete(root);
return false;
}
Isso evita que o firmware leia dados inválidos, acesse ponteiros nulos ou aceite comandos malformados. Em sistemas embarcados, esse cuidado é essencial, porque uma mensagem incorreta não deve travar o dispositivo.
Agora podemos integrar tudo no main.c.
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);
free(request);
return 0;
}
A saída esperada será parecida com:
[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
Observe que a árvore cJSON existe apenas durante a interpretação. Depois que os valores são copiados para AppConfig, chamamos:
cJSON_Delete(root);
Esse é um padrão saudável para firmware: parsear, copiar, liberar.
Também há um detalhe no teste do status. O código acima faz uma verificação simples dos dois primeiros caracteres de "ok". Em um projeto mais rigoroso, usaríamos strcmp():
#include <string.h>
if (strcmp(status->valuestring, "ok") != 0)
{
cJSON_Delete(root);
return false;
}
Essa forma é mais correta, porque garante que o texto seja exatamente "ok" e não apenas algo começando com ok.
Com isso, já temos o ciclo básico completo:
MCU cria JSON
MCU envia para serviço mock
Serviço mock responde JSON
MCU interpreta resposta
MCU aplica configuração recebida