Skip to content

Commit

Permalink
[MatterYamlTests] Add basic support for darwin-framework-tool (#28969)
Browse files Browse the repository at this point in the history
* Add darwin-framework-tool python yaml parser connection support. For the moment darwin-framework-tool does not send back anything nor supports special test commands that have been added to chip-tool to fully support yaml

* Add sleep command to darwin-framework-tool

* Add wait-for-commissionee command to darwin-framework-tool

* Add darwin-framework-tool python yaml parser output connection support

* Hack examples/chip-tool/py_matter_chip_tool_adapter/matter_chip_tool_adapter/encoder.py so it does use the right argument names for darwin-framework-tool

---------

Co-authored-by: Vivien Nicolas <[email protected]>
  • Loading branch information
2 people authored and pull[bot] committed Nov 30, 2023
1 parent 352371e commit ec67254
Show file tree
Hide file tree
Showing 22 changed files with 17,961 additions and 2,737 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,22 @@ def run(self, specs, value, cluster_name: str, typename: str, array: bool):
)
del value[str(field_code)]

# darwin-framework-tool returns the field name but with a different casing than what
# the test suite expects.
# To not confuse the test suite, the field name is replaced by its field name
# equivalent from the spec and then removed.
wrong_casing_field_name = field_name[0].lower(
) + field_name[1:]
if field_name not in value and field_name[0].upper() == field_name[0] and wrong_casing_field_name in value:
value[field_name] = self.run(
specs,
value[wrong_casing_field_name],
cluster_name,
field_type,
field_array
)
del value[wrong_casing_field_name]

if specs.is_fabric_scoped(struct):
value[_FABRIC_INDEX_FIELD_NAME] = self.run(
specs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

import base64
import json
import os
import re
import sys

_ANY_COMMANDS_LIST = [
'ReadById',
Expand Down Expand Up @@ -208,6 +210,12 @@ class Encoder:
def __init__(self, specifications):
self.__specs = specifications

# This is not the best way to toggle this flag. But for now it prevents having
# to build a new adapter for the very small differences that exists...
is_darwin_framework_tool = os.path.basename(
sys.argv[0]) == 'darwinframeworktool.py'
self.__is_darwin_framework_tool = is_darwin_framework_tool

def encode(self, request):
cluster = self.__get_cluster_name(request)
command, command_specifier = self.__get_command_name(request)
Expand Down Expand Up @@ -305,7 +313,10 @@ def __maybe_add_destination(self, rv, request):
if not self._supports_destination(request):
return rv

destination_argument_name = 'destination-id'
if self.__is_darwin_framework_tool:
destination_argument_name = 'node-id'
else:
destination_argument_name = 'destination-id'
destination_argument_value = None

if request.group_id:
Expand Down Expand Up @@ -333,6 +344,9 @@ def __maybe_add_endpoint(self, rv, request):
if (request.is_attribute and not request.command == "writeAttribute") or request.is_event or (request.command in _ANY_COMMANDS_LIST and not request.command == "WriteById"):
endpoint_argument_name = 'endpoint-ids'

if self.__is_darwin_framework_tool:
endpoint_argument_name = 'endpoint-id'

if rv:
rv += ', '
rv += f'"{endpoint_argument_name}": "{endpoint_argument_value}"'
Expand Down Expand Up @@ -378,7 +392,10 @@ def __get_argument_name(self, request, entry):

if request.is_attribute:
if command_name == 'writeAttribute':
argument_name = 'attribute-values'
if self.__is_darwin_framework_tool:
argument_name = 'attr-value'
else:
argument_name = 'attribute-values'
else:
argument_name = 'value'

Expand Down
20 changes: 18 additions & 2 deletions examples/darwin-framework-tool/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import("//build_overrides/build.gni")
import("//build_overrides/chip.gni")
import("//build_overrides/jsoncpp.gni")

import("${chip_root}/build/chip/tools.gni")
import("${chip_root}/build/config/compiler/compiler.gni")
Expand Down Expand Up @@ -132,6 +133,7 @@ action("build-darwin-framework") {
config("config") {
include_dirs = [
".",
"${chip_root}/examples/common",
"${chip_root}/examples/darwin-framework-tool/commands/common",
"${chip_root}/zzz_generated/darwin-framework-tool",
"${chip_root}/zzz_generated/controller-clusters",
Expand Down Expand Up @@ -178,6 +180,13 @@ executable("darwin-framework-tool") {
"commands/common/MTRError.mm",
"commands/common/MTRError_Utils.h",
"commands/common/MTRLogging.h",
"commands/common/RemoteDataModelLogger.h",
"commands/common/RemoteDataModelLogger.mm",
"commands/delay/Commands.h",
"commands/delay/SleepCommand.h",
"commands/delay/SleepCommand.mm",
"commands/delay/WaitForCommissioneeCommand.h",
"commands/delay/WaitForCommissioneeCommand.mm",
"commands/discover/Commands.h",
"commands/discover/DiscoverCommissionablesCommand.h",
"commands/discover/DiscoverCommissionablesCommand.mm",
Expand All @@ -200,12 +209,16 @@ executable("darwin-framework-tool") {

deps = [
":build-darwin-framework",
"${chip_root}/third_party/jsoncpp",
jsoncpp_root,
]

if (config_use_interactive_mode) {
sources += [ "commands/interactive/InteractiveCommands.mm" ]
deps += [ "${editline_root}:editline" ]

deps += [
"${chip_root}/examples/common/websocket-server",
"${editline_root}:editline",
]
}

ldflags = [
Expand Down Expand Up @@ -240,6 +253,7 @@ executable("darwin-framework-tool") {

# pics is needed by tests
"${chip_root}/src/app/tests/suites/pics",
"${chip_root}/src/protocols:im_status",
]

defines = []
Expand All @@ -248,6 +262,8 @@ executable("darwin-framework-tool") {
"${chip_root}/config/standalone/",
"${chip_root}/src/",
"${chip_root}/src/include/",
"${chip_root}/src/protocols/",
"${chip_root}/src/protocols/interaction_model",
"${chip_root}/third_party/nlassert/repo/include/",
"${chip_root}/third_party/nlio/repo/include/",
"${chip_root}/zzz_generated/app-common/",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
ChipLogProgress(chipTool, "Running Command");
ReturnErrorOnFailure(MaybeSetUpStack());
SetIdentity(mCommissionerName.HasValue() ? mCommissionerName.Value() : kIdentityAlpha);

{
std::lock_guard<std::mutex> lk(cvWaitingForResponseMutex);
mWaitingForResponse = YES;
}

ReturnLogErrorOnFailure(RunCommand());

auto err = StartWaiting(GetWaitDuration());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2023 Project CHIP Authors
* All rights reserved.
*
* 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.
*
*/

#import <Matter/Matter.h>

#include <lib/core/CHIPError.h>

class RemoteDataModelLoggerDelegate {
public:
CHIP_ERROR virtual LogJSON(const char *) = 0;
virtual ~RemoteDataModelLoggerDelegate() {};
};

namespace RemoteDataModelLogger {
CHIP_ERROR LogAttributeAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * attributeId, id result);
CHIP_ERROR LogCommandAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * commandId, id result);
CHIP_ERROR LogAttributeErrorAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * attributeId, NSError * error);
CHIP_ERROR LogCommandErrorAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * commandId, NSError * error);
void SetDelegate(RemoteDataModelLoggerDelegate * delegate);
}; // namespace RemoteDataModelLogger
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* Copyright (c) 2023 Project CHIP Authors
* All rights reserved.
*
* 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 "RemoteDataModelLogger.h"

#import "MTRError_Utils.h"
#import <objc/runtime.h>

#include <json/json.h>
#include <lib/support/SafeInt.h>
#include <protocols/interaction_model/StatusCode.h>

#include <string>

constexpr const char * kClusterIdKey = "clusterId";
constexpr const char * kEndpointIdKey = "endpointId";
constexpr const char * kAttributeIdKey = "attributeId";
constexpr const char * kCommandIdKey = "commandId";
constexpr const char * kErrorIdKey = "error";
constexpr const char * kClusterErrorIdKey = "clusterError";
constexpr const char * kValueKey = "value";

constexpr const char kBase64Header[] = "base64:";

namespace {
RemoteDataModelLoggerDelegate * gDelegate;

std::string JsonToString(Json::Value & json)
{
Json::FastWriter writer;
writer.omitEndingLineFeed();
return writer.write(json);
}

CHIP_ERROR LogError(Json::Value & value, const chip::app::StatusIB & status)
{
if (status.mClusterStatus.HasValue()) {
auto statusValue = status.mClusterStatus.Value();
value[kClusterErrorIdKey] = statusValue;
}

#if CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT
auto statusName = chip::Protocols::InteractionModel::StatusName(status.mStatus);
value[kErrorIdKey] = statusName;
#else
auto statusName = status.mStatus;
value[kErrorIdKey] = chip::to_underlying(statusName);
#endif // CHIP_CONFIG_IM_STATUS_CODE_VERBOSE_FORMAT

auto valueStr = JsonToString(value);
return gDelegate->LogJSON(valueStr.c_str());
}

CHIP_ERROR AsJsonValue(id value, Json::Value & jsonValue)
{
if (value == nil) {
jsonValue = Json::nullValue;
} else if ([value isKindOfClass:[NSNumber class]]) {
if (CFNumberIsFloatType((CFNumberRef) value)) {
jsonValue = [value doubleValue];
} else if ([[value stringValue] hasPrefix:@"-"]) {
jsonValue = [value longLongValue];
} else {
jsonValue = [value unsignedLongLongValue];
}
} else if ([value isKindOfClass:[NSArray class]]) {
jsonValue = Json::arrayValue;

NSArray * array = value;
for (id element in array) {
Json::Value jsonElement;
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue(element, jsonElement));
jsonValue.append(jsonElement);
}
} else if ([value isKindOfClass:[NSDictionary class]]) {
jsonValue = Json::ValueType::objectValue;

NSDictionary * dict = value;
for (id key in dict) {
Json::Value jsonElement;
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue([dict objectForKey:key], jsonElement));
jsonValue[[key UTF8String]] = jsonElement;
}
} else if ([value isKindOfClass:[NSData class]]) {
NSData * data = value;
data = [data base64EncodedDataWithOptions:0];
auto base64Str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
auto prefix = [NSString stringWithUTF8String:kBase64Header];
auto base64PrefixedStr = [prefix stringByAppendingString:base64Str];
jsonValue = [base64PrefixedStr UTF8String];
} else if ([value isKindOfClass:[NSString class]]) {
jsonValue = [value UTF8String];
} else if ([value isKindOfClass:[NSObject class]]) {
jsonValue = Json::ValueType::objectValue;

unsigned int numberOfProperties;
objc_property_t * properties = class_copyPropertyList([value class], &numberOfProperties);
for (NSUInteger i = 0; i < numberOfProperties; i++) {
objc_property_t property = properties[i];
NSString * key = [[NSString alloc] initWithUTF8String:property_getName(property)];

Json::Value jsonElement;
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue([value valueForKey:key], jsonElement));
jsonValue[[key UTF8String]] = jsonElement;
}
free(properties);
} else {
return CHIP_ERROR_NOT_IMPLEMENTED;
}

return CHIP_NO_ERROR;
}

} // namespace

namespace RemoteDataModelLogger {
CHIP_ERROR LogAttributeAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * attributeId, id result)
{
VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR);

Json::Value value;
value[kEndpointIdKey] = [endpointId unsignedLongLongValue];
value[kClusterIdKey] = [clusterId unsignedLongLongValue];
value[kAttributeIdKey] = [attributeId unsignedLongLongValue];

Json::Value jsonValue;
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue(result, jsonValue));
value[kValueKey] = jsonValue;

auto valueStr = JsonToString(value);
return gDelegate->LogJSON(valueStr.c_str());
}

CHIP_ERROR LogCommandAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * commandId, id result)
{
VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR);

Json::Value value;
value[kEndpointIdKey] = [endpointId unsignedLongLongValue];
value[kClusterIdKey] = [clusterId unsignedLongLongValue];
value[kCommandIdKey] = [commandId unsignedLongLongValue];

Json::Value jsonValue;
VerifyOrDie(CHIP_NO_ERROR == AsJsonValue(result, jsonValue));
value[kValueKey] = jsonValue;

auto valueStr = JsonToString(value);
return gDelegate->LogJSON(valueStr.c_str());
}

CHIP_ERROR LogAttributeErrorAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * attributeId, NSError * error)
{
VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR);

Json::Value value;
value[kEndpointIdKey] = [endpointId unsignedLongLongValue];
value[kClusterIdKey] = [clusterId unsignedLongLongValue];
value[kAttributeIdKey] = [attributeId unsignedLongLongValue];

auto err = MTRErrorToCHIPErrorCode(error);
auto status = chip::app::StatusIB(err);
return LogError(value, status);
}

CHIP_ERROR LogCommandErrorAsJSON(NSNumber * endpointId, NSNumber * clusterId, NSNumber * commandId, NSError * error)
{
VerifyOrReturnError(gDelegate != nullptr, CHIP_NO_ERROR);

Json::Value value;
value[kEndpointIdKey] = [endpointId unsignedLongLongValue];
value[kClusterIdKey] = [clusterId unsignedLongLongValue];
value[kCommandIdKey] = [commandId unsignedLongLongValue];

auto err = MTRErrorToCHIPErrorCode(error);
auto status = chip::app::StatusIB(err);
return LogError(value, status);
}

void SetDelegate(RemoteDataModelLoggerDelegate * delegate) { gDelegate = delegate; }
}; // namespace RemoteDataModelLogger
Loading

0 comments on commit ec67254

Please sign in to comment.