Skip to content

Commit

Permalink
Darwin: Pass event timestamp to MTRDeviceDelegate
Browse files Browse the repository at this point in the history
  • Loading branch information
jtung-apple committed Feb 7, 2023
1 parent 5d2f1a1 commit 1098872
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 11 deletions.
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 };

@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;

// 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;
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;
}
@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;
{
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) {
_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
* 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
* 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.
* MTREventPriorityKey : NSNumber-wrapped uint8_t value.
* 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.
*/
- (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
// of uint64 => NSTimeInterval (double), convert whole seconds and remainder separately and then combine
NSTimeInterval eventTimestampValueSeconds = (NSTimeInterval)(timeValue / chip::kMillisecondsPerSecond);
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;
}
}
}
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

0 comments on commit 1098872

Please sign in to comment.