mirror of
https://github.com/espressif/esp-matter.git
synced 2026-04-27 19:13:13 +00:00
Merge branch 'feat/decode_command_response_as_json' into 'main'
components/esp_matter_controller: use generic TLV to JSON for command responses decoding See merge request app-frameworks/esp-matter!1488
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
set(SRC_DIRS_LIST "."
|
||||
"utils"
|
||||
"utils/jsontlv"
|
||||
"${MATTER_SDK_PATH}/zzz_generated/app-common/app-common/zap-generated/attributes"
|
||||
)
|
||||
|
||||
set(INCLUDE_DIRS_LIST "."
|
||||
"utils"
|
||||
"utils/jsontlv"
|
||||
"data_model_provider"
|
||||
"${MATTER_SDK_PATH}/zzz_generated/app-common"
|
||||
"${MATTER_SDK_PATH}/third_party/nlfaultinjection/include"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
list(APPEND srcs_list "attribute_get_val.cpp")
|
||||
list(APPEND srcs_list "attribute_get_val_type.cpp")
|
||||
list(APPEND srcs_list "attribute_report.cpp")
|
||||
list(APPEND srcs_list "jsontlv.cpp")
|
||||
|
||||
idf_component_register(SRCS ${srcs_list}
|
||||
INCLUDE_DIRS "."
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <esp_err.h>
|
||||
#include <json_to_tlv.h>
|
||||
#include <lib/core/TLV.h>
|
||||
#include <tlv_to_json.h>
|
||||
#include <unity.h>
|
||||
|
||||
static constexpr size_t k_tlv_buffer_size = 1024;
|
||||
|
||||
static esp_err_t roundtrip_json_tree(const char *input_json, cJSON **output_json)
|
||||
{
|
||||
if (output_json == nullptr) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
*output_json = nullptr;
|
||||
|
||||
uint8_t buffer[k_tlv_buffer_size] = { 0 };
|
||||
chip::TLV::TLVWriter writer;
|
||||
writer.Init(buffer, sizeof(buffer));
|
||||
|
||||
esp_err_t err = esp_matter::json_to_tlv(input_json, writer, chip::TLV::AnonymousTag());
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
chip::TLV::TLVReader reader;
|
||||
reader.Init(buffer, writer.GetLengthWritten());
|
||||
return esp_matter::tlv_to_json(reader, output_json);
|
||||
}
|
||||
|
||||
static void expect_roundtrip(const char *input_json, const char *expected_json)
|
||||
{
|
||||
cJSON *actual_json = nullptr;
|
||||
cJSON *expected_json_tree = cJSON_Parse(expected_json);
|
||||
TEST_ASSERT_NOT_NULL(expected_json_tree);
|
||||
|
||||
esp_err_t err = roundtrip_json_tree(input_json, &actual_json);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_NOT_NULL(actual_json);
|
||||
|
||||
char *actual_printed = cJSON_PrintUnformatted(actual_json);
|
||||
char *expected_printed = cJSON_PrintUnformatted(expected_json_tree);
|
||||
TEST_ASSERT_NOT_NULL(actual_printed);
|
||||
TEST_ASSERT_NOT_NULL(expected_printed);
|
||||
TEST_ASSERT_EQUAL_STRING(expected_printed, actual_printed);
|
||||
|
||||
cJSON_free(actual_printed);
|
||||
cJSON_free(expected_printed);
|
||||
cJSON_Delete(actual_json);
|
||||
cJSON_Delete(expected_json_tree);
|
||||
}
|
||||
|
||||
static void expect_json_to_tlv_failure(const char *input_json)
|
||||
{
|
||||
uint8_t buffer[k_tlv_buffer_size] = { 0 };
|
||||
chip::TLV::TLVWriter writer;
|
||||
writer.Init(buffer, sizeof(buffer));
|
||||
|
||||
esp_err_t err = esp_matter::json_to_tlv(input_json, writer, chip::TLV::AnonymousTag());
|
||||
TEST_ASSERT_TRUE(err != ESP_OK);
|
||||
}
|
||||
|
||||
TEST_CASE("jsontlv roundtrip scalar values", "[jsontlv][roundtrip]")
|
||||
{
|
||||
expect_roundtrip(
|
||||
R"({"5:STR":"chip","2:I16":-1234,"4:BOOL":true,"1:U8":42,"3:NULL":null})",
|
||||
R"({"1:U8":42,"2:I16":-1234,"3:NULL":null,"4:BOOL":true,"5:STR":"chip"})");
|
||||
|
||||
expect_roundtrip(
|
||||
R"({"2:OBJ":{"4:BOOL":false,"1:U8":7},"1:ARR-U8":[3,1,2]})",
|
||||
R"({"1:ARR-U8":[3,1,2],"2:OBJ":{"1:U8":7,"4:BOOL":false}})");
|
||||
|
||||
expect_roundtrip(
|
||||
R"({"1:BYT":"AQID","2:FP":"INF","3:DFP":"-INF"})",
|
||||
R"({"1:BYT":"AQID","2:FP":"INF","3:DFP":"-INF"})");
|
||||
|
||||
expect_roundtrip(
|
||||
R"({"1:I64":"-1234567890123456789","2:U64":"12345678901234567890"})",
|
||||
R"({"1:I64":"-1234567890123456789","2:U64":"12345678901234567890"})");
|
||||
}
|
||||
|
||||
TEST_CASE("jsontlv roundtrip container and ordering cases", "[jsontlv][roundtrip]")
|
||||
{
|
||||
expect_roundtrip(
|
||||
R"({"1:ARR-?":[]})",
|
||||
R"({"1:ARR-?":[]})");
|
||||
|
||||
expect_roundtrip(
|
||||
R"({"255:U16":65535,"1:U8":1})",
|
||||
R"({"1:U8":1,"255:U16":65535})");
|
||||
|
||||
expect_roundtrip(
|
||||
R"({"9:OBJ":{"3:STR":"c","1:STR":"a","2:STR":"b"},"2:U8":2,"1:U8":1})",
|
||||
R"({"1:U8":1,"2:U8":2,"9:OBJ":{"1:STR":"a","2:STR":"b","3:STR":"c"}})");
|
||||
|
||||
expect_roundtrip(
|
||||
R"({"7:ARR-STR":[]})",
|
||||
R"({"7:ARR-?":[]})");
|
||||
}
|
||||
|
||||
TEST_CASE("jsontlv roundtrip numeric float values", "[jsontlv][roundtrip]")
|
||||
{
|
||||
cJSON *json = nullptr;
|
||||
esp_err_t err = roundtrip_json_tree(R"({"1:FP":1.5,"2:DFP":-2.25})", &json);
|
||||
TEST_ASSERT_EQUAL(ESP_OK, err);
|
||||
TEST_ASSERT_NOT_NULL(json);
|
||||
|
||||
cJSON *float_value = cJSON_GetObjectItemCaseSensitive(json, "1:FP");
|
||||
TEST_ASSERT_NOT_NULL(float_value);
|
||||
TEST_ASSERT_TRUE(cJSON_IsNumber(float_value));
|
||||
TEST_ASSERT_DOUBLE_WITHIN(0.0001, 1.5, float_value->valuedouble);
|
||||
|
||||
cJSON *double_value = cJSON_GetObjectItemCaseSensitive(json, "2:DFP");
|
||||
TEST_ASSERT_NOT_NULL(double_value);
|
||||
TEST_ASSERT_TRUE(cJSON_IsNumber(double_value));
|
||||
TEST_ASSERT_DOUBLE_WITHIN(0.0001, -2.25, double_value->valuedouble);
|
||||
|
||||
cJSON_Delete(json);
|
||||
}
|
||||
|
||||
TEST_CASE("jsontlv rejects invalid structure and type inputs", "[jsontlv][invalid]")
|
||||
{
|
||||
expect_json_to_tlv_failure(R"({"1:ARR":[1,2,3]})");
|
||||
expect_json_to_tlv_failure(R"({"1:BYT":"not-base64"})");
|
||||
expect_json_to_tlv_failure(R"({"1":42})");
|
||||
expect_json_to_tlv_failure(R"({"1:BOOL":"true"})");
|
||||
expect_json_to_tlv_failure(R"({"1:FP":"NaN"})");
|
||||
expect_json_to_tlv_failure(R"({"1:I64":"123aaa"})");
|
||||
expect_json_to_tlv_failure(R"({"1:I64":"aaa123"})");
|
||||
expect_json_to_tlv_failure(R"({"1:U64":"123aaa"})");
|
||||
expect_json_to_tlv_failure(R"({"1:U64":"aaa123"})");
|
||||
}
|
||||
|
||||
TEST_CASE("jsontlv rejects invalid numeric boundaries", "[jsontlv][invalid]")
|
||||
{
|
||||
expect_json_to_tlv_failure(R"({"1:U8":256})");
|
||||
expect_json_to_tlv_failure(R"({"1:U32":-1})");
|
||||
expect_json_to_tlv_failure(R"({"1:I8":128})");
|
||||
expect_json_to_tlv_failure(R"({"4294967295:U8":1})");
|
||||
expect_json_to_tlv_failure(R"({"1:U32":1.5})");
|
||||
expect_json_to_tlv_failure(R"({"1:I32":2147483648})");
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2026 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace esp_matter {
|
||||
|
||||
namespace element_type {
|
||||
// Supported Data Type
|
||||
const char k_int8[] = "I8";
|
||||
const char k_int16[] = "I16";
|
||||
const char k_int32[] = "I32";
|
||||
const char k_int64[] = "I64";
|
||||
const char k_uint8[] = "U8";
|
||||
const char k_uint16[] = "U16";
|
||||
const char k_uint32[] = "U32";
|
||||
const char k_uint64[] = "U64";
|
||||
const char k_bool[] = "BOOL";
|
||||
const char k_float[] = "FP";
|
||||
const char k_double[] = "DFP";
|
||||
const char k_bytes[] = "BYT";
|
||||
const char k_string[] = "STR";
|
||||
const char k_null[] = "NULL";
|
||||
const char k_object[] = "OBJ";
|
||||
const char k_array[] = "ARR";
|
||||
const char k_empty[] = "?";
|
||||
|
||||
const char k_floating_point_positive_infinity[] = "INF";
|
||||
const char k_floating_point_negative_infinity[] = "-INF";
|
||||
} // namespace element_type
|
||||
|
||||
} // namespace esp_matter
|
||||
+29
-5
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Espressif Systems (Shanghai) PTE LTD
|
||||
// Copyright 2023-2026 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cJSON.h>
|
||||
#include <element_types.h>
|
||||
#include <esp_check.h>
|
||||
#include <esp_matter_mem.h>
|
||||
#include <json_to_tlv.h>
|
||||
@@ -79,6 +80,14 @@ static bool is_unsigned_integer(const char *str, size_t len)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_integral_json_number(const cJSON *val)
|
||||
{
|
||||
if (val == nullptr || val->type != cJSON_Number) {
|
||||
return false;
|
||||
}
|
||||
return static_cast<double>(val->valueint) == val->valuedouble;
|
||||
}
|
||||
|
||||
static esp_err_t type_str_to_tlv_element_type(const char *type_str, size_t len, TLVElementType &type)
|
||||
{
|
||||
if (len == strlen(element_type::k_int8) && strncmp(type_str, element_type::k_int8, len) == 0) {
|
||||
@@ -254,6 +263,9 @@ static esp_err_t encode_tlv_element(const cJSON *val, TLV::TLVWriter &writer, co
|
||||
}
|
||||
case TLVElementType::Int32: {
|
||||
ESP_RETURN_ON_FALSE(val->type == cJSON_Number, ESP_ERR_INVALID_ARG, TAG, "Invalid type");
|
||||
ESP_RETURN_ON_FALSE(is_integral_json_number(val), ESP_ERR_INVALID_ARG, TAG, "Invalid value");
|
||||
ESP_RETURN_ON_FALSE(val->valuedouble <= INT32_MAX && val->valuedouble >= INT32_MIN, ESP_ERR_INVALID_ARG, TAG,
|
||||
"Invalid range");
|
||||
int32_t int32_val = val->valueint;
|
||||
ESP_RETURN_ON_FALSE(writer.Put(tag, int32_val) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to encode");
|
||||
break;
|
||||
@@ -266,7 +278,12 @@ static esp_err_t encode_tlv_element(const cJSON *val, TLV::TLVWriter &writer, co
|
||||
int64_val =
|
||||
(val->valueint < INT32_MAX && val->valueint > INT32_MIN) ? val->valueint : (int64_t)val->valuedouble;
|
||||
} else {
|
||||
int64_val = strtoll(val->valuestring, nullptr, 10);
|
||||
ESP_RETURN_ON_FALSE(val->valuestring && val->valuestring[0] != '\0', ESP_ERR_INVALID_ARG, TAG,
|
||||
"Invalid int64 string");
|
||||
char *end = nullptr;
|
||||
int64_val = strtoll(val->valuestring, &end, 10);
|
||||
ESP_RETURN_ON_FALSE(end != val->valuestring && end && *end == '\0', ESP_ERR_INVALID_ARG, TAG,
|
||||
"Invalid int64 string");
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(writer.Put(tag, int64_val) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to encode");
|
||||
break;
|
||||
@@ -289,8 +306,10 @@ static esp_err_t encode_tlv_element(const cJSON *val, TLV::TLVWriter &writer, co
|
||||
}
|
||||
case TLVElementType::UInt32: {
|
||||
ESP_RETURN_ON_FALSE(val->type == cJSON_Number, ESP_ERR_INVALID_ARG, TAG, "Invalid type");
|
||||
ESP_RETURN_ON_FALSE(val->valueint >= 0, ESP_ERR_INVALID_ARG, TAG, "Invalid range");
|
||||
uint32_t uint32_val = val->valueint < INT32_MAX ? val->valueint : (uint32_t)val->valuedouble;
|
||||
ESP_RETURN_ON_FALSE(is_integral_json_number(val), ESP_ERR_INVALID_ARG, TAG, "Invalid value");
|
||||
ESP_RETURN_ON_FALSE(val->valuedouble >= 0 && val->valuedouble <= UINT32_MAX, ESP_ERR_INVALID_ARG, TAG,
|
||||
"Invalid range");
|
||||
uint32_t uint32_val = static_cast<uint32_t>(val->valuedouble);
|
||||
ESP_RETURN_ON_FALSE(writer.Put(tag, uint32_val) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to encode");
|
||||
break;
|
||||
}
|
||||
@@ -302,7 +321,12 @@ static esp_err_t encode_tlv_element(const cJSON *val, TLV::TLVWriter &writer, co
|
||||
ESP_RETURN_ON_FALSE(val->valueint >= 0, ESP_ERR_INVALID_ARG, TAG, "Invalid range");
|
||||
uint64_val = val->valueint < INT32_MAX ? val->valueint : (uint64_t)val->valuedouble;
|
||||
} else {
|
||||
uint64_val = strtoull(val->valuestring, nullptr, 10);
|
||||
ESP_RETURN_ON_FALSE(val->valuestring && val->valuestring[0] != '\0', ESP_ERR_INVALID_ARG, TAG,
|
||||
"Invalid uint64 string");
|
||||
char *end = nullptr;
|
||||
uint64_val = strtoull(val->valuestring, &end, 10);
|
||||
ESP_RETURN_ON_FALSE(end != val->valuestring && end && *end == '\0', ESP_ERR_INVALID_ARG, TAG,
|
||||
"Invalid uint64 string");
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(writer.Put(tag, uint64_val) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to encode");
|
||||
break;
|
||||
+1
-26
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 Espressif Systems (Shanghai) PTE LTD
|
||||
// Copyright 2023-2026 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -17,34 +17,9 @@
|
||||
#include "cJSON.h"
|
||||
#include <esp_err.h>
|
||||
#include <lib/core/TLV.h>
|
||||
#include <string>
|
||||
|
||||
namespace esp_matter {
|
||||
|
||||
namespace element_type {
|
||||
// Supported Data Type
|
||||
const char k_int8[] = "I8";
|
||||
const char k_int16[] = "I16";
|
||||
const char k_int32[] = "I32";
|
||||
const char k_int64[] = "I64";
|
||||
const char k_uint8[] = "U8";
|
||||
const char k_uint16[] = "U16";
|
||||
const char k_uint32[] = "U32";
|
||||
const char k_uint64[] = "U64";
|
||||
const char k_bool[] = "BOOL";
|
||||
const char k_float[] = "FP";
|
||||
const char k_double[] = "DFP";
|
||||
const char k_bytes[] = "BYT";
|
||||
const char k_string[] = "STR";
|
||||
const char k_null[] = "NULL";
|
||||
const char k_object[] = "OBJ";
|
||||
const char k_array[] = "ARR";
|
||||
const char k_empty[] = "?";
|
||||
|
||||
const char k_floating_point_positive_infinity[] = "INF";
|
||||
const char k_floating_point_negative_infinity[] = "-INF";
|
||||
} // namespace element_type
|
||||
|
||||
/** Convert a JSON object to the given TLVWriter
|
||||
*
|
||||
* @param[in] json_str The JSON string that represents a TLV structure
|
||||
@@ -0,0 +1,416 @@
|
||||
// Copyright 2026 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <cmath>
|
||||
#include <element_types.h>
|
||||
#include <esp_check.h>
|
||||
#include <esp_matter_mem.h>
|
||||
#include <tlv_to_json.h>
|
||||
#include <lib/support/Base64.h>
|
||||
|
||||
#include "support/CodeUtils.h"
|
||||
|
||||
using namespace chip;
|
||||
using chip::TLV::TLVElementType;
|
||||
|
||||
constexpr char TAG[] = "TlvToJson";
|
||||
|
||||
namespace esp_matter {
|
||||
|
||||
static TLVElementType get_tlv_element_type(const TLV::TLVReader &reader)
|
||||
{
|
||||
return static_cast<TLVElementType>(reader.GetControlByte() & TLV::kTLVTypeMask);
|
||||
}
|
||||
|
||||
static esp_err_t tlv_element_type_to_type_str(TLVElementType type, const char **type_str)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(type_str, ESP_ERR_INVALID_ARG, TAG, "type_str cannot be NULL");
|
||||
switch (type) {
|
||||
case TLVElementType::Int8:
|
||||
*type_str = element_type::k_int8;
|
||||
return ESP_OK;
|
||||
case TLVElementType::Int16:
|
||||
*type_str = element_type::k_int16;
|
||||
return ESP_OK;
|
||||
case TLVElementType::Int32:
|
||||
*type_str = element_type::k_int32;
|
||||
return ESP_OK;
|
||||
case TLVElementType::Int64:
|
||||
*type_str = element_type::k_int64;
|
||||
return ESP_OK;
|
||||
case TLVElementType::UInt8:
|
||||
*type_str = element_type::k_uint8;
|
||||
return ESP_OK;
|
||||
case TLVElementType::UInt16:
|
||||
*type_str = element_type::k_uint16;
|
||||
return ESP_OK;
|
||||
case TLVElementType::UInt32:
|
||||
*type_str = element_type::k_uint32;
|
||||
return ESP_OK;
|
||||
case TLVElementType::UInt64:
|
||||
*type_str = element_type::k_uint64;
|
||||
return ESP_OK;
|
||||
case TLVElementType::BooleanTrue:
|
||||
case TLVElementType::BooleanFalse:
|
||||
*type_str = element_type::k_bool;
|
||||
return ESP_OK;
|
||||
case TLVElementType::FloatingPointNumber32:
|
||||
*type_str = element_type::k_float;
|
||||
return ESP_OK;
|
||||
case TLVElementType::FloatingPointNumber64:
|
||||
*type_str = element_type::k_double;
|
||||
return ESP_OK;
|
||||
case TLVElementType::UTF8String_1ByteLength:
|
||||
case TLVElementType::UTF8String_2ByteLength:
|
||||
case TLVElementType::UTF8String_4ByteLength:
|
||||
case TLVElementType::UTF8String_8ByteLength:
|
||||
*type_str = element_type::k_string;
|
||||
return ESP_OK;
|
||||
case TLVElementType::ByteString_1ByteLength:
|
||||
case TLVElementType::ByteString_2ByteLength:
|
||||
case TLVElementType::ByteString_4ByteLength:
|
||||
case TLVElementType::ByteString_8ByteLength:
|
||||
*type_str = element_type::k_bytes;
|
||||
return ESP_OK;
|
||||
case TLVElementType::Null:
|
||||
*type_str = element_type::k_null;
|
||||
return ESP_OK;
|
||||
case TLVElementType::Structure:
|
||||
*type_str = element_type::k_object;
|
||||
return ESP_OK;
|
||||
case TLVElementType::Array:
|
||||
*type_str = element_type::k_array;
|
||||
return ESP_OK;
|
||||
case TLVElementType::NotSpecified:
|
||||
*type_str = element_type::k_empty;
|
||||
return ESP_OK;
|
||||
default:
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
};
|
||||
}
|
||||
|
||||
static esp_err_t create_json_name(TLV::Tag tag, TLVElementType type, TLVElementType sub_type, char *json_name,
|
||||
size_t json_name_size)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(tag != TLV::AnonymousTag(), ESP_ERR_INVALID_ARG, TAG, "Anonymous tag is not supported");
|
||||
ESP_RETURN_ON_FALSE(json_name, ESP_ERR_INVALID_ARG, TAG, "json name cannot be NULL");
|
||||
|
||||
const char *type_str = nullptr;
|
||||
ESP_RETURN_ON_ERROR(tlv_element_type_to_type_str(type, &type_str), TAG, "Unsupported tlv element type");
|
||||
|
||||
uint64_t tag_number = TLV::TagNumFromTag(tag);
|
||||
if (type == TLVElementType::Array) {
|
||||
const char *sub_type_str = nullptr;
|
||||
ESP_RETURN_ON_ERROR(tlv_element_type_to_type_str(sub_type, &sub_type_str), TAG, "Unsupported array subtype");
|
||||
snprintf(json_name, json_name_size, "%" PRIu64 ":%s-%s", tag_number, type_str, sub_type_str);
|
||||
} else {
|
||||
snprintf(json_name, json_name_size, "%" PRIu64 ":%s", tag_number, type_str);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t encode_integer_string(int64_t value, cJSON **json)
|
||||
{
|
||||
char value_str[32] = { 0 };
|
||||
snprintf(value_str, sizeof(value_str), "%" PRId64, value);
|
||||
*json = cJSON_CreateString(value_str);
|
||||
ESP_RETURN_ON_FALSE(*json, ESP_ERR_NO_MEM, TAG, "No memory");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t encode_unsigned_integer_string(uint64_t value, cJSON **json)
|
||||
{
|
||||
char value_str[32] = { 0 };
|
||||
snprintf(value_str, sizeof(value_str), "%" PRIu64, value);
|
||||
*json = cJSON_CreateString(value_str);
|
||||
ESP_RETURN_ON_FALSE(*json, ESP_ERR_NO_MEM, TAG, "No memory");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t encode_byte_string(TLV::TLVReader &reader, cJSON **json)
|
||||
{
|
||||
ByteSpan value;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read byte string");
|
||||
|
||||
char *value_str = static_cast<char *>(esp_matter_mem_calloc(BASE64_ENCODED_LEN(value.size()) + 1, sizeof(char)));
|
||||
ESP_RETURN_ON_FALSE(value_str, ESP_ERR_NO_MEM, TAG, "No memory");
|
||||
Base64Encode(value.data(), static_cast<uint16_t>(value.size()), value_str);
|
||||
|
||||
*json = cJSON_CreateString(value_str);
|
||||
esp_matter_mem_free(value_str);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t encode_utf8_string(TLV::TLVReader &reader, cJSON **json)
|
||||
{
|
||||
ByteSpan value;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read byte string");
|
||||
|
||||
char *value_str = static_cast<char *>(esp_matter_mem_calloc(value.size() + 1, sizeof(char)));
|
||||
ESP_RETURN_ON_FALSE(value_str, ESP_ERR_NO_MEM, TAG, "No memory");
|
||||
memcpy(value_str, value.data(), value.size());
|
||||
|
||||
*json = cJSON_CreateString(value_str);
|
||||
esp_matter_mem_free(value_str);
|
||||
ESP_RETURN_ON_FALSE(*json, ESP_ERR_NO_MEM, TAG, "No memory");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t encode_floating_point(float value, cJSON **json)
|
||||
{
|
||||
if (std::isinf(value)) {
|
||||
*json = cJSON_CreateString(value > 0 ? element_type::k_floating_point_positive_infinity
|
||||
: element_type::k_floating_point_negative_infinity);
|
||||
} else {
|
||||
*json = cJSON_CreateNumber(value);
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(*json, ESP_ERR_NO_MEM, TAG, "No memory");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t encode_floating_point(double value, cJSON **json)
|
||||
{
|
||||
if (std::isinf(value)) {
|
||||
*json = cJSON_CreateString(value > 0 ? element_type::k_floating_point_positive_infinity
|
||||
: element_type::k_floating_point_negative_infinity);
|
||||
} else {
|
||||
*json = cJSON_CreateNumber(value);
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(*json, ESP_ERR_NO_MEM, TAG, "No memory");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t encode_tlv_node(TLV::TLVReader &reader, cJSON **json, TLVElementType &sub_type);
|
||||
|
||||
static esp_err_t encode_tlv_object(TLV::TLVReader &reader, cJSON **json)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
TLV::TLVType container_type;
|
||||
cJSON *json_obj = cJSON_CreateObject();
|
||||
cJSON *json_obj_child = nullptr;
|
||||
bool container_opened = false;
|
||||
CHIP_ERROR err = CHIP_NO_ERROR;
|
||||
|
||||
ESP_GOTO_ON_FALSE(json_obj, ESP_ERR_NO_MEM, cleanup, TAG, "No memory");
|
||||
err = reader.EnterContainer(container_type);
|
||||
ESP_GOTO_ON_FALSE(err == CHIP_NO_ERROR, ESP_FAIL, cleanup, TAG, "Failed to enter container: %" CHIP_ERROR_FORMAT, err.Format());
|
||||
container_opened = true;
|
||||
|
||||
while ((err = reader.Next()) == CHIP_NO_ERROR) {
|
||||
TLV::Tag child_tag = reader.GetTag();
|
||||
TLVElementType child_type = get_tlv_element_type(reader);
|
||||
TLVElementType child_sub_type = TLVElementType::NotSpecified;
|
||||
ESP_GOTO_ON_ERROR(encode_tlv_node(reader, &json_obj_child, child_sub_type), cleanup, TAG, "Failed to encode tlv node");
|
||||
|
||||
char json_name[64] = { 0 };
|
||||
ESP_GOTO_ON_ERROR(create_json_name(child_tag, child_type, child_sub_type, json_name, sizeof(json_name)),
|
||||
cleanup, TAG, "Failed to create json name");
|
||||
|
||||
cJSON_AddItemToObject(json_obj, json_name, json_obj_child);
|
||||
json_obj_child = nullptr;
|
||||
}
|
||||
ESP_GOTO_ON_FALSE(err == CHIP_END_OF_TLV, ESP_FAIL, cleanup, TAG, "Failed to iterate container: %" CHIP_ERROR_FORMAT, err.Format());
|
||||
|
||||
err = reader.ExitContainer(container_type);
|
||||
ESP_GOTO_ON_FALSE(err == CHIP_NO_ERROR, ESP_FAIL, cleanup, TAG, "Failed to exit container: %" CHIP_ERROR_FORMAT, err.Format());
|
||||
container_opened = false;
|
||||
|
||||
*json = json_obj;
|
||||
json_obj = nullptr;
|
||||
|
||||
cleanup:
|
||||
cJSON_Delete(json_obj_child);
|
||||
cJSON_Delete(json_obj);
|
||||
|
||||
if (container_opened) {
|
||||
err = reader.ExitContainer(container_type);
|
||||
if (err != CHIP_NO_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to exit container: %" CHIP_ERROR_FORMAT, err.Format());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t encode_tlv_array(TLV::TLVReader &reader, cJSON **json, TLVElementType &sub_type)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
TLV::TLVType container_type;
|
||||
cJSON *json_array = cJSON_CreateArray();
|
||||
cJSON *json_array_child = nullptr;
|
||||
CHIP_ERROR err = CHIP_NO_ERROR;
|
||||
bool container_opened = false;
|
||||
|
||||
ESP_GOTO_ON_FALSE(json_array, ESP_ERR_NO_MEM, cleanup, TAG, "No memory");
|
||||
err = reader.EnterContainer(container_type);
|
||||
ESP_GOTO_ON_FALSE(err == CHIP_NO_ERROR, ESP_FAIL, cleanup, TAG, "Failed to enter container: %" CHIP_ERROR_FORMAT,
|
||||
err.Format());
|
||||
container_opened = true;
|
||||
|
||||
sub_type = TLVElementType::NotSpecified;
|
||||
while ((err = reader.Next()) == CHIP_NO_ERROR) {
|
||||
TLVElementType child_sub_type = TLVElementType::NotSpecified;
|
||||
ESP_GOTO_ON_ERROR(encode_tlv_node(reader, &json_array_child, child_sub_type), cleanup, TAG, "Failed to encode tlv node");
|
||||
|
||||
if (sub_type == TLVElementType::NotSpecified) {
|
||||
sub_type = get_tlv_element_type(reader);
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(json_array, json_array_child);
|
||||
json_array_child = nullptr;
|
||||
}
|
||||
ESP_GOTO_ON_FALSE(err == CHIP_END_OF_TLV, ESP_FAIL, cleanup, TAG, "Failed to iterate container: %" CHIP_ERROR_FORMAT,
|
||||
err.Format());
|
||||
|
||||
err = reader.ExitContainer(container_type);
|
||||
ESP_GOTO_ON_FALSE(err == CHIP_NO_ERROR, ESP_FAIL, cleanup, TAG, "Failed to exit container: %" CHIP_ERROR_FORMAT, err.Format());
|
||||
container_opened = false;
|
||||
|
||||
*json = json_array;
|
||||
json_array = nullptr;
|
||||
|
||||
cleanup:
|
||||
cJSON_Delete(json_array_child);
|
||||
cJSON_Delete(json_array);
|
||||
if (container_opened) {
|
||||
err = reader.ExitContainer(container_type);
|
||||
if (err != CHIP_NO_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to exit container: %" CHIP_ERROR_FORMAT, err.Format());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t encode_tlv_node(TLV::TLVReader &reader, cJSON **json, TLVElementType &sub_type)
|
||||
{
|
||||
sub_type = TLVElementType::NotSpecified;
|
||||
|
||||
switch (get_tlv_element_type(reader)) {
|
||||
case TLVElementType::Int8: {
|
||||
int8_t value = 0;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read int8");
|
||||
*json = cJSON_CreateNumber(value);
|
||||
break;
|
||||
}
|
||||
case TLVElementType::Int16: {
|
||||
int16_t value = 0;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read int16");
|
||||
*json = cJSON_CreateNumber(value);
|
||||
break;
|
||||
}
|
||||
case TLVElementType::Int32: {
|
||||
int32_t value = 0;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read int32");
|
||||
*json = cJSON_CreateNumber(value);
|
||||
break;
|
||||
}
|
||||
case TLVElementType::Int64: {
|
||||
int64_t value = 0;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read int64");
|
||||
return encode_integer_string(value, json);
|
||||
}
|
||||
case TLVElementType::UInt8: {
|
||||
uint8_t value = 0;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read uint8");
|
||||
*json = cJSON_CreateNumber(value);
|
||||
break;
|
||||
}
|
||||
case TLVElementType::UInt16: {
|
||||
uint16_t value = 0;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read uint16");
|
||||
*json = cJSON_CreateNumber(value);
|
||||
break;
|
||||
}
|
||||
case TLVElementType::UInt32: {
|
||||
uint32_t value = 0;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read uint32");
|
||||
*json = cJSON_CreateNumber(value);
|
||||
break;
|
||||
}
|
||||
case TLVElementType::UInt64: {
|
||||
uint64_t value = 0;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read uint64");
|
||||
return encode_unsigned_integer_string(value, json);
|
||||
}
|
||||
case TLVElementType::BooleanFalse:
|
||||
case TLVElementType::BooleanTrue: {
|
||||
bool value = false;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read bool");
|
||||
*json = cJSON_CreateBool(value);
|
||||
break;
|
||||
}
|
||||
case TLVElementType::FloatingPointNumber32: {
|
||||
float value = 0;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read float");
|
||||
return encode_floating_point(value, json);
|
||||
}
|
||||
case TLVElementType::FloatingPointNumber64: {
|
||||
double value = 0;
|
||||
ESP_RETURN_ON_FALSE(reader.Get(value) == CHIP_NO_ERROR, ESP_FAIL, TAG, "Failed to read double");
|
||||
return encode_floating_point(value, json);
|
||||
}
|
||||
case TLVElementType::UTF8String_1ByteLength:
|
||||
case TLVElementType::UTF8String_2ByteLength:
|
||||
case TLVElementType::UTF8String_4ByteLength:
|
||||
case TLVElementType::UTF8String_8ByteLength:
|
||||
return encode_utf8_string(reader, json);
|
||||
case TLVElementType::ByteString_1ByteLength:
|
||||
case TLVElementType::ByteString_2ByteLength:
|
||||
case TLVElementType::ByteString_4ByteLength:
|
||||
case TLVElementType::ByteString_8ByteLength:
|
||||
return encode_byte_string(reader, json);
|
||||
case TLVElementType::Null:
|
||||
*json = cJSON_CreateNull();
|
||||
break;
|
||||
case TLVElementType::Structure:
|
||||
return encode_tlv_object(reader, json);
|
||||
case TLVElementType::Array:
|
||||
case TLVElementType::List:
|
||||
return encode_tlv_array(reader, json, sub_type);
|
||||
default:
|
||||
ESP_LOGE(TAG, "Unsupported tlv element type: %d", static_cast<int>(get_tlv_element_type(reader)));
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_FALSE(*json, ESP_ERR_NO_MEM, TAG, "No memory");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t tlv_to_json(TLV::TLVReader &reader, cJSON **json)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(json, ESP_ERR_INVALID_ARG, TAG, "json cannot be NULL");
|
||||
|
||||
*json = nullptr;
|
||||
|
||||
TLV::TLVReader reader_copy;
|
||||
reader_copy.Init(reader);
|
||||
|
||||
if (reader_copy.GetType() == TLV::kTLVType_NotSpecified) {
|
||||
CHIP_ERROR chip_err = reader_copy.Next();
|
||||
if (chip_err != CHIP_NO_ERROR) {
|
||||
ESP_LOGE(TAG, "Failed to move tlv reader: %" CHIP_ERROR_FORMAT, chip_err.Format());
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
TLVElementType sub_type = TLVElementType::NotSpecified;
|
||||
esp_err_t err = encode_tlv_node(reader_copy, json, sub_type);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
} // namespace esp_matter
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2026 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <esp_err.h>
|
||||
#include <lib/core/TLV.h>
|
||||
|
||||
namespace esp_matter {
|
||||
|
||||
/** Convert TLV data model payload to cJSON.
|
||||
*
|
||||
* @param[in] reader The TLV reader positioned at the payload.
|
||||
* @param[out] json The JSON object output.
|
||||
*
|
||||
* @return ESP_OK on success.
|
||||
* @return error in case of failure.
|
||||
*/
|
||||
esp_err_t tlv_to_json(chip::TLV::TLVReader &reader, cJSON **json);
|
||||
|
||||
} // namespace esp_matter
|
||||
Reference in New Issue
Block a user