Skip to content

Commit

Permalink
Include event header information in results from multi-path read/subs…
Browse files Browse the repository at this point in the history
…cribe. (#26173)

Fixes #26075
  • Loading branch information
bzbarsky-apple authored and pull[bot] committed Jan 4, 2024
1 parent 4dd0396 commit 1071929
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 113 deletions.
19 changes: 19 additions & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ NS_ASSUME_NONNULL_BEGIN
* MTRDataKey: Data-value NSDictionary object.
* Included when there is data and when there is no error.
* The data-value is described below.
* MTREventNumberKey : NSNumber-wrapped uint64_t value. Monotonically increasing, and consecutive event reports
* should have consecutive numbers unless device reboots, or if events are lost.
* Only present when both MTREventPathKey and MTRDataKey are present.
* MTREventPriorityKey : NSNumber-wrapped MTREventPriority value.
* Only present when both MTREventPathKey and MTRDataKey are present.
* MTREventTimeTypeKey : NSNumber-wrapped MTREventTimeType value.
* Only present when both MTREventPathKey and MTRDataKey are present.
* MTREventSystemUpTimeKey : NSNumber-wrapped NSTimeInterval value.
* Only present when MTREventTimeTypeKey is MTREventTimeTypeSystemUpTime.
* MTREventTimestampDateKey : NSDate object.
* Only present when MTREventTimeTypeKey is MTREventTimeTypeTimestampDate.
*
* Only one of MTREventTimestampDateKey and MTREventSystemUpTimeKey will be present, depending on the value for
* MTREventTimeTypeKey.
*
* A data-value is an NSDictionary object with the following key values:
*
Expand Down Expand Up @@ -117,6 +131,11 @@ extern NSString * const MTRDoubleValueType;
extern NSString * const MTRNullValueType;
extern NSString * const MTRStructureValueType;
extern NSString * const MTRArrayValueType;
extern NSString * const MTREventNumberKey API_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));
extern NSString * const MTREventPriorityKey API_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));
extern NSString * const MTREventTimeTypeKey API_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));
extern NSString * const MTREventSystemUpTimeKey API_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));
extern NSString * const MTREventTimestampDateKey API_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));

@class MTRClusterStateCacheContainer;
@class MTRAttributeCacheContainer;
Expand Down
120 changes: 98 additions & 22 deletions src/darwin/Framework/CHIP/MTRBaseDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@
NSString * const MTRNullValueType = @"Null";
NSString * const MTRStructureValueType = @"Structure";
NSString * const MTRArrayValueType = @"Array";
NSString * const MTREventNumberKey = @"eventNumber";
NSString * const MTREventPriorityKey = @"eventPriority";
NSString * const MTREventTimeTypeKey = @"eventTimeType";
NSString * const MTREventSystemUpTimeKey = @"eventSystemUpTime";
NSString * const MTREventTimestampDateKey = @"eventTimestampDate";

class MTRDataValueDictionaryCallbackBridge;

Expand Down Expand Up @@ -752,7 +757,7 @@ CHIP_ERROR Encode(chip::TLV::TLVWriter & writer, chip::TLV::Tag tag) const
public:
using OnSuccessAttributeCallbackType
= std::function<void(const ConcreteAttributePath & aPath, const DecodableValueType & aData)>;
using OnSuccessEventCallbackType = std::function<void(const ConcreteEventPath & aPath, const DecodableValueType & aData)>;
using OnSuccessEventCallbackType = std::function<void(const EventHeader & aEventHeader, const DecodableValueType & aData)>;
using OnErrorCallbackType = std::function<void(
const app::ConcreteAttributePath * attributePath, const app::ConcreteEventPath * eventPath, CHIP_ERROR aError)>;
using OnDoneCallbackType = std::function<void(BufferedReadClientCallback * callback)>;
Expand Down Expand Up @@ -844,7 +849,7 @@ void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, cons

SuccessOrExit(err = app::DataModel::Decode(*apData, value));

mOnEventSuccess(aEventHeader.mPath, value);
mOnEventSuccess(aEventHeader, value);

exit:
if (err != CHIP_NO_ERROR) {
Expand Down Expand Up @@ -944,31 +949,28 @@ - (void)readAttributePaths:(NSArray<MTRAttributeRequestPath *> * _Nullable)attri

auto resultArray = [[NSMutableArray alloc] init];
auto onAttributeSuccessCb
= [resultArray](const ConcreteAttributePath & attributePath, const MTRDataValueDictionaryDecodableType & aData) {
= [resultArray](const ConcreteAttributePath & aAttributePath, const MTRDataValueDictionaryDecodableType & aData) {
[resultArray addObject:@ {
MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:attributePath],
MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:aAttributePath],
MTRDataKey : aData.GetDecodedObject()
}];
};

auto onEventSuccessCb
= [resultArray](const ConcreteEventPath & eventPath, const MTRDataValueDictionaryDecodableType & aData) {
[resultArray addObject:@ {
MTREventPathKey : [[MTREventPath alloc] initWithPath:eventPath],
MTRDataKey : aData.GetDecodedObject()
}];
= [resultArray](const EventHeader & aEventHeader, const MTRDataValueDictionaryDecodableType & aData) {
[resultArray addObject:[MTRBaseDevice eventReportForHeader:aEventHeader andData:aData.GetDecodedObject()]];
};

auto onFailureCb = [resultArray, interactionStatus](const app::ConcreteAttributePath * attributePath,
const app::ConcreteEventPath * eventPath, CHIP_ERROR aError) {
if (attributePath != nullptr) {
auto onFailureCb = [resultArray, interactionStatus](const app::ConcreteAttributePath * aAttributePath,
const app::ConcreteEventPath * aEventPath, CHIP_ERROR aError) {
if (aAttributePath != nullptr) {
[resultArray addObject:@ {
MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:*attributePath],
MTRAttributePathKey : [[MTRAttributePath alloc] initWithPath:*aAttributePath],
MTRErrorKey : [MTRError errorForCHIPErrorCode:aError]
}];
} else if (eventPath != nullptr) {
} else if (aEventPath != nullptr) {
[resultArray addObject:@ {
MTREventPathKey : [[MTREventPath alloc] initWithPath:*eventPath],
MTREventPathKey : [[MTREventPath alloc] initWithPath:*aEventPath],
MTRErrorKey : [MTRError errorForCHIPErrorCode:aError]
}];
} else {
Expand Down Expand Up @@ -1343,14 +1345,11 @@ - (void)subscribeToAttributePaths:(NSArray<MTRAttributeRequestPath *> * _Nullabl
});
};

auto onEventReportCb = [queue, reportHandler](const ConcreteEventPath & eventPath,
const MTRDataValueDictionaryDecodableType & data) {
id valueObject = data.GetDecodedObject();
ConcreteEventPath pathCopy(eventPath);
auto onEventReportCb = [queue, reportHandler](
const EventHeader & eventHeader, const MTRDataValueDictionaryDecodableType & data) {
NSDictionary * report = [MTRBaseDevice eventReportForHeader:eventHeader andData:data.GetDecodedObject()];
dispatch_async(queue, ^{
reportHandler(
@[ @ { MTREventPathKey : [[MTREventPath alloc] initWithPath:pathCopy], MTRDataKey : valueObject } ],
nil);
reportHandler(@[ report ], nil);
});
};

Expand Down Expand Up @@ -1540,6 +1539,43 @@ static CHIP_ERROR OpenCommissioningWindow(Controller::DeviceController * control
delete self;
}

#pragma mark - Utility for time conversion
NSTimeInterval MTRTimeIntervalForEventTimestampValue(uint64_t timeValue)
{
// Note: The event timestamp value as written in the spec is in microseconds, but the released 1.0 SDK implemented it in
// milliseconds. The following issue was filed to address the inconsistency:
// https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/6236
// For consistency with the released behavior, calculations here will be done in milliseconds.

// First convert the event timestamp value (in milliseconds) to NSTimeInterval - to minimize potential loss of precision
// of uint64 => NSTimeInterval (double), convert whole seconds and remainder separately and then combine
uint64_t eventTimestampValueSeconds = timeValue / chip::kMillisecondsPerSecond;
uint64_t eventTimestampValueRemainderMilliseconds = timeValue % chip::kMillisecondsPerSecond;
NSTimeInterval eventTimestampValueRemainder
= NSTimeInterval(eventTimestampValueRemainderMilliseconds) / chip::kMillisecondsPerSecond;
NSTimeInterval eventTimestampValue = eventTimestampValueSeconds + eventTimestampValueRemainder;

return eventTimestampValue;
}

#pragma mark - Utility for event priority conversion
BOOL MTRPriorityLevelIsValid(chip::app::PriorityLevel priorityLevel)
{
return (priorityLevel >= chip::app::PriorityLevel::Debug) && (priorityLevel <= chip::app::PriorityLevel::Critical);
}

MTREventPriority MTREventPriorityForValidPriorityLevel(chip::app::PriorityLevel priorityLevel)
{
switch (priorityLevel) {
case chip::app::PriorityLevel::Debug:
return MTREventPriorityDebug;
case chip::app::PriorityLevel::Info:
return MTREventPriorityInfo;
default:
return MTREventPriorityCritical;
}
}

} // anonymous namespace

- (void)_openCommissioningWindowWithSetupPasscode:(nullable NSNumber *)setupPasscode
Expand Down Expand Up @@ -1738,6 +1774,46 @@ - (void)subscribeToEventsWithEndpointID:(NSNumber * _Nullable)endpointID
subscriptionEstablished:subscriptionEstablished
resubscriptionScheduled:nil];
}

+ (NSDictionary *)eventReportForHeader:(const chip::app::EventHeader &)header andData:(id _Nullable)data
{
MTREventPath * eventPath = [[MTREventPath alloc] initWithPath:header.mPath];
if (data == nil) {
MTR_LOG_ERROR("%@ could not decode event data", eventPath);
return @{ MTREventPathKey : eventPath, MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT] };
}

// Construct the right type, and key/value depending on the type
NSNumber * eventTimeType;
NSString * timestampKey;
id timestampValue;
if (header.mTimestamp.mType == Timestamp::Type::kSystem) {
eventTimeType = @(MTREventTimeTypeSystemUpTime);
timestampKey = MTREventSystemUpTimeKey;
timestampValue = @(MTRTimeIntervalForEventTimestampValue(header.mTimestamp.mValue));
} else if (header.mTimestamp.mType == Timestamp::Type::kEpoch) {
eventTimeType = @(MTREventTimeTypeTimestampDate);
timestampKey = MTREventTimestampDateKey;
timestampValue = [NSDate dateWithTimeIntervalSince1970:MTRTimeIntervalForEventTimestampValue(header.mTimestamp.mValue)];
} else {
MTR_LOG_ERROR("%@ Unsupported event timestamp type %u - ignoring", eventPath, (unsigned int) header.mTimestamp.mType);
return @{ MTREventPathKey : eventPath, MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE] };
}

if (!MTRPriorityLevelIsValid(header.mPriorityLevel)) {
MTR_LOG_ERROR("%@ Unsupported event priority %u - ignoring", eventPath, (unsigned int) header.mPriorityLevel);
return @{ MTREventPathKey : eventPath, MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE] };
}

return @{
MTREventPathKey : eventPath,
MTRDataKey : data,
MTREventNumberKey : @(header.mEventNumber),
MTREventPriorityKey : @(MTREventPriorityForValidPriorityLevel(header.mPriorityLevel)),
MTREventTimeTypeKey : eventTimeType,
timestampKey : timestampValue
};
}
@end

@implementation MTRBaseDevice (Deprecated)
Expand Down
8 changes: 8 additions & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <app/ConcreteCommandPath.h>
#include <app/ConcreteEventPath.h>
#include <app/DeviceProxy.h>
#include <app/EventHeader.h>
#include <app/EventLoggingTypes.h>
#include <app/EventPathParams.h>

Expand Down Expand Up @@ -75,6 +76,13 @@ static inline MTRTransportType MTRMakeTransportType(chip::Transport::Type type)
*/
- (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceController *)controller;

/**
* Create a report, suitable in including in the sort of data structure that
* gets passed to MTRDeviceResponseHandler, from a given event header and
* already-decoded event data. The data is allowed to be nil in error cases
* (e.g. when TLV decoding failed).
*/
+ (NSDictionary *)eventReportForHeader:(const chip::app::EventHeader &)header andData:(id _Nullable)data;
@end

@interface MTRClusterPath ()
Expand Down
6 changes: 0 additions & 6 deletions src/darwin/Framework/CHIP/MTRDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,6 @@ typedef NS_ENUM(NSUInteger, MTRDeviceState) {

@end

extern NSString * const MTREventNumberKey API_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));
extern NSString * const MTREventPriorityKey API_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));
extern NSString * const MTREventTimeTypeKey API_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));
extern NSString * const MTREventSystemUpTimeKey API_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));
extern NSString * const MTREventTimestampDateKey API_AVAILABLE(ios(16.5), macos(13.4), watchos(9.5), tvos(16.5));

@protocol MTRDeviceDelegate <NSObject>
@required
/**
Expand Down
84 changes: 7 additions & 77 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,6 @@
#include <app/InteractionModelEngine.h>
#include <platform/PlatformManager.h>

NSString * const MTREventNumberKey = @"eventNumber";
NSString * const MTREventPriorityKey = @"eventPriority";
NSString * const MTREventTimeTypeKey = @"eventTimeType";
NSString * const MTREventSystemUpTimeKey = @"eventSystemUpTime";
NSString * const MTREventTimestampDateKey = @"eventTimestampDate";

typedef void (^MTRDeviceAttributeReportHandler)(NSArray * _Nonnull);

// Consider moving utility classes to their own file
Expand Down Expand Up @@ -89,41 +83,6 @@ - (id)strongObject
return aNumber;
}

NSTimeInterval MTRTimeIntervalForEventTimestampValue(uint64_t timeValue)
{
// Note: The event timestamp value as written in the spec is in microseconds, but the released 1.0 SDK implemented it in
// milliseconds. The following issue was filed to address the inconsistency:
// https://github.com/CHIP-Specifications/connectedhomeip-spec/issues/6236
// For consistency with the released behavior, calculations here will be done in milliseconds.

// First convert the event timestamp value (in milliseconds) to NSTimeInterval - to minimize potential loss of precision
// of uint64 => NSTimeInterval (double), convert whole seconds and remainder separately and then combine
uint64_t eventTimestampValueSeconds = timeValue / chip::kMillisecondsPerSecond;
uint64_t eventTimestampValueRemainderMilliseconds = timeValue % chip::kMillisecondsPerSecond;
NSTimeInterval eventTimestampValueRemainder
= NSTimeInterval(eventTimestampValueRemainderMilliseconds) / chip::kMillisecondsPerSecond;
NSTimeInterval eventTimestampValue = eventTimestampValueSeconds + eventTimestampValueRemainder;

return eventTimestampValue;
}

BOOL MTRPriorityLevelIsValid(chip::app::PriorityLevel priorityLevel)
{
return (priorityLevel >= chip::app::PriorityLevel::Debug) && (priorityLevel <= chip::app::PriorityLevel::Critical);
}

MTREventPriority MTREventPriorityForValidPriorityLevel(chip::app::PriorityLevel priorityLevel)
{
switch (priorityLevel) {
case chip::app::PriorityLevel::Debug:
return MTREventPriorityDebug;
case chip::app::PriorityLevel::Info:
return MTREventPriorityInfo;
default:
return MTREventPriorityCritical;
}
}

#pragma mark - SubscriptionCallback class declaration
using namespace chip;
using namespace chip::app;
Expand Down Expand Up @@ -1214,43 +1173,14 @@ - (void)invokeCommandWithEndpointID:(NSNumber *)endpointID
MTRErrorKey : [MTRError errorForCHIPErrorCode:CHIP_ERROR_INVALID_ARGUMENT]
}];
} else {
id value = MTRDecodeDataValueDictionaryFromCHIPTLV(apData);
if (value) {
// Construct the right type, and key/value depending on the type
NSNumber * eventTimeType;
NSString * timestampKey;
id timestampValue;
if (aEventHeader.mTimestamp.mType == Timestamp::Type::kSystem) {
eventTimeType = @(MTREventTimeTypeSystemUpTime);
timestampKey = MTREventSystemUpTimeKey;
timestampValue = @(MTRTimeIntervalForEventTimestampValue(aEventHeader.mTimestamp.mValue));
} else if (aEventHeader.mTimestamp.mType == Timestamp::Type::kEpoch) {
eventTimeType = @(MTREventTimeTypeTimestampDate);
timestampKey = MTREventTimestampDateKey;
timestampValue =
[NSDate dateWithTimeIntervalSince1970:MTRTimeIntervalForEventTimestampValue(aEventHeader.mTimestamp.mValue)];
} else {
MTR_LOG_INFO(
"%@ Unsupported event timestamp type %u - ignoring", eventPath, (unsigned int) aEventHeader.mTimestamp.mType);
ReportError(CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
return;
}

if (!MTRPriorityLevelIsValid(aEventHeader.mPriorityLevel)) {
MTR_LOG_INFO("%@ Unsupported event priority %u - ignoring", eventPath, (unsigned int) aEventHeader.mPriorityLevel);
ReportError(CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
return;
}

[mEventReports addObject:@{
MTREventPathKey : eventPath,
MTRDataKey : value,
MTREventNumberKey : @(aEventHeader.mEventNumber),
MTREventPriorityKey : @(MTREventPriorityForValidPriorityLevel(aEventHeader.mPriorityLevel)),
MTREventTimeTypeKey : eventTimeType,
timestampKey : timestampValue
}];
id value;
if (apData == nullptr) {
value = nil;
} else {
value = MTRDecodeDataValueDictionaryFromCHIPTLV(apData);
}

[mEventReports addObject:[MTRBaseDevice eventReportForHeader:aEventHeader andData:value]];
}
}

Expand Down
7 changes: 0 additions & 7 deletions src/darwin/Framework/CHIP/MTRDevice_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,4 @@ typedef void (^MTRDevicePerformAsyncBlock)(MTRBaseDevice * baseDevice);
// Returns min or max, if it is below or above, respectively.
NSNumber * MTRClampedNumber(NSNumber * aNumber, NSNumber * min, NSNumber * max);

#pragma mark - Utility for time conversion
NSTimeInterval MTRTimeIntervalForEventTimestampValue(uint64_t timeValue);

#pragma mark - Utility for event priority conversion
BOOL MTRPriorityLevelIsValid(chip::app::PriorityLevel priorityLevel);
MTREventPriority MTREventPriorityForValidPriorityLevel(chip::app::PriorityLevel);

NS_ASSUME_NONNULL_END
Loading

0 comments on commit 1071929

Please sign in to comment.