Skip to content

Commit 54c43e9

Browse files
committed
Merge branch 'controller/write_multiple_attributes' into 'main'
components/esp_matter_controller: write multiple attributes support See merge request app-frameworks/esp-matter!979
2 parents cb21664 + 8bf0d71 commit 54c43e9

8 files changed

+224
-27
lines changed

components/esp_matter/esp_matter_client.cpp

+51
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,57 @@ esp_err_t send_request(client::peer_device_t *remote_device, AttributePathParams
531531
return send_request(remote_device, attr_path, type, callback, timeout_ms);
532532
}
533533

534+
esp_err_t send_request(client::peer_device_t *remote_device,
535+
ScopedMemoryBufferWithSize<AttributePathParams> &attr_paths, multiple_write_encodable_type &json_encodable,
536+
WriteClient::Callback &callback, const chip::Optional<uint16_t> &timeout_ms)
537+
{
538+
VerifyOrReturnError(
539+
json_encodable.GetJsonArraySize() == attr_paths.AllocatedSize(), ESP_ERR_INVALID_ARG,
540+
ESP_LOGE(TAG, "The attr_values array length should be the same as the attr_paths array length"));
541+
VerifyOrReturnError(remote_device->GetSecureSession().HasValue() &&
542+
!remote_device->GetSecureSession().Value()->IsGroupSession(),
543+
ESP_ERR_INVALID_ARG, ESP_LOGE(TAG, "Invalid Session Type"));
544+
auto client_deleter_callback = chip::Platform::MakeUnique<client_deleter_write_callback>(callback);
545+
VerifyOrReturnError(client_deleter_callback, ESP_ERR_NO_MEM,
546+
ESP_LOGE(TAG, "Failed to allocate memory for client deleter callback"));
547+
auto write_client = chip::Platform::MakeUnique<WriteClient>(remote_device->GetExchangeManager(),
548+
client_deleter_callback.get(), timeout_ms, false);
549+
VerifyOrReturnError(write_client, ESP_ERR_NO_MEM, ESP_LOGE(TAG, "Failed to allocate memory for WriteClient"));
550+
551+
for (size_t i = 0; i < attr_paths.AllocatedSize(); ++i) {
552+
ConcreteDataAttributePath path(attr_paths[i].mEndpointId, attr_paths[i].mClusterId, attr_paths[i].mAttributeId);
553+
chip::Platform::ScopedMemoryBuffer<uint8_t> encoded_buf;
554+
encoded_buf.Alloc(k_encoded_buf_size);
555+
VerifyOrReturnError((encoded_buf.Get()), ESP_ERR_NO_MEM,
556+
ESP_LOGE(TAG, "Failed to alloc memory for encoded_buf"));
557+
TLVReader reader;
558+
TLVWriter writer;
559+
TLVReader attr_val_reader;
560+
writer.Init(encoded_buf.Get(), k_encoded_buf_size);
561+
VerifyOrReturnError(json_encodable.EncodeTo(writer, chip::TLV::AnonymousTag(), i) == CHIP_NO_ERROR, ESP_FAIL,
562+
ESP_LOGE(TAG, "Failed to encode attribute value"));
563+
VerifyOrReturnError(writer.Finalize() == CHIP_NO_ERROR, ESP_FAIL,
564+
ESP_LOGE(TAG, "Failed to finalize TLV writer"));
565+
reader.Init(encoded_buf.Get(), writer.GetLengthWritten());
566+
VerifyOrReturnError(reader.Next() == CHIP_NO_ERROR, ESP_FAIL, ESP_LOGE(TAG, "Failed to read next"));
567+
VerifyOrReturnError(reader.GetType() == chip::TLV::TLVType::kTLVType_Structure, ESP_ERR_INVALID_ARG,
568+
ESP_LOGE(TAG, "The TLV type must be structure"));
569+
VerifyOrReturnError(reader.OpenContainer(attr_val_reader) == CHIP_NO_ERROR, ESP_FAIL,
570+
ESP_LOGE(TAG, "Failed to open container"));
571+
VerifyOrReturnError(attr_val_reader.Next() == CHIP_NO_ERROR, ESP_FAIL, ESP_LOGE(TAG, "Failed to read next"));
572+
VerifyOrReturnError(write_client->PutPreencodedAttribute(path, attr_val_reader) == CHIP_NO_ERROR, ESP_FAIL,
573+
ESP_LOGE(TAG, "Failed to put pre-encoded attribute value to WriteClient"));
574+
}
575+
576+
VerifyOrReturnError(write_client->SendWriteRequest(remote_device->GetSecureSession().Value()) == CHIP_NO_ERROR,
577+
ESP_FAIL, ESP_LOGE(TAG, "Failed to Send Write Request"));
578+
579+
// Release the write_client and client deleter callback as it will be managed by the client deleter callback
580+
write_client.release();
581+
client_deleter_callback.release();
582+
return ESP_OK;
583+
}
584+
534585
} // namespace write
535586
} // namespace interaction
536587
} // namespace client

components/esp_matter/esp_matter_client.h

+38
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ using chip::app::ReadClient;
3636
using chip::app::StatusIB;
3737
using chip::app::WriteClient;
3838
using chip::Messaging::ExchangeManager;
39+
using chip::Platform::ScopedMemoryBufferWithSize;
3940
using chip::System::Clock::Timeout;
4041
using chip::TLV::TLVReader;
4142
using client::peer_device_t;
@@ -78,6 +79,39 @@ class custom_encodable_type : public EncodableToTLV
7879
char *m_json_str = NULL;
7980
};
8081

82+
class multiple_write_encodable_type
83+
{
84+
public:
85+
multiple_write_encodable_type(const char *json_str)
86+
{
87+
json = cJSON_Parse(json_str);
88+
}
89+
90+
~multiple_write_encodable_type() { cJSON_Delete(json); }
91+
92+
CHIP_ERROR EncodeTo(chip::TLV::TLVWriter &writer, chip::TLV::Tag tag, size_t index)
93+
{
94+
cJSON *json_at_index = NULL;
95+
if (!json) {
96+
return CHIP_ERROR_INVALID_ARGUMENT;
97+
}
98+
if (json->type != cJSON_Array) {
99+
json_at_index = json;
100+
} else {
101+
json_at_index = cJSON_GetArrayItem(json, index);
102+
}
103+
if (json_to_tlv(json_at_index, writer, tag) != ESP_OK) {
104+
return CHIP_ERROR_INTERNAL;
105+
}
106+
return CHIP_NO_ERROR;
107+
}
108+
109+
size_t GetJsonArraySize() { return static_cast<size_t>(cJSON_GetArraySize(json)); }
110+
111+
private:
112+
cJSON *json = NULL;
113+
};
114+
81115
/** Command invoke APIs
82116
*
83117
* They can be used for all the commands of all the clusters, including the custom clusters.
@@ -185,6 +219,10 @@ esp_err_t send_request(client::peer_device_t *remote_device, AttributePathParams
185219
esp_err_t send_request(client::peer_device_t *remote_device, AttributePathParams &attr_path,
186220
const chip::app::DataModel::EncodableToTLV &encodable, WriteClient::Callback &callback,
187221
const chip::Optional<uint16_t> &timeout_ms);
222+
223+
esp_err_t send_request(client::peer_device_t *remote_device, ScopedMemoryBufferWithSize<AttributePathParams> &attr_paths,
224+
multiple_write_encodable_type &json_encodable, WriteClient::Callback &callback,
225+
const chip::Optional<uint16_t> &timeout_ms);
188226
} // namespace write
189227

190228
namespace subscribe {

components/esp_matter/utils/json_to_tlv.cpp

+7-2
Original file line numberDiff line numberDiff line change
@@ -439,11 +439,17 @@ static esp_err_t encode_tlv_element(const cJSON *val, TLV::TLVWriter &writer, co
439439
esp_err_t json_to_tlv(const char *json_str, chip::TLV::TLVWriter &writer, chip::TLV::Tag tag)
440440
{
441441
cJSON *json = cJSON_Parse(json_str);
442+
esp_err_t err = json_to_tlv(json, writer, tag);
443+
cJSON_Delete(json);
444+
return err;
445+
}
446+
447+
esp_err_t json_to_tlv(cJSON *json, chip::TLV::TLVWriter &writer, chip::TLV::Tag tag)
448+
{
442449
if (!json) {
443450
return ESP_ERR_INVALID_ARG;
444451
}
445452
if (json->type != cJSON_Object) {
446-
cJSON_Delete(json);
447453
return ESP_ERR_INVALID_ARG;
448454
}
449455
element_context element_ctx;
@@ -454,7 +460,6 @@ esp_err_t json_to_tlv(const char *json_str, chip::TLV::TLVWriter &writer, chip::
454460
if (err != ESP_OK) {
455461
ESP_LOGE(TAG, "Failed to encode tlv element");
456462
}
457-
cJSON_Delete(json);
458463
return err;
459464
}
460465

components/esp_matter/utils/json_to_tlv.h

+12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#pragma once
1616

17+
#include "cJSON.h"
1718
#include <esp_err.h>
1819
#include <lib/core/TLV.h>
1920
#include <string>
@@ -55,4 +56,15 @@ const char k_floating_point_negative_infinity[] = "-INF";
5556
*/
5657
esp_err_t json_to_tlv(const char *json_str, chip::TLV::TLVWriter &writer, chip::TLV::Tag tag);
5758

59+
/** Convert a JSON object to the given TLVWriter
60+
*
61+
* @param[in] json The JSON object
62+
* @param[out] writer The TLV output from the JSON object
63+
* @param[in] tag The TLV tag of the TLV structure
64+
*
65+
* @return ESP_OK on success
66+
* @return error in case of failure
67+
*/
68+
esp_err_t json_to_tlv(cJSON *json, chip::TLV::TLVWriter &writer, chip::TLV::Tag tag);
69+
5870
} // namespace esp_matter

components/esp_matter_controller/commands/esp_matter_controller_write_command.cpp

+33-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ void write_command::on_device_connected_fcn(void *context, ExchangeManager &exch
4646
{
4747
write_command *cmd = (write_command *)context;
4848
chip::OperationalDeviceProxy device_proxy(&exchangeMgr, sessionHandle);
49-
esp_err_t err = interaction::write::send_request(&device_proxy, cmd->m_attr_path, cmd->m_attr_val,
49+
esp_err_t err = interaction::write::send_request(&device_proxy, cmd->m_attr_paths, cmd->m_attr_vals,
5050
cmd->m_chunked_callback, cmd->m_timed_write_timeout_ms);
5151
if (err != ESP_OK) {
5252
chip::Platform::Delete(cmd);
@@ -105,5 +105,37 @@ esp_err_t send_write_attr_command(uint64_t node_id, uint16_t endpoint_id, uint32
105105
return cmd->send_command();
106106
}
107107

108+
esp_err_t send_write_attr_command(uint64_t node_id, ScopedMemoryBufferWithSize<uint16_t> &endpoint_ids,
109+
ScopedMemoryBufferWithSize<uint32_t> &cluster_ids,
110+
ScopedMemoryBufferWithSize<uint32_t> &attribute_ids, const char *attr_val_json_str,
111+
chip::Optional<uint16_t> timed_write_timeout_ms)
112+
{
113+
if (endpoint_ids.AllocatedSize() != cluster_ids.AllocatedSize() ||
114+
endpoint_ids.AllocatedSize() != attribute_ids.AllocatedSize()) {
115+
ESP_LOGE(TAG,
116+
"The endpoint_id array length should be the same as the cluster_ids array length"
117+
"and the attribute_ids array length");
118+
return ESP_ERR_INVALID_ARG;
119+
}
120+
ScopedMemoryBufferWithSize<AttributePathParams> attr_paths;
121+
ScopedMemoryBufferWithSize<EventPathParams> event_paths;
122+
attr_paths.Alloc(endpoint_ids.AllocatedSize());
123+
if (!attr_paths.Get()) {
124+
ESP_LOGE(TAG, "Failed to alloc memory for attribute paths");
125+
return ESP_ERR_NO_MEM;
126+
}
127+
for (size_t i = 0; i < attr_paths.AllocatedSize(); ++i) {
128+
attr_paths[i] = AttributePathParams(endpoint_ids[i], cluster_ids[i], attribute_ids[i]);
129+
}
130+
131+
write_command *cmd =
132+
chip::Platform::New<write_command>(node_id, std::move(attr_paths), attr_val_json_str, timed_write_timeout_ms);
133+
if (!cmd) {
134+
ESP_LOGE(TAG, "Failed to alloc memory for read_command");
135+
return ESP_ERR_NO_MEM;
136+
}
137+
return cmd->send_command();
138+
}
139+
108140
} // namespace controller
109141
} // namespace esp_matter

components/esp_matter_controller/commands/esp_matter_controller_write_command.h

+47-5
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,48 @@ using chip::app::ConcreteDataAttributePath;
3131
using chip::app::StatusIB;
3232
using chip::app::WriteClient;
3333
using chip::Messaging::ExchangeManager;
34+
using chip::Platform::ScopedMemoryBufferWithSize;
3435
using chip::TLV::TLVElementType;
3536
using esp_matter::client::peer_device_t;
3637
using esp_matter::client::interaction::custom_encodable_type;
38+
using esp_matter::client::interaction::multiple_write_encodable_type;
3739

3840
/** Write command class to send a write interaction command to a server **/
3941
class write_command : public WriteClient::Callback {
4042
public:
43+
/** Constructor for command with multiple paths**/
44+
write_command(uint64_t node_id, ScopedMemoryBufferWithSize<AttributePathParams> &&attr_paths,
45+
const char *attribute_val_str,
46+
const chip::Optional<uint16_t> timed_write_timeout_ms = chip::NullOptional)
47+
: m_node_id(node_id)
48+
, m_attr_paths(std::move(attr_paths))
49+
, m_chunked_callback(this)
50+
, m_attr_vals(attribute_val_str)
51+
, m_timed_write_timeout_ms(timed_write_timeout_ms)
52+
, on_device_connected_cb(on_device_connected_fcn, this)
53+
, on_device_connection_failure_cb(on_device_connection_failure_fcn, this)
54+
{
55+
}
56+
4157
/** Constructor for command with an attribute path**/
4258
write_command(uint64_t node_id, uint16_t endpoint_id, uint32_t cluster_id, uint32_t attribute_id,
4359
const char *attribute_val_str,
4460
const chip::Optional<uint16_t> timed_write_timeout_ms = chip::NullOptional)
4561
: m_node_id(node_id)
46-
, m_attr_path(endpoint_id, cluster_id, attribute_id)
4762
, m_chunked_callback(this)
48-
, m_attr_val(attribute_val_str, custom_encodable_type::interaction_type::k_write_attr)
63+
, m_attr_vals(attribute_val_str)
4964
, m_timed_write_timeout_ms(timed_write_timeout_ms)
5065
, on_device_connected_cb(on_device_connected_fcn, this)
51-
, on_device_connection_failure_cb(on_device_connection_failure_fcn, this) {}
66+
, on_device_connection_failure_cb(on_device_connection_failure_fcn, this)
67+
{
68+
m_attr_paths.Alloc(1);
69+
if (m_attr_paths.Get()) {
70+
m_attr_paths[0] = AttributePathParams(endpoint_id, cluster_id, attribute_id);
71+
} else {
72+
ChipLogError(DeviceLayer, "Alloc space for attribute path failed!");
73+
assert(0);
74+
}
75+
}
5276

5377
~write_command() {}
5478

@@ -76,9 +100,9 @@ class write_command : public WriteClient::Callback {
76100

77101
private:
78102
uint64_t m_node_id;
79-
AttributePathParams m_attr_path;
103+
ScopedMemoryBufferWithSize<AttributePathParams> m_attr_paths;
80104
ChunkedWriteCallback m_chunked_callback;
81-
custom_encodable_type m_attr_val;
105+
multiple_write_encodable_type m_attr_vals;
82106
chip::Optional<uint16_t> m_timed_write_timeout_ms;
83107

84108
static void on_device_connected_fcn(void *context, ExchangeManager &exchangeMgr,
@@ -106,5 +130,23 @@ esp_err_t send_write_attr_command(uint64_t node_id, uint16_t endpoint_id, uint32
106130
const char *attr_val_json_str,
107131
chip::Optional<uint16_t> timed_write_timeout_ms = chip::NullOptional);
108132

133+
/** Send write attribute command to multiple attribute paths
134+
*
135+
* @param[in] node_id Remote NodeId
136+
* @param[in] endpoint_ids EndpointIds
137+
* @param[in] cluster_ids ClusterIds
138+
* @param[in] attribute_ids AttributeIds
139+
* @param[in] attr_val_json_str Attribute value string with JSON format
140+
* (https://docs.espressif.com/projects/esp-matter/en/latest/esp32/developing.html#write-attribute-commands)
141+
* @param[in] timed_write_timeout_ms Timeout in millisecond for timed-write attributes
142+
*
143+
* @return ESP_OK on success.
144+
* @return error in case of failure.
145+
*/
146+
esp_err_t send_write_attr_command(uint64_t node_id, ScopedMemoryBufferWithSize<uint16_t> &endpoint_ids,
147+
ScopedMemoryBufferWithSize<uint32_t> &cluster_ids,
148+
ScopedMemoryBufferWithSize<uint32_t> &attribute_ids, const char *attr_val_json_str,
149+
chip::Optional<uint16_t> timed_write_timeout_ms = chip::NullOptional);
150+
109151
} // namespace controller
110152
} // namespace esp_matter

components/esp_matter_controller/core/esp_matter_controller_console.cpp

+16-10
Original file line numberDiff line numberDiff line change
@@ -460,20 +460,24 @@ static esp_err_t controller_write_attr_handler(int argc, char **argv)
460460
}
461461

462462
uint64_t node_id = string_to_uint64(argv[0]);
463-
uint16_t endpoint_id = string_to_uint16(argv[1]);
464-
uint32_t cluster_id = string_to_uint32(argv[2]);
465-
uint32_t attribute_id = string_to_uint32(argv[3]);
463+
ScopedMemoryBufferWithSize<uint16_t> endpoint_ids;
464+
ScopedMemoryBufferWithSize<uint32_t> cluster_ids;
465+
ScopedMemoryBufferWithSize<uint32_t> attribute_ids;
466+
ESP_RETURN_ON_ERROR(string_to_uint16_array(argv[1], endpoint_ids), TAG, "Failed to parse endpoint IDs");
467+
ESP_RETURN_ON_ERROR(string_to_uint32_array(argv[2], cluster_ids), TAG, "Failed to parse cluster IDs");
468+
ESP_RETURN_ON_ERROR(string_to_uint32_array(argv[3], attribute_ids), TAG, "Failed to parse attribute IDs");
469+
466470
char *attribute_val_str = argv[4];
467471

468472
if (argc > 5) {
469473
uint16_t timed_write_timeout_ms = string_to_uint16(argv[5]);
470474
if (timed_write_timeout_ms > 0) {
471-
return controller::send_write_attr_command(node_id, endpoint_id, cluster_id, attribute_id,
475+
return controller::send_write_attr_command(node_id, endpoint_ids, cluster_ids, attribute_ids,
472476
attribute_val_str, chip::MakeOptional(timed_write_timeout_ms));
473477
}
474478
}
475479

476-
return controller::send_write_attr_command(node_id, endpoint_id, cluster_id, attribute_id, attribute_val_str);
480+
return controller::send_write_attr_command(node_id, endpoint_ids, cluster_ids, attribute_ids, attribute_val_str);
477481
}
478482

479483
static esp_err_t controller_read_event_handler(int argc, char **argv)
@@ -639,14 +643,16 @@ esp_err_t controller_register_commands()
639643
{
640644
.name = "read-attr",
641645
.description = "Read attributes of the nodes.\n"
642-
"\tUsage: controller read-attr <node-id> <endpoint-id> <cluster-id> <attr-id>",
646+
"\tUsage: controller read-attr <node-id> <endpoint-ids> <cluster-ids> <attr-ids>\n"
647+
"\tNotes: endpoint-ids can represent a single or multiple endpoints, e.g. '0' or '0,1'. "
648+
"And the same applies to cluster-ids, attr-ids, and event-ids.",
643649
.handler = controller_read_attr_handler,
644650
},
645651
{
646652
.name = "write-attr",
647653
.description =
648654
"Write attributes of the nodes.\n"
649-
"\tUsage: controller write-attr <node-id> <endpoint-id> <cluster-id> <attr-id> <attr-value> [timed_write_timeout_ms]\n"
655+
"\tUsage: controller write-attr <node-id> <endpoint-ids> <cluster-ids> <attr-ids> <attr-value> [timed_write_timeout_ms]\n"
650656
"\tNotes: attr-value should be a JSON object that contains the attribute value JSON item."
651657
"You can get the format of the attr-value from "
652658
"https://docs.espressif.com/projects/esp-matter/en/latest/esp32/"
@@ -656,20 +662,20 @@ esp_err_t controller_register_commands()
656662
{
657663
.name = "read-event",
658664
.description = "Read events of the nodes.\n"
659-
"\tUsage: controller read-event <node-id> <endpoint-id> <cluster-id> <event-id>",
665+
"\tUsage: controller read-event <node-id> <endpoint-ids> <cluster-ids> <event-ids>",
660666
.handler = controller_read_event_handler,
661667
},
662668
{
663669
.name = "subs-attr",
664670
.description = "Subscribe attributes of the nodes.\n"
665-
"\tUsage: controller subs-attr <node-id> <endpoint-id> <cluster-id> <attr-id> "
671+
"\tUsage: controller subs-attr <node-id> <endpoint-ids> <cluster-ids> <attr-ids> "
666672
"<min-interval> <max-interval>",
667673
.handler = controller_subscribe_attr_handler,
668674
},
669675
{
670676
.name = "subs-event",
671677
.description = "Subscribe events of the nodes.\n"
672-
"\tUsage: controller subs-attr <node-id> <endpoint-id> <cluster-id> <event-id> "
678+
"\tUsage: controller subs-event <node-id> <endpoint-ids> <cluster-ids> <event-ids> "
673679
"<min-interval> <max-interval>",
674680
.handler = controller_subscribe_event_handler,
675681
},

0 commit comments

Comments
 (0)