Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Darwin: Pass event timestamp to MTRDeviceDelegate #24858

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion src/darwin/Framework/CHIP/MTRBaseDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -429,11 +429,21 @@ API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4))
@property (nonatomic, readonly, copy, nullable) NSError * error;
@end

typedef NS_ENUM(NSUInteger, MTREventTimeType) { MTREventTimeTypeSystemUpTime = 0, MTREventTimeTypeTimestampDate };
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved

@interface MTREventReport : NSObject
@property (nonatomic, readonly, copy) MTREventPath * path;
@property (nonatomic, readonly, copy) NSNumber * eventNumber; // EventNumber type (uint64_t)
@property (nonatomic, readonly, copy) NSNumber * priority; // PriorityLevel type (uint8_t)
@property (nonatomic, readonly, copy) NSNumber * timestamp; // Timestamp type (uint64_t)

// Either systemUpTime or timestampDate will be valid depending on eventTimeType
@property (nonatomic, readonly) MTREventTimeType eventTimeType;
MTR_NEWLY_AVAILABLE;
@property (nonatomic, readonly) NSTimeInterval systemUpTime;
MTR_NEWLY_AVAILABLE;
@property (nonatomic, readonly, copy, nullable) NSDate * timestampDate;
MTR_NEWLY_AVAILABLE;
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved

// An instance of one of the event payload interfaces.
@property (nonatomic, readonly, copy) id value;

Expand Down Expand Up @@ -543,4 +553,9 @@ API_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4))

@end

@interface MTREventReport (Deprecated)
@property (nonatomic, readonly, copy) NSNumber * timestamp;
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved
MTR_NEWLY_DEPRECATED("Please use timestampDate and systemUpTime")
@end

NS_ASSUME_NONNULL_END
34 changes: 29 additions & 5 deletions src/darwin/Framework/CHIP/MTRBaseDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2042,26 +2042,49 @@ - (instancetype)initWithPath:(const ConcreteDataAttributePath &)path value:(id _
}
@end

@interface MTREventReport () {
NSNumber * _timestampValue;
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved
}
@end

@implementation MTREventReport
- (instancetype)initWithPath:(const ConcreteEventPath &)path
- (instancetype)initWithPath:(const chip::app::ConcreteEventPath &)path
eventNumber:(NSNumber *)eventNumber
priority:(NSNumber *)priority
timestamp:(NSNumber *)timestamp
timestampType:(NSNumber *)timestampType
timestampValue:(NSNumber *)timestampValue
value:(id _Nullable)value
error:(NSError * _Nullable)error
error:(NSError * _Nullable)error;
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved
{
if (self = [super init]) {
_path = [[MTREventPath alloc] initWithPath:path];
_eventNumber = eventNumber;
_priority = priority;
_timestamp = timestamp;
_timestampValue = timestampValue;
if ((Timestamp::Type) timestampType.unsignedIntegerValue == Timestamp::Type::kSystem) {
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved
_eventTimeType = MTREventTimeTypeSystemUpTime;
_systemUpTime = MTRTimeIntervalForEventTimestampValue(timestampValue.unsignedLongLongValue);
} else if ((Timestamp::Type) timestampType.unsignedIntegerValue == Timestamp::Type::kSystem) {
_eventTimeType = MTREventTimeTypeTimestampDate;
_timestampDate =
[NSDate dateWithTimeIntervalSince1970:MTRTimeIntervalForEventTimestampValue(timestampValue.unsignedLongLongValue)];
} else {
return nil;
}
_value = value;
_error = error;
}
return self;
}
@end

@implementation MTREventReport (Deprecated)
- (NSNumber *)timestamp
{
return _timestampValue;
}
@end

namespace {
void SubscriptionCallback::OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus)
{
Expand Down Expand Up @@ -2094,7 +2117,8 @@ - (instancetype)initWithPath:(const ConcreteEventPath &)path
[mEventReports addObject:[[MTREventReport alloc] initWithPath:aEventHeader.mPath
eventNumber:@(aEventHeader.mEventNumber)
priority:@((uint8_t) aEventHeader.mPriorityLevel)
timestamp:@(aEventHeader.mTimestamp.mValue)
timestampType:@((NSUInteger) aEventHeader.mTimestamp.mType)
timestampValue:@(aEventHeader.mTimestamp.mValue)
value:value
error:error]];
}
Expand Down
3 changes: 2 additions & 1 deletion src/darwin/Framework/CHIP/MTRBaseDevice_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ static inline MTRTransportType MTRMakeTransportType(chip::Transport::Type type)
- (instancetype)initWithPath:(const chip::app::ConcreteEventPath &)path
eventNumber:(NSNumber *)eventNumber
priority:(NSNumber *)priority
timestamp:(NSNumber *)timestamp
timestampType:(NSNumber *)timestampType
timestampValue:(NSNumber *)timestampValue
value:(id _Nullable)value
error:(NSError * _Nullable)error;
@end
Expand Down
30 changes: 30 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ typedef NS_ENUM(NSUInteger, MTRDeviceState) {
*/
@property (nonatomic, readonly) MTRDeviceState state;

/**
* The estimated device system start time.
*
* A device can reports its events with either calendar time or time since system start time. When events are reported with time
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved
* since system start time, this property will return an estimation of the device system start time. Because a device may report
* timestamp this way due to the lack of a wall clock, system start time can only be estimated based on event receive time and the
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved
* timestamp value, and this estimation may change over time, but only toward an earlier date.
*
* If events are always reported with calendar time, then this property will return nil.
*/
@property (nonatomic, readonly, nullable) NSDate * estimatedStartTime MTR_NEWLY_AVAILABLE;

/**
* Set the delegate to receive asynchronous callbacks about the device.
*
Expand Down Expand Up @@ -168,6 +180,12 @@ typedef NS_ENUM(NSUInteger, MTRDeviceState) {

@end

extern NSString * const MTREventNumberKey MTR_NEWLY_AVAILABLE;
extern NSString * const MTREventPriorityKey MTR_NEWLY_AVAILABLE;
extern NSString * const MTREventTimeTypeKey MTR_NEWLY_AVAILABLE;
extern NSString * const MTREventSystemUpTimeKey MTR_NEWLY_AVAILABLE;
extern NSString * const MTREventTimestampDateKey MTR_NEWLY_AVAILABLE;

@protocol MTRDeviceDelegate <NSObject>
@required
/**
Expand All @@ -186,6 +204,18 @@ typedef NS_ENUM(NSUInteger, MTRDeviceState) {
* Notifies delegate of event reports from the MTRDevice
*
* @param eventReport An array of response-value objects as described in MTRDeviceResponseHandler
*
* In addition to the MTREventPathKey and MTRDataKey containing the path and event values, eventReport also contains
* these keys:
*
* MTREventNumberKey : NSNumber-wrapped uint64_t value.
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved
* MTREventPriorityKey : NSNumber-wrapped uint8_t value.
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved
* MTREventTimeTypeKey : NSNumber-wrapped MTREventTimeType value.
* MTREventSystemUpTimeKey : NSNumber-wrapped NSTimeInterval value.
* MTREventTimestampDateKey : NSDate object.
*
* Only one of MTREventTimestampDateKey and MTREventSystemUpTimeKey will be present, depending on the value for
* MTREvenTimeTypeKey.
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved
*/
- (void)device:(MTRDevice *)device receivedEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventReport;

Expand Down
75 changes: 73 additions & 2 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
#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 @@ -102,6 +108,18 @@ - (id)strongObject
return aNumber;
}

NSTimeInterval MTRTimeIntervalForEventTimestampValue(uint64_t timeValue)
{
// First convert the event timestamp value (in milliseconds) to NSTimeInterval - to minimize potential loss of precision
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved
// of uint64 => NSTimeInterval (double), convert whole seconds and remainder separately and then combine
NSTimeInterval eventTimestampValueSeconds = (NSTimeInterval)(timeValue / chip::kMillisecondsPerSecond);
bzbarsky-apple marked this conversation as resolved.
Show resolved Hide resolved
NSTimeInterval eventTimestampValueRemainder
= ((NSTimeInterval)(timeValue % chip::kMillisecondsPerSecond)) / chip::kMillisecondsPerSecond;
NSTimeInterval eventTimestampValue = eventTimestampValueSeconds + eventTimestampValueRemainder;

return eventTimestampValue;
}

#pragma mark - SubscriptionCallback class declaration
using namespace chip;
using namespace chip::app;
Expand Down Expand Up @@ -347,7 +365,33 @@ - (void)_handleEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventRepor
{
os_unfair_lock_lock(&self->_lock);

// first combine with previous unreported events, if they exist
// If event time is of MTREventTimeTypeSystemUpTime type, then update estimated start time as needed
NSDate * oldEstimatedStartTime = _estimatedStartTime;
for (NSDictionary<NSString *, id> * eventDict in eventReport) {
NSNumber * eventTimeTypeNumber = eventDict[MTREventTimeTypeKey];
if (!eventTimeTypeNumber) {
MTR_LOG_ERROR("Event %@ missing event time type", eventDict);
continue;
}
MTREventTimeType eventTimeType = (MTREventTimeType) eventTimeTypeNumber.unsignedIntegerValue;
if (eventTimeType == MTREventTimeTypeSystemUpTime) {
NSNumber * eventTimeValueNumber = eventDict[MTREventSystemUpTimeKey];
if (!eventTimeValueNumber) {
MTR_LOG_ERROR("Event %@ missing event time value", eventDict);
continue;
}
NSTimeInterval eventTimeValue = eventTimeValueNumber.doubleValue;
NSDate * potentialSystemStartTime = [NSDate dateWithTimeIntervalSinceNow:-eventTimeValue];
if (!_estimatedStartTime || ([potentialSystemStartTime compare:_estimatedStartTime] == NSOrderedAscending)) {
_estimatedStartTime = potentialSystemStartTime;
jtung-apple marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
if (oldEstimatedStartTime != _estimatedStartTime) {
MTR_LOG_INFO("%@ updated estimated start time to %@", self, _estimatedStartTime);
}

// Combine with previous unreported events, if they exist
if (_unreportedEvents) {
eventReport = [_unreportedEvents arrayByAddingObjectsFromArray:eventReport];
_unreportedEvents = nil;
Expand Down Expand Up @@ -950,7 +994,34 @@ - (void)invokeCommandWithEndpointID:(NSNumber *)endpointID
} else {
id value = MTRDecodeDataValueDictionaryFromCHIPTLV(apData);
if (value) {
[mEventReports addObject:@ { MTREventPathKey : eventPath, MTRDataKey : 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;
}

[mEventReports addObject:@{
MTREventPathKey : eventPath,
MTRDataKey : value,
MTREventNumberKey : @(aEventHeader.mEventNumber),
MTREventPriorityKey : @((uint8_t) aEventHeader.mPriorityLevel),
MTREventTimeTypeKey : eventTimeType,
timestampKey : timestampValue
}];
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/darwin/Framework/CHIP/MTRDevice_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,7 @@ 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);

NS_ASSUME_NONNULL_END
17 changes: 15 additions & 2 deletions src/darwin/Framework/CHIPTests/MTRDeviceTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -1388,8 +1388,21 @@ - (void)test017_TestMTRDeviceBasics
};

__block unsigned eventReportsReceived = 0;
delegate.onEventDataReceived = ^(NSArray<NSDictionary<NSString *, id> *> * data) {
eventReportsReceived += data.count;
delegate.onEventDataReceived = ^(NSArray<NSDictionary<NSString *, id> *> * eventReport) {
eventReportsReceived += eventReport.count;

for (NSDictionary<NSString *, id> * eventDict in eventReport) {
NSNumber * eventTimeTypeNumber = eventDict[MTREventTimeTypeKey];
XCTAssertNotNil(eventTimeTypeNumber);
MTREventTimeType eventTimeType = (MTREventTimeType) eventTimeTypeNumber.unsignedIntegerValue;
XCTAssert((eventTimeType == MTREventTimeTypeSystemUpTime) || (eventTimeType == MTREventTimeTypeTimestampDate));
if (eventTimeType == MTREventTimeTypeSystemUpTime) {
XCTAssertNotNil(eventDict[MTREventSystemUpTimeKey]);
XCTAssertNotNil(device.estimatedStartTime);
} else if (eventTimeType == MTREventTimeTypeTimestampDate) {
XCTAssertNotNil(eventDict[MTREventTimestampDateKey]);
}
}
};

[device setDelegate:delegate queue:queue];
Expand Down