From f4f1e19b0d9dda19e1fc972ed14331c4f6c07553 Mon Sep 17 00:00:00 2001 From: Justin Wood Date: Tue, 19 Mar 2024 11:20:22 -0700 Subject: [PATCH 01/11] Moving more Darwin stuff to std::lock_guard lock(_lock); (#32558) * Initial commit * Restyled by whitespace * Reverting this one * Addressing feedback * Adding a few more * Update src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue.mm * Removing this one * Restyled by clang-format * Reverting these --------- Co-authored-by: Restyled.io --- .../CHIP/MTRAsyncCallbackWorkQueue.mm | 30 +++---- .../Framework/CHIP/MTRAsyncWorkQueue.mm | 14 ++-- src/darwin/Framework/CHIP/MTRDevice.mm | 81 ++++++------------- .../Framework/CHIP/MTRDeviceController.mm | 7 +- .../CHIP/MTRDeviceControllerFactory.mm | 9 +-- src/darwin/Framework/CHIP/MTRLogging.mm | 4 +- 6 files changed, 48 insertions(+), 97 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue.mm b/src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue.mm index 00b6d33788f6c0..1e44b4a04ef702 100644 --- a/src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue.mm +++ b/src/darwin/Framework/CHIP/MTRAsyncCallbackWorkQueue.mm @@ -23,6 +23,8 @@ #import #import "MTRLogging_Internal.h" +#import "MTRUnfairLock.h" + #import #pragma mark - Class extensions @@ -72,14 +74,10 @@ - (instancetype)initWithContext:(id)context queue:(dispatch_queue_t)queue - (NSString *)description { - os_unfair_lock_lock(&_lock); + std::lock_guard lock(_lock); - auto * desc = [NSString + return [NSString stringWithFormat:@"MTRAsyncCallbackWorkQueue context: %@ items count: %lu", self.context, (unsigned long) self.items.count]; - - os_unfair_lock_unlock(&_lock); - - return desc; } - (void)enqueueWorkItem:(MTRAsyncCallbackQueueWorkItem *)item @@ -91,12 +89,11 @@ - (void)enqueueWorkItem:(MTRAsyncCallbackQueueWorkItem *)item [item markEnqueued]; - os_unfair_lock_lock(&_lock); + std::lock_guard lock(_lock); item.workQueue = self; [self.items addObject:item]; [self _callNextReadyWorkItem]; - os_unfair_lock_unlock(&_lock); } - (void)invalidate @@ -115,11 +112,10 @@ - (void)invalidate // called after executing a work item - (void)_postProcessWorkItem:(MTRAsyncCallbackQueueWorkItem *)workItem retry:(BOOL)retry { - os_unfair_lock_lock(&_lock); + std::lock_guard lock(_lock); // sanity check if running if (!self.runningWorkItemCount) { // something is wrong with state - nothing is currently running - os_unfair_lock_unlock(&_lock); MTR_LOG_ERROR("MTRAsyncCallbackWorkQueue endWork: no work is running on work queue"); return; } @@ -129,7 +125,6 @@ - (void)_postProcessWorkItem:(MTRAsyncCallbackQueueWorkItem *)workItem retry:(BO MTRAsyncCallbackQueueWorkItem * firstWorkItem = self.items.firstObject; if (firstWorkItem != workItem) { // something is wrong with this work item - should not be currently running - os_unfair_lock_unlock(&_lock); MTR_LOG_ERROR("MTRAsyncCallbackWorkQueue endWork: work item is not first on work queue"); return; } @@ -142,7 +137,6 @@ - (void)_postProcessWorkItem:(MTRAsyncCallbackQueueWorkItem *)workItem retry:(BO // when "concurrency width" is implemented this will be decremented instead self.runningWorkItemCount = 0; [self _callNextReadyWorkItem]; - os_unfair_lock_unlock(&_lock); } - (void)endWork:(MTRAsyncCallbackQueueWorkItem *)workItem @@ -203,34 +197,30 @@ - (void)_invalidate - (void)invalidate { - os_unfair_lock_lock(&_lock); + std::lock_guard lock(_lock); [self _invalidate]; - os_unfair_lock_unlock(&_lock); } - (void)markEnqueued { - os_unfair_lock_lock(&_lock); + std::lock_guard lock(_lock); _enqueued = YES; - os_unfair_lock_unlock(&_lock); } - (void)setReadyHandler:(MTRAsyncCallbackReadyHandler)readyHandler { - os_unfair_lock_lock(&_lock); + std::lock_guard lock(_lock); if (!_enqueued) { _readyHandler = readyHandler; } - os_unfair_lock_unlock(&_lock); } - (void)setCancelHandler:(dispatch_block_t)cancelHandler { - os_unfair_lock_lock(&_lock); + std::lock_guard lock(_lock); if (!_enqueued) { _cancelHandler = cancelHandler; } - os_unfair_lock_unlock(&_lock); } - (void)endWork diff --git a/src/darwin/Framework/CHIP/MTRAsyncWorkQueue.mm b/src/darwin/Framework/CHIP/MTRAsyncWorkQueue.mm index ff7242c3582377..be839561c1be33 100644 --- a/src/darwin/Framework/CHIP/MTRAsyncWorkQueue.mm +++ b/src/darwin/Framework/CHIP/MTRAsyncWorkQueue.mm @@ -18,6 +18,7 @@ #import "MTRAsyncWorkQueue.h" #import "MTRDefines_Internal.h" #import "MTRLogging_Internal.h" +#import "MTRUnfairLock.h" #import #import @@ -273,13 +274,12 @@ - (void)enqueueWorkItem:(MTRAsyncWorkItem *)item - (void)invalidate { ContextSnapshot context(_context); // outside of lock - os_unfair_lock_lock(&_lock); + std::lock_guard lock(_lock); MTR_LOG_INFO("MTRAsyncWorkQueue<%@> invalidate %tu items", context.description, _items.count); for (MTRAsyncWorkItem * item in _items) { [item cancel]; } [_items removeAllObjects]; - os_unfair_lock_unlock(&_lock); } - (void)_postProcessWorkItem:(MTRAsyncWorkItem *)workItem @@ -376,8 +376,7 @@ - (void)_callNextReadyWorkItemWithContext:(ContextSnapshot const &)context - (BOOL)hasDuplicateForTypeID:(NSUInteger)opaqueDuplicateTypeID workItemData:(id)opaqueWorkItemData { - BOOL hasDuplicate = NO; - os_unfair_lock_lock(&_lock); + std::lock_guard lock(_lock); // Start from the last item for (MTRAsyncWorkItem * item in [_items reverseObjectEnumerator]) { auto duplicateCheckHandler = item.duplicateCheckHandler; @@ -386,13 +385,12 @@ - (BOOL)hasDuplicateForTypeID:(NSUInteger)opaqueDuplicateTypeID workItemData:(id BOOL isDuplicate = NO; duplicateCheckHandler(opaqueWorkItemData, &isDuplicate, &stop); if (stop) { - hasDuplicate = isDuplicate; - break; + return isDuplicate; } } } - os_unfair_lock_unlock(&_lock); - return hasDuplicate; + + return NO; } @end diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 98387bfde797fb..a63902f68d3711 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -352,22 +352,19 @@ - (void)_updateDeviceTimeAndScheduleNextUpdate - (void)_performScheduledTimeUpdate { - os_unfair_lock_lock(&self->_timeSyncLock); + std::lock_guard lock(_timeSyncLock); // Device needs to still be reachable if (self.state != MTRDeviceStateReachable) { MTR_LOG_DEBUG("%@ Device is not reachable, canceling Device Time Updates.", self); - os_unfair_lock_unlock(&self->_timeSyncLock); return; } // Device must not be invalidated if (!self.timeUpdateScheduled) { MTR_LOG_DEBUG("%@ Device Time Update is no longer scheduled, MTRDevice may have been invalidated.", self); - os_unfair_lock_unlock(&self->_timeSyncLock); return; } self.timeUpdateScheduled = NO; [self _updateDeviceTimeAndScheduleNextUpdate]; - os_unfair_lock_unlock(&self->_timeSyncLock); } - (NSArray *)_endpointsWithTimeSyncClusterServer @@ -504,7 +501,7 @@ - (void)setDelegate:(id)delegate queue:(dispatch_queue_t)queu } #endif - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); _weakDelegate = [MTRWeakReference weakReferenceWithObject:delegate]; _delegateQueue = queue; @@ -517,8 +514,6 @@ - (void)setDelegate:(id)delegate queue:(dispatch_queue_t)queu if (setUpSubscription) { [self _setupSubscription]; } - - os_unfair_lock_unlock(&self->_lock); } - (void)invalidate @@ -579,11 +574,9 @@ - (void)nodeMayBeAdvertisingOperational // Return YES if there's a valid delegate AND subscription is expected to report value - (BOOL)_subscriptionAbleToReport { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); id delegate = _weakDelegate.strongObject; - auto state = _state; - os_unfair_lock_unlock(&self->_lock); - return (delegate != nil) && (state == MTRDeviceStateReachable); + return (delegate != nil) && (_state == MTRDeviceStateReachable); } - (BOOL)_callDelegateWithBlock:(void (^)(id))block @@ -667,41 +660,35 @@ - (void)_handleSubscriptionEstablished - (void)_handleSubscriptionError:(NSError *)error { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); _internalDeviceState = MTRInternalDeviceStateUnsubscribed; _unreportedEvents = nil; [self _changeState:MTRDeviceStateUnreachable]; - - os_unfair_lock_unlock(&self->_lock); } - (void)_handleResubscriptionNeeded { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); [self _changeState:MTRDeviceStateUnknown]; - - os_unfair_lock_unlock(&self->_lock); } - (void)_handleSubscriptionReset { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); // if there is no delegate then also do not retry id delegate = _weakDelegate.strongObject; if (!delegate) { // NOTE: Do not log anythig here: we have been invalidated, and the // Matter stack might already be torn down. - os_unfair_lock_unlock(&self->_lock); return; } // don't schedule multiple retries if (self.reattemptingSubscription) { MTR_LOG_DEFAULT("%@ already reattempting subscription", self); - os_unfair_lock_unlock(&self->_lock); return; } @@ -722,8 +709,6 @@ - (void)_handleSubscriptionReset [self _reattemptSubscriptionNowIfNeeded]; os_unfair_lock_unlock(&self->_lock); }); - - os_unfair_lock_unlock(&self->_lock); } - (void)_reattemptSubscriptionNowIfNeeded @@ -740,7 +725,7 @@ - (void)_reattemptSubscriptionNowIfNeeded - (void)_handleUnsolicitedMessageFromPublisher { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); [self _changeState:MTRDeviceStateReachable]; @@ -759,8 +744,6 @@ - (void)_handleUnsolicitedMessageFromPublisher // ReadClient how did we get this notification and if we _do_ have an active // ReadClient, this call or _setupSubscription would be no-ops. [self _reattemptSubscriptionNowIfNeeded]; - - os_unfair_lock_unlock(&self->_lock); } - (void)_markDeviceAsUnreachableIfNotSusbcribed @@ -776,7 +759,8 @@ - (void)_markDeviceAsUnreachableIfNotSusbcribed - (void)_handleReportBegin { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); + _receivingReport = YES; if (_state != MTRDeviceStateReachable) { _receivingPrimingReport = YES; @@ -784,12 +768,11 @@ - (void)_handleReportBegin } else { _receivingPrimingReport = NO; } - os_unfair_lock_unlock(&self->_lock); } - (void)_handleReportEnd { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); _receivingReport = NO; _receivingPrimingReport = NO; _estimatedStartTimeFromGeneralDiagnosticsUpTime = nil; @@ -805,7 +788,6 @@ - (void)_handleReportEnd }); } #endif - os_unfair_lock_unlock(&self->_lock); } // assume lock is held @@ -824,12 +806,10 @@ - (void)_reportAttributes:(NSArray *> *)attributes - (void)_handleAttributeReport:(NSArray *> *)attributeReport { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); // _getAttributesToReportWithReportedValues will log attribute paths reported [self _reportAttributes:[self _getAttributesToReportWithReportedValues:attributeReport]]; - - os_unfair_lock_unlock(&self->_lock); } #ifdef DEBUG @@ -843,7 +823,7 @@ - (void)unitTestInjectEventReport:(NSArray *> *)eve - (void)_handleEventReport:(NSArray *> *)eventReport { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); NSDate * oldEstimatedStartTime = _estimatedStartTime; // Combine with previous unreported events, if they exist @@ -937,14 +917,13 @@ - (void)_handleEventReport:(NSArray *> *)eventRepor // save unreported events _unreportedEvents = reportToReturn; } - - os_unfair_lock_unlock(&self->_lock); } - (NSDictionary *)_getCachedDataVersions { NSMutableDictionary * dataVersions = [NSMutableDictionary dictionary]; - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); + for (MTRAttributePath * path in _readCache) { NSDictionary * dataValue = _readCache[path]; NSNumber * dataVersionNumber = dataValue[MTRDataVersionKey]; @@ -958,7 +937,6 @@ - (void)_handleEventReport:(NSArray *> *)eventRepor } } MTR_LOG_INFO("%@ _getCachedDataVersions dataVersions count: %lu readCache count: %lu", self, static_cast(dataVersions.count), static_cast(_readCache.count)); - os_unfair_lock_unlock(&self->_lock); return dataVersions; } @@ -1083,11 +1061,12 @@ - (void)_setupSubscription os_unfair_lock_lock(&self->_lock); self->_currentReadClient = nullptr; self->_currentSubscriptionCallback = nullptr; - os_unfair_lock_unlock(&self->_lock); + dispatch_async(self.queue, ^{ // OnDone [self _handleSubscriptionReset]; }); + os_unfair_lock_unlock(&self->_lock); }, ^(void) { MTR_LOG_DEFAULT("%@ got unsolicited message from publisher", self); @@ -1855,18 +1834,16 @@ - (void)_checkExpiredExpectedValues - (void)_performScheduledExpirationCheck { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); self.expirationCheckScheduled = NO; [self _checkExpiredExpectedValues]; - - os_unfair_lock_unlock(&self->_lock); } // Get attribute value dictionary for an attribute path from the right cache - (NSDictionary *)_attributeValueDictionaryForAttributePath:(MTRAttributePath *)attributePath { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); // First check expected value cache NSArray * expectedValue = _expectedValueCache[attributePath]; @@ -1876,8 +1853,6 @@ - (void)_performScheduledExpirationCheck // expired - purge and fall through _expectedValueCache[attributePath] = nil; } else { - os_unfair_lock_unlock(&self->_lock); - // not yet expired - return result return expectedValue[MTRDeviceExpectedValueFieldValueIndex]; } @@ -1886,8 +1861,6 @@ - (void)_performScheduledExpirationCheck // Then check read cache NSDictionary * cachedAttributeValue = _readCache[attributePath]; if (cachedAttributeValue) { - os_unfair_lock_unlock(&self->_lock); - return cachedAttributeValue; } else { // TODO: when not found in cache, generated default values should be used @@ -1895,8 +1868,6 @@ - (void)_performScheduledExpirationCheck attributePath); } - os_unfair_lock_unlock(&self->_lock); - return nil; } @@ -2024,7 +1995,7 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray *)attributeValues reportChanges:(BOOL)reportChanges { MTR_LOG_INFO("%@ setAttributeValues count: %lu reportChanges: %d", self, static_cast(attributeValues.count), reportChanges); - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); if (reportChanges) { [self _reportAttributes:[self _getAttributesToReportWithReportedValues:attributeValues]]; @@ -2040,8 +2011,6 @@ - (void)setAttributeValues:(NSArray *)attributeValues reportChan if ([self _isCachePrimedWithInitialConfigurationData]) { _delegateDeviceCachePrimedCalled = YES; } - - os_unfair_lock_unlock(&self->_lock); } - (BOOL)deviceCachePrimed @@ -2166,7 +2135,7 @@ - (void)setExpectedValues:(NSArray *> *)values MTR_LOG_INFO( "%@ Setting expected values %@ with expiration time %f seconds from now", self, values, [expirationTime timeIntervalSinceNow]); - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); // _getAttributesToReportWithNewExpectedValues will log attribute paths reported NSArray * attributesToReport = [self _getAttributesToReportWithNewExpectedValues:values @@ -2175,24 +2144,22 @@ - (void)setExpectedValues:(NSArray *> *)values [self _reportAttributes:attributesToReport]; [self _checkExpiredExpectedValues]; - os_unfair_lock_unlock(&self->_lock); } - (void)removeExpectedValuesForAttributePaths:(NSArray *)attributePaths expectedValueID:(uint64_t)expectedValueID { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); + for (MTRAttributePath * attributePath in attributePaths) { [self _removeExpectedValueForAttributePath:attributePath expectedValueID:expectedValueID]; } - os_unfair_lock_unlock(&self->_lock); } - (void)removeExpectedValueForAttributePath:(MTRAttributePath *)attributePath expectedValueID:(uint64_t)expectedValueID { - os_unfair_lock_lock(&self->_lock); + std::lock_guard lock(_lock); [self _removeExpectedValueForAttributePath:attributePath expectedValueID:expectedValueID]; - os_unfair_lock_unlock(&self->_lock); } - (void)_removeExpectedValueForAttributePath:(MTRAttributePath *)attributePath expectedValueID:(uint64_t)expectedValueID diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index 7b7fd457447590..ae6669cdab5add 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -46,6 +46,7 @@ #import "MTRServerEndpoint_Internal.h" #import "MTRSetupPayload.h" #import "MTRTimeUtils.h" +#import "MTRUnfairLock.h" #import "NSDataSpanConversion.h" #import "NSStringSpanConversion.h" #import @@ -916,7 +917,7 @@ - (MTRBaseDevice *)baseDeviceForNodeID:(NSNumber *)nodeID - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID { - os_unfair_lock_lock(&_deviceMapLock); + std::lock_guard lock(_deviceMapLock); MTRDevice * deviceToReturn = _nodeIDToDeviceMap[nodeID]; if (!deviceToReturn) { deviceToReturn = [[MTRDevice alloc] initWithNodeID:nodeID controller:self]; @@ -935,14 +936,13 @@ - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID [deviceToReturn setAttributeValues:attributesFromCache reportChanges:NO]; } } - os_unfair_lock_unlock(&_deviceMapLock); return deviceToReturn; } - (void)removeDevice:(MTRDevice *)device { - os_unfair_lock_lock(&_deviceMapLock); + std::lock_guard lock(_deviceMapLock); auto * nodeID = device.nodeID; MTRDevice * deviceToRemove = _nodeIDToDeviceMap[nodeID]; if (deviceToRemove == device) { @@ -951,7 +951,6 @@ - (void)removeDevice:(MTRDevice *)device } else { MTR_LOG_ERROR("Error: Cannot remove device %p with nodeID %llu", device, nodeID.unsignedLongLongValue); } - os_unfair_lock_unlock(&_deviceMapLock); } - (void)setDeviceControllerDelegate:(id)delegate queue:(dispatch_queue_t)queue diff --git a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm index 2ccf89634646e8..3d84a8fea22571 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceControllerFactory.mm @@ -1121,10 +1121,8 @@ - (void)controllerShuttingDown:(MTRDeviceController *)controller - (NSArray *)getRunningControllers { - os_unfair_lock_lock(&_controllersLock); - NSArray * controllersCopy = [_controllers copy]; - os_unfair_lock_unlock(&_controllersLock); - return controllersCopy; + std::lock_guard lock(_controllersLock); + return [_controllers copy]; } - (nullable MTRDeviceController *)runningControllerForFabricIndex:(FabricIndex)fabricIndex @@ -1196,9 +1194,8 @@ - (BOOL)addServerEndpoint:(MTRServerEndpoint *)endpoint - (void)removeServerEndpoint:(MTRServerEndpoint *)endpoint { - os_unfair_lock_lock(&_serverEndpointsLock); + std::lock_guard lock(_serverEndpointsLock); [_serverEndpoints removeObject:endpoint]; - os_unfair_lock_unlock(&_serverEndpointsLock); } - (NSArray *)accessGrantsForFabricIndex:(chip::FabricIndex)fabricIndex clusterPath:(MTRClusterPath *)clusterPath diff --git a/src/darwin/Framework/CHIP/MTRLogging.mm b/src/darwin/Framework/CHIP/MTRLogging.mm index 23b310a37a05fa..e5529780b73f9c 100644 --- a/src/darwin/Framework/CHIP/MTRLogging.mm +++ b/src/darwin/Framework/CHIP/MTRLogging.mm @@ -19,6 +19,7 @@ #import "MTRLogging_Internal.h" #import "MTRFramework.h" +#import "MTRUnfairLock.h" #import #import @@ -56,7 +57,7 @@ void MTRSetLogCallback(MTRLogType logTypeThreshold, MTRLogCallback _Nullable cal { MTRFrameworkInit(); - os_unfair_lock_lock(&logCallbackLock); + std::lock_guard lock(logCallbackLock); if (callback) { SetLogRedirectCallback(&MTRLogCallbackTrampoline); SetLogFilter(static_cast(std::min(std::max(logTypeThreshold, MTRLogTypeError), MTRLogTypeDetail))); @@ -66,5 +67,4 @@ void MTRSetLogCallback(MTRLogType logTypeThreshold, MTRLogCallback _Nullable cal SetLogFilter(kLogCategory_None); SetLogRedirectCallback(nullptr); } - os_unfair_lock_unlock(&logCallbackLock); } From 82287051caf2bd8a80dce800f21f92a27c4f31dd Mon Sep 17 00:00:00 2001 From: Karsten Sperling <113487422+ksperling-apple@users.noreply.github.com> Date: Wed, 20 Mar 2024 09:55:34 +1300 Subject: [PATCH 02/11] CI: Don't run bootstrap if we restored from cache (#32610) * CI: Don't run bootstrap if we restored from cache Handle caching and log upload directly within the bootstrap action, and don't perform the actual bootstrap if we have a cache hit. Include the platform name in the cache key. * Silabs requirements are for non-Docker builds --- .github/actions/bootstrap-cache/action.yaml | 2 +- .github/actions/bootstrap/action.yaml | 35 +++++++++++++++++-- .../action.yaml | 9 +---- .../actions/upload-bootstrap-logs/action.yaml | 18 ---------- .github/workflows/cirque.yaml | 1 + .github/workflows/fuzzing-build.yaml | 8 ----- .github/workflows/release_artifacts.yaml | 8 ----- scripts/setup/bootstrap.sh | 20 +++++------ ...abs_docker.txt => requirements.silabs.txt} | 0 9 files changed, 46 insertions(+), 55 deletions(-) delete mode 100644 .github/actions/upload-bootstrap-logs/action.yaml rename scripts/setup/{requirements.silabs_docker.txt => requirements.silabs.txt} (100%) diff --git a/.github/actions/bootstrap-cache/action.yaml b/.github/actions/bootstrap-cache/action.yaml index b22ed1e0ac60bf..9a883ecf22da79 100644 --- a/.github/actions/bootstrap-cache/action.yaml +++ b/.github/actions/bootstrap-cache/action.yaml @@ -1,5 +1,5 @@ name: Bootstrap cache -description: Bootstrap cache +description: Bootstrap cache (deprecated) runs: using: "composite" steps: diff --git a/.github/actions/bootstrap/action.yaml b/.github/actions/bootstrap/action.yaml index 8f94830b8c0e51..32fe6c9cfedba3 100644 --- a/.github/actions/bootstrap/action.yaml +++ b/.github/actions/bootstrap/action.yaml @@ -5,9 +5,40 @@ inputs: description: "Platform name" required: false default: none + bootstrap-log-name: + description: "Bootstrap log name" + required: false + default: bootstrap-logs-${{ github.job }} + runs: using: "composite" steps: - - name: Bootstrap + - uses: Wandalen/wretry.action@v1.4.10 + name: Bootstrap from cache + id: bootstrap-cache + continue-on-error: true + with: + action: buildjet/cache@v4 + attempt_limit: 3 + attempt_delay: 2000 + with: | + key: ${{ runner.os }}-${{ inputs.platform }}-env-${{ hashFiles('scripts/setup/*', 'third_party/pigweed/**') }} + path: | + .environment + build_overrides/pigweed_environment.gni + + - name: Run bootstrap + if: fromJSON(steps.bootstrap-cache.outputs.outputs).cache-hit != 'true' # retry returns all outputs in `outputs` + env: + PW_NO_CIPD_CACHE_DIR: Y shell: bash - run: bash scripts/bootstrap.sh -p all,${{ inputs.platform }} + run: source scripts/bootstrap.sh -p all,${{ inputs.platform }} + + - name: Uploading bootstrap logs + uses: actions/upload-artifact@v3 + if: always() && !env.ACT && fromJSON(steps.bootstrap-cache.outputs.outputs).cache-hit != 'true' + with: + name: ${{ inputs.bootstrap-log-name }} + path: | + .environment/gn_out/.ninja_log + .environment/pigweed-venv/*.log diff --git a/.github/actions/checkout-submodules-and-bootstrap/action.yaml b/.github/actions/checkout-submodules-and-bootstrap/action.yaml index 7424ca529f74df..df3fdff1e02ce4 100644 --- a/.github/actions/checkout-submodules-and-bootstrap/action.yaml +++ b/.github/actions/checkout-submodules-and-bootstrap/action.yaml @@ -26,21 +26,14 @@ runs: with: platform: ${{ inputs.platform }} extra-parameters: ${{ inputs.extra-submodule-parameters }} - - name: Bootstrap Cache - uses: ./.github/actions/bootstrap-cache - name: Bootstrap uses: ./.github/actions/bootstrap - env: - PW_NO_CIPD_CACHE_DIR: Y with: platform: ${{ inputs.platform }} + bootstrap-log-name: ${{ inputs.bootstrap-log-name }} - name: Dump disk info after checkout submodule & Bootstrap shell: bash run: scripts/dump_diskspace_info.sh - - name: Upload Bootstrap Logs - uses: ./.github/actions/upload-bootstrap-logs - with: - bootstrap-log-name: ${{ inputs.bootstrap-log-name }} - name: Work around TSAN ASLR issues if: runner.os == 'Linux' && !env.ACT shell: bash diff --git a/.github/actions/upload-bootstrap-logs/action.yaml b/.github/actions/upload-bootstrap-logs/action.yaml deleted file mode 100644 index 522058c16ff93c..00000000000000 --- a/.github/actions/upload-bootstrap-logs/action.yaml +++ /dev/null @@ -1,18 +0,0 @@ -name: Upload bootstrap logs -description: Upload bootstrap logs -inputs: - bootstrap-log-name: - description: "Bootstrap log name" - required: false - default: bootstrap-logs-${{ github.job }} -runs: - using: "composite" - steps: - - name: Uploading bootstrap logs - uses: actions/upload-artifact@v4 - if: ${{ always() && !env.ACT }} - with: - name: ${{ inputs.bootstrap-log-name }} - path: | - .environment/gn_out/.ninja_log - .environment/pigweed-venv/*.log diff --git a/.github/workflows/cirque.yaml b/.github/workflows/cirque.yaml index fafe7265aea5d9..fcd49a2ab65337 100644 --- a/.github/workflows/cirque.yaml +++ b/.github/workflows/cirque.yaml @@ -57,6 +57,7 @@ jobs: with: platform: linux + # TODO: Is what's being cached here actually compatible with a regular bootstrap? - name: Bootstrap Cache uses: ./.github/actions/bootstrap-cache - name: Bootstrap Cirque diff --git a/.github/workflows/fuzzing-build.yaml b/.github/workflows/fuzzing-build.yaml index 341097915cab4f..aedbe66ddb0ae5 100644 --- a/.github/workflows/fuzzing-build.yaml +++ b/.github/workflows/fuzzing-build.yaml @@ -46,12 +46,8 @@ jobs: run: | mkdir objdir-clone || true - - name: Bootstrap Cache - uses: ./.github/actions/bootstrap-cache - name: Bootstrap uses: ./.github/actions/bootstrap - - name: Upload Bootstrap Logs - uses: ./.github/actions/upload-bootstrap-logs - name: Build all-clusters-app run: | @@ -84,12 +80,8 @@ jobs: run: | mkdir objdir-clone || true - - name: Bootstrap Cache - uses: ./.github/actions/bootstrap-cache - name: Bootstrap uses: ./.github/actions/bootstrap - - name: Upload Bootstrap Logs - uses: ./.github/actions/upload-bootstrap-logs - name: Build all-clusters-app run: | diff --git a/.github/workflows/release_artifacts.yaml b/.github/workflows/release_artifacts.yaml index 65896c73b3faca..78b0c34275430e 100644 --- a/.github/workflows/release_artifacts.yaml +++ b/.github/workflows/release_artifacts.yaml @@ -39,12 +39,8 @@ jobs: uses: actions/checkout@v4 with: ref: "${{ github.event.inputs.releaseTag }}" - - name: Bootstrap Cache - uses: ./.github/actions/bootstrap-cache - name: Bootstrap uses: ./.github/actions/bootstrap - - name: Upload Bootstrap Logs - uses: ./.github/actions/upload-bootstrap-logs - name: Build run: scripts/examples/esp_example.sh all-clusters-app @@ -74,12 +70,8 @@ jobs: uses: actions/checkout@v4 with: ref: "${{ github.event.inputs.releaseTag }}" - - name: Bootstrap Cache - uses: ./.github/actions/bootstrap-cache - name: Bootstrap uses: ./.github/actions/bootstrap - - name: Upload Bootstrap Logs - uses: ./.github/actions/upload-bootstrap-logs - name: Build example EFR32 Lock App run: scripts/examples/gn_silabs_example.sh examples/lock-app/efr32/ diff --git a/scripts/setup/bootstrap.sh b/scripts/setup/bootstrap.sh index 1b813216222efe..d723e86f70924a 100644 --- a/scripts/setup/bootstrap.sh +++ b/scripts/setup/bootstrap.sh @@ -21,14 +21,14 @@ _install_additional_pip_requirements() { # figure out additional pip install items while [ $# -gt 0 ]; do case $1 in - -p | --platform) - _SETUP_PLATFORM=$2 - shift # argument - shift # value - ;; - *) - shift - ;; + -p | --platform) + _SETUP_PLATFORM=$2 + shift # argument + shift # value + ;; + *) + shift + ;; esac done @@ -41,7 +41,7 @@ _install_additional_pip_requirements() { for platform in ${_SETUP_PLATFORM}; do # Allow none as an alias of nothing extra installed (like -p none) - if [ "$platform" != "none" ]; then + if [ "$platform" != "none" -a -e "$_CHIP_ROOT/scripts/setup/requirements.$platform.txt" ]; then echo "Installing pip requirements for $platform..." pip install -q \ -r "$_CHIP_ROOT/scripts/setup/requirements.$platform.txt" \ @@ -66,7 +66,7 @@ _bootstrap_or_activate() { local _BOOTSTRAP_NAME="${_BOOTSTRAP_PATH##*/}" local _BOOTSTRAP_DIR="${_BOOTSTRAP_PATH%/*}" # Strip off the 'scripts[/setup]' directory, leaving the root of the repo. - _CHIP_ROOT="$(cd "${_BOOTSTRAP_DIR%/setup}/.." > /dev/null && pwd)" + _CHIP_ROOT="$(cd "${_BOOTSTRAP_DIR%/setup}/.." >/dev/null && pwd)" local _CONFIG_FILE="scripts/setup/environment.json" diff --git a/scripts/setup/requirements.silabs_docker.txt b/scripts/setup/requirements.silabs.txt similarity index 100% rename from scripts/setup/requirements.silabs_docker.txt rename to scripts/setup/requirements.silabs.txt From 0ad1a366bfd5b66ee9d37e781da8bf3a02f83263 Mon Sep 17 00:00:00 2001 From: Ricardo Casallas <77841255+rcasallas-silabs@users.noreply.github.com> Date: Tue, 19 Mar 2024 17:08:13 -0400 Subject: [PATCH 03/11] [Silabs] Si917: C files converted to C++. (#32547) * [Silabs] Si917: C files converted to C++. * Restyled by clang-format --------- Co-authored-by: Restyled.io --- examples/platform/silabs/SiWx917/BUILD.gn | 4 +-- .../SiWx917/{sl_wifi_if.c => sl_wifi_if.cpp} | 30 ++++++++-------- .../{wfx_rsi_host.c => wfx_rsi_host.cpp} | 36 +++++++++---------- .../platform/silabs/efr32/rs911x/rs9117.gni | 4 +-- .../platform/silabs/efr32/rs911x/rs911x.gni | 2 +- .../platform/silabs/efr32/rs911x/sl_wifi_if.c | 1 - .../silabs/efr32/rs911x/sl_wifi_if.cpp | 1 + .../{wfx_rsi_host.c => wfx_rsi_host.cpp} | 0 8 files changed, 39 insertions(+), 39 deletions(-) rename examples/platform/silabs/SiWx917/SiWx917/{sl_wifi_if.c => sl_wifi_if.cpp} (98%) rename examples/platform/silabs/SiWx917/SiWx917/{wfx_rsi_host.c => wfx_rsi_host.cpp} (93%) delete mode 120000 examples/platform/silabs/efr32/rs911x/sl_wifi_if.c create mode 120000 examples/platform/silabs/efr32/rs911x/sl_wifi_if.cpp rename examples/platform/silabs/efr32/rs911x/{wfx_rsi_host.c => wfx_rsi_host.cpp} (100%) diff --git a/examples/platform/silabs/SiWx917/BUILD.gn b/examples/platform/silabs/SiWx917/BUILD.gn index 9214e97adef89e..73e70c18e1d7f8 100644 --- a/examples/platform/silabs/SiWx917/BUILD.gn +++ b/examples/platform/silabs/SiWx917/BUILD.gn @@ -219,8 +219,8 @@ source_set("siwx917-common") { "${silabs_plat_si91x_wifi_dir}/ethernetif.cpp", "${silabs_plat_si91x_wifi_dir}/lwip_netif.cpp", "${silabs_plat_si91x_wifi_dir}/wfx_notify.cpp", - "SiWx917/sl_wifi_if.c", - "SiWx917/wfx_rsi_host.c", + "SiWx917/sl_wifi_if.cpp", + "SiWx917/wfx_rsi_host.cpp", ] if (chip_enable_pw_rpc || chip_build_libshell || sl_uart_log_output) { diff --git a/examples/platform/silabs/SiWx917/SiWx917/sl_wifi_if.c b/examples/platform/silabs/SiWx917/SiWx917/sl_wifi_if.cpp similarity index 98% rename from examples/platform/silabs/SiWx917/SiWx917/sl_wifi_if.c rename to examples/platform/silabs/SiWx917/SiWx917/sl_wifi_if.cpp index 48b8f54ff540fa..c443bec49b072c 100644 --- a/examples/platform/silabs/SiWx917/SiWx917/sl_wifi_if.c +++ b/examples/platform/silabs/SiWx917/SiWx917/sl_wifi_if.cpp @@ -25,15 +25,11 @@ #include "FreeRTOS.h" #include "event_groups.h" #include "sl_board_configuration.h" -#include "sl_net.h" -#include "sl_si91x_host_interface.h" #include "sl_si91x_types.h" -#include "sl_wifi_callback_framework.h" #include "sl_wifi_constants.h" #include "sl_wifi_types.h" #include "sl_wlan_config.h" #include "task.h" -#include "wfx_host_events.h" #if (EXP_BOARD) #include "rsi_bt_common_apis.h" @@ -53,7 +49,6 @@ bool btn0_pressed = false; #endif // SL_ICD_ENABLED && SLI_SI91X_MCU_INTERFACE #include "dhcp_client.h" -#include "sl_wifi.h" #include "wfx_host_events.h" #include "wfx_rsi.h" #define ADV_SCAN_THRESHOLD -40 @@ -65,11 +60,17 @@ bool btn0_pressed = false; // TODO: Confirm that this value works for size and timing #define WFX_QUEUE_SIZE 10 - +extern "C" { +#include "sl_net.h" +#include "sl_si91x_host_interface.h" +#include "sl_wifi.h" +#include "sl_wifi_callback_framework.h" +#include "wfx_host_events.h" #if SLI_SI91X_MCU_INTERFACE #include "sl_si91x_trng.h" #define TRNGKEY_SIZE 4 #endif // SLI_SI91X_MCU_INTERFACE +} // extern "C" { WfxRsi_t wfx_rsi; @@ -553,7 +554,7 @@ sl_status_t show_scan_results(sl_wifi_scan_result_t * scan_result) SILABS_LOG("SCAN SSID: %s , ap scan: %s", wfx_rsi.scan_ssid, ap.ssid); if (strcmp(wfx_rsi.scan_ssid, ap.ssid) == CMP_SUCCESS) { - ap.security = scan_result->scan_info[x].security_mode; + ap.security = static_cast(scan_result->scan_info[x].security_mode); ap.rssi = (-1) * scan_result->scan_info[x].rssi_val; memcpy(&ap.bssid[0], &scan_result->scan_info[x].bssid[0], BSSID_MAX_STR_LEN); (*wfx_rsi.scan_cb)(&ap); @@ -562,7 +563,7 @@ sl_status_t show_scan_results(sl_wifi_scan_result_t * scan_result) } else { - ap.security = scan_result->scan_info[x].security_mode; + ap.security = static_cast(scan_result->scan_info[x].security_mode); ap.rssi = (-1) * scan_result->scan_info[x].rssi_val; memcpy(&ap.bssid[0], &scan_result->scan_info[x].bssid[0], BSSID_MAX_STR_LEN); (*wfx_rsi.scan_cb)(&ap); @@ -597,8 +598,7 @@ static void wfx_rsi_save_ap_info() // translation { sl_status_t status = SL_STATUS_OK; #ifndef EXP_BOARD // TODO: this changes will be reverted back after the SDK team fix the scan API - sl_wifi_scan_configuration_t wifi_scan_configuration = { 0 }; - wifi_scan_configuration = default_wifi_scan_configuration; + sl_wifi_scan_configuration_t wifi_scan_configuration = default_wifi_scan_configuration; #endif sl_wifi_ssid_t ssid_arg; ssid_arg.length = strlen(wfx_rsi.sec.ssid); @@ -682,10 +682,11 @@ static sl_status_t wfx_rsi_do_join(void) /* Call rsi connect call with given ssid and password * And check there is a success */ - sl_wifi_credential_t cred = { 0 }; - cred.type = SL_WIFI_PSK_CREDENTIAL; + sl_wifi_credential_t cred; + memset(&cred, 0, sizeof(sl_wifi_credential_t)); + cred.type = SL_WIFI_PSK_CREDENTIAL; memcpy(cred.psk.value, &wfx_rsi.sec.passkey[0], strlen(wfx_rsi.sec.passkey)); - sl_wifi_credential_id_t id = SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID; + sl_net_credential_id_t id = SL_NET_DEFAULT_WIFI_CLIENT_CREDENTIAL_ID; status = sl_net_set_credential(id, SL_NET_WIFI_PSK, &wfx_rsi.sec.passkey[0], strlen(wfx_rsi.sec.passkey)); if (SL_STATUS_OK != status) { @@ -851,7 +852,8 @@ void ProcessEvent(WfxEvent_t inEvent) if (!(wfx_rsi.dev_state & WFX_RSI_ST_SCANSTARTED)) { SILABS_LOG("%s: start SSID scan", __func__); - sl_wifi_scan_configuration_t wifi_scan_configuration = { 0 }; + sl_wifi_scan_configuration_t wifi_scan_configuration; + memset(&wifi_scan_configuration, 0, sizeof(sl_wifi_scan_configuration_t)); // TODO: Add scan logic sl_wifi_advanced_scan_configuration_t advanced_scan_configuration = { 0 }; diff --git a/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi_host.c b/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi_host.cpp similarity index 93% rename from examples/platform/silabs/SiWx917/SiWx917/wfx_rsi_host.c rename to examples/platform/silabs/SiWx917/SiWx917/wfx_rsi_host.cpp index 1711076e46bd1c..83c3a95ec0caf7 100644 --- a/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi_host.c +++ b/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi_host.cpp @@ -103,16 +103,14 @@ bool wfx_is_sta_mode_enabled(void) ***********************************************************************/ void wfx_get_wifi_mac_addr(sl_wfx_interface_t interface, sl_wfx_mac_address_t * addr) { - sl_wfx_mac_address_t * mac; - + if (addr) + { #ifdef SL_WFX_CONFIG_SOFTAP - mac = (interface == SL_WFX_SOFTAP_INTERFACE) ? &wfx_rsi.softap_mac : &wfx_rsi.sta_mac; + *addr = (interface == SL_WFX_SOFTAP_INTERFACE) ? wfx_rsi.softap_mac : wfx_rsi.sta_mac; #else - mac = &wfx_rsi.sta_mac; + *addr = wfx_rsi.sta_mac; #endif - *addr = *mac; - SILABS_LOG("%s: %02x:%02x:%02x:%02x:%02x:%02x", __func__, mac->octet[0], mac->octet[1], mac->octet[2], mac->octet[3], - mac->octet[4], mac->octet[5]); + } } /********************************************************************* @@ -125,10 +123,11 @@ void wfx_get_wifi_mac_addr(sl_wfx_interface_t interface, sl_wfx_mac_address_t * ***********************************************************************/ void wfx_set_wifi_provision(wfx_wifi_provision_t * cfg) { - SILABS_LOG("%s: SSID: %s", __func__, &wfx_rsi.sec.ssid[0]); - - wfx_rsi.sec = *cfg; - wfx_rsi.dev_state |= WFX_RSI_ST_STA_PROVISIONED; + if (cfg) + { + wfx_rsi.sec = *cfg; + wfx_rsi.dev_state |= WFX_RSI_ST_STA_PROVISIONED; + } } /********************************************************************* @@ -179,13 +178,13 @@ sl_status_t wfx_connect_to_ap(void) WfxEvent_t event; if (wfx_rsi.dev_state & WFX_RSI_ST_STA_PROVISIONED) { - SILABS_LOG("%s: connecting to access point -> SSID: %s", __func__, &wfx_rsi.sec.ssid[0]); + SILABS_LOG("Connecting to access point -> SSID: %s", &wfx_rsi.sec.ssid[0]); event.eventType = WFX_EVT_STA_START_JOIN; WfxPostEvent(&event); } else { - SILABS_LOG("%s: error: access point not provisioned", __func__); + SILABS_LOG("Error: access point not provisioned."); return SL_STATUS_INVALID_CONFIGURATION; } return SL_STATUS_OK; @@ -237,9 +236,8 @@ void wfx_setup_ip6_link_local(sl_wfx_interface_t whichif) ***********************************************************************/ bool wfx_is_sta_connected(void) { - bool status; - status = (wfx_rsi.dev_state & WFX_RSI_ST_STA_CONNECTED) ? true : false; - SILABS_LOG("%s: status: %s", __func__, (status ? "connected" : "not connected")); + bool status = (wfx_rsi.dev_state & WFX_RSI_ST_STA_CONNECTED) > 0; + SILABS_LOG("%s: %s", __func__, (status ? "Connected" : "Disconnected")); return status; } @@ -289,7 +287,7 @@ bool wfx_have_ipv4_addr(sl_wfx_interface_t which_if) bool status = false; if (which_if == SL_WFX_STA_INTERFACE) { - status = (wfx_rsi.dev_state & WFX_RSI_ST_STA_DHCP_DONE) ? true : false; + status = (wfx_rsi.dev_state & WFX_RSI_ST_STA_DHCP_DONE) > 0; } else { @@ -313,13 +311,13 @@ bool wfx_have_ipv6_addr(sl_wfx_interface_t which_if) bool status = false; if (which_if == SL_WFX_STA_INTERFACE) { - status = (wfx_rsi.dev_state & WFX_RSI_ST_STA_CONNECTED) ? true : false; + status = (wfx_rsi.dev_state & WFX_RSI_ST_STA_CONNECTED) > 0; } else { status = false; /* TODO */ } - SILABS_LOG("%s: status: %d", __func__, status); + SILABS_LOG("%s: %d", __func__, status); return status; } diff --git a/examples/platform/silabs/efr32/rs911x/rs9117.gni b/examples/platform/silabs/efr32/rs911x/rs9117.gni index 5a561c9f201eab..c068e7aa3efaff 100644 --- a/examples/platform/silabs/efr32/rs911x/rs9117.gni +++ b/examples/platform/silabs/efr32/rs911x/rs9117.gni @@ -3,8 +3,8 @@ import("//build_overrides/efr32_sdk.gni") import("${efr32_sdk_build_root}/efr32_sdk.gni") rs911x_src_plat = [ - "${examples_plat_dir}/rs911x/sl_wifi_if.c", - "${examples_plat_dir}/rs911x/wfx_rsi_host.c", + "${examples_plat_dir}/rs911x/sl_wifi_if.cpp", + "${examples_plat_dir}/rs911x/wfx_rsi_host.cpp", "${examples_plat_dir}/rs911x/hal/rsi_hal_mcu_interrupt.c", "${examples_plat_dir}/rs911x/hal/sl_si91x_ncp_utility.c", "${examples_plat_dir}/rs911x/hal/efx32_ncp_host.c", diff --git a/examples/platform/silabs/efr32/rs911x/rs911x.gni b/examples/platform/silabs/efr32/rs911x/rs911x.gni index 6b52ff1b99c676..ebf7c546f6a068 100644 --- a/examples/platform/silabs/efr32/rs911x/rs911x.gni +++ b/examples/platform/silabs/efr32/rs911x/rs911x.gni @@ -4,7 +4,7 @@ import("${efr32_sdk_build_root}/efr32_sdk.gni") rs911x_src_plat = [ "${examples_plat_dir}/rs911x/rsi_if.c", - "${examples_plat_dir}/rs911x/wfx_rsi_host.c", + "${examples_plat_dir}/rs911x/wfx_rsi_host.cpp", "${examples_plat_dir}/rs911x/hal/rsi_hal_mcu_interrupt.c", "${examples_plat_dir}/rs911x/hal/rsi_hal_mcu_ioports.c", "${examples_plat_dir}/rs911x/hal/rsi_hal_mcu_timer.c", diff --git a/examples/platform/silabs/efr32/rs911x/sl_wifi_if.c b/examples/platform/silabs/efr32/rs911x/sl_wifi_if.c deleted file mode 120000 index eae406da321152..00000000000000 --- a/examples/platform/silabs/efr32/rs911x/sl_wifi_if.c +++ /dev/null @@ -1 +0,0 @@ -../../SiWx917/SiWx917/sl_wifi_if.c \ No newline at end of file diff --git a/examples/platform/silabs/efr32/rs911x/sl_wifi_if.cpp b/examples/platform/silabs/efr32/rs911x/sl_wifi_if.cpp new file mode 120000 index 00000000000000..2f233ccc6cdbe0 --- /dev/null +++ b/examples/platform/silabs/efr32/rs911x/sl_wifi_if.cpp @@ -0,0 +1 @@ +../../SiWx917/SiWx917/sl_wifi_if.cpp \ No newline at end of file diff --git a/examples/platform/silabs/efr32/rs911x/wfx_rsi_host.c b/examples/platform/silabs/efr32/rs911x/wfx_rsi_host.cpp similarity index 100% rename from examples/platform/silabs/efr32/rs911x/wfx_rsi_host.c rename to examples/platform/silabs/efr32/rs911x/wfx_rsi_host.cpp From 43202d932f2a5e7a6fd8d940b881f8370ca83011 Mon Sep 17 00:00:00 2001 From: Karsten Sperling <113487422+ksperling-apple@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:48:59 +1300 Subject: [PATCH 04/11] Darwin project fixes (#32637) * Darwin: Run gn build during installapi so TAPI can see project headers * Darwin: Use default DEBUG_INFORMATION_FORMAT (dwarf-with-dsym) This ensures on-device debugging actually works properly. * Darwin: Move miscellaneous settings up to project level ... where applicable, instead of overriding settings per target. --- .../Matter.xcodeproj/project.pbxproj | 25 ++++++------------- .../Framework/chip_xcode_build_connector.sh | 15 ++++------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index ccb09921417a2b..c6111d85b0f537 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -2009,9 +2009,7 @@ isa = XCBuildConfiguration; buildSettings = { CHIP_BUILD_TOOLS = true; - CHIP_ROOT = "$(PROJECT_DIR)/../../.."; CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; @@ -2070,9 +2068,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; - STRIP_INSTALLED_PRODUCT = NO; SYSTEM_HEADER_SEARCH_PATHS = "$(CHIP_ROOT)/src/darwin/Framework/CHIP/"; - USER_HEADER_SEARCH_PATHS = ""; WARNING_CFLAGS = ( "-Wformat", "-Wformat-nonliteral", @@ -2087,13 +2083,10 @@ isa = XCBuildConfiguration; buildSettings = { CHIP_BUILD_TOOLS = true; - CHIP_ROOT = "$(PROJECT_DIR)/../../.."; CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "c++17"; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", @@ -2151,7 +2144,6 @@ PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SYSTEM_HEADER_SEARCH_PATHS = "$(CHIP_ROOT)/src/darwin/Framework/CHIP/"; - USER_HEADER_SEARCH_PATHS = ""; WARNING_CFLAGS = ( "-Wformat", "-Wformat-nonliteral", @@ -2166,6 +2158,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CHIP_ROOT = "$(PROJECT_DIR)/../../.."; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; @@ -2194,12 +2187,9 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -2222,6 +2212,8 @@ SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvos appletvsimulator watchos watchsimulator"; SUPPORTS_TEXT_BASED_API = YES; SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TAPI_ENABLE_PROJECT_HEADERS = YES; TARGETED_DEVICE_FAMILY = "1,2,3,4"; VERSIONING_SYSTEM = "apple-generic"; @@ -2238,7 +2230,6 @@ BA09EB742474881D00605257 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - CHIP_ROOT = "$(PROJECT_DIR)/../../.."; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; @@ -2253,6 +2244,7 @@ "MTR_ENABLE_PROVISIONAL=1", ); INFOPLIST_FILE = CHIP/Info.plist; + INSTALLHDRS_SCRIPT_PHASE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LIBRARY_SEARCH_PATHS = "$(TEMP_DIR)/out/lib"; @@ -2329,8 +2321,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.chip.CHIPTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "CHIPTests/MatterTests-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4"; }; name = Debug; @@ -2339,6 +2329,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CHIP_ROOT = "$(PROJECT_DIR)/../../.."; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; @@ -2367,7 +2358,6 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -2389,6 +2379,7 @@ SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvos appletvsimulator watchos watchsimulator"; SUPPORTS_TEXT_BASED_API = YES; SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -2405,7 +2396,6 @@ BA09EB782474882200605257 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - CHIP_ROOT = "$(PROJECT_DIR)/../../.."; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; @@ -2420,6 +2410,7 @@ "MTR_ENABLE_PROVISIONAL=1", ); INFOPLIST_FILE = CHIP/Info.plist; + INSTALLHDRS_SCRIPT_PHASE = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LIBRARY_SEARCH_PATHS = "$(TEMP_DIR)/out/lib"; @@ -2460,7 +2451,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.csa.matter; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; - STRIP_STYLE = "non-global"; SYSTEM_HEADER_SEARCH_PATHS = ( "$(TEMP_DIR)/out/gen/include", "$(CHIP_ROOT)/src/darwin/Framework/CHIP/", @@ -2498,7 +2488,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.chip.CHIPTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "CHIPTests/MatterTests-Bridging-Header.h"; - SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4"; }; name = Release; diff --git a/src/darwin/Framework/chip_xcode_build_connector.sh b/src/darwin/Framework/chip_xcode_build_connector.sh index 78023f97a469d8..a6d9d1da0b146c 100755 --- a/src/darwin/Framework/chip_xcode_build_connector.sh +++ b/src/darwin/Framework/chip_xcode_build_connector.sh @@ -25,19 +25,14 @@ # but is all uppper). Variables defined herein and used locally are lower-case # -here=$(cd "${0%/*}" && pwd) -me=${0##*/} - -CHIP_ROOT=$(cd "$here/../../.." && pwd) - -die() { - echo "$me: *** ERROR: $*" - exit 1 -} +CHIP_ROOT=$(cd "$(dirname "$0")/../../.." && pwd) # lotsa debug output :-) set -ex +# We only have work to do for the `installapi` and `build` phases +[[ "$ACTION" == installhdrs ]] && exit 0 + # helpful debugging, save off environment that Xcode gives us, can source it to # retry/repro failures from a bash terminal mkdir -p "$TEMP_DIR" @@ -191,7 +186,7 @@ find_in_ancestors() { # actual build stuff { - cd "$CHIP_ROOT" # pushd and popd because we need the env vars from activate + cd "$CHIP_ROOT" if ENV=$(find_in_ancestors chip_xcode_build_connector_env.sh 2>/dev/null); then . "$ENV" From 2b590aaa3ab65eba1c4c82dff0ab679a753a6864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20Ba=C5=82ys?= Date: Wed, 20 Mar 2024 09:44:38 +0100 Subject: [PATCH 05/11] [nrfconnect] Switch to NCS 2.6.0 revision (#32543) * [nrfconnect] Switch to NCS 2.6.0 revision Regular update NCS module in Matter. * [nrfconnect] Replace XOROSHIRO_RANDOM_GENERATOR It has been deprecated and the correct one to use now is XOSHIRO_RANDOM_GENERATOR. * [nrfconnect] Rename hci_rpmsg to hci_ipc This application has been renamed in zephyr * [nrfconnect] Adapt to zcbor 0.8.1 New parameter to ZCBOR_STATE_D() * [nrfconnect] Align nrf_power calls to new scheme Now the API to manage GPREGRET register is unified for all devices having one or more GPREGRET entries. * [nrfconnect] Switch to PSA Crypto API Enabled PSA Crypto API by default in nrfconnect platform * [nrfconnect] Adapt nrfconnect examples to PSA Crypto API Enabled PSA Crypto api in all nrfconnect examples. * [docker] Bump docker version to 41 * [Zephyr] Fix random include for nrfconnect --- .github/workflows/bloat_check.yaml | 2 +- .github/workflows/build.yaml | 10 +- .github/workflows/chef.yaml | 8 +- .github/workflows/cirque.yaml | 2 +- .github/workflows/doxygen.yaml | 2 +- .github/workflows/examples-ameba.yaml | 2 +- .github/workflows/examples-asr.yaml | 2 +- .github/workflows/examples-bouffalolab.yaml | 2 +- .../workflows/examples-cc13x2x7_26x2x7.yaml | 2 +- .github/workflows/examples-cc32xx.yaml | 2 +- .github/workflows/examples-efr32.yaml | 2 +- .github/workflows/examples-esp32.yaml | 4 +- .github/workflows/examples-infineon.yaml | 2 +- .github/workflows/examples-linux-arm.yaml | 2 +- .github/workflows/examples-linux-imx.yaml | 2 +- .../workflows/examples-linux-standalone.yaml | 2 +- .github/workflows/examples-mbed.yaml | 2 +- .github/workflows/examples-mw320.yaml | 2 +- .github/workflows/examples-nrfconnect.yaml | 2 +- .github/workflows/examples-openiotsdk.yaml | 2 +- .github/workflows/examples-qpg.yaml | 2 +- .github/workflows/examples-stm32.yaml | 2 +- .github/workflows/examples-telink.yaml | 2 +- .github/workflows/examples-tizen.yaml | 2 +- .github/workflows/examples-tv-app.yaml | 2 +- .github/workflows/full-android.yaml | 2 +- .github/workflows/fuzzing-build.yaml | 2 +- .github/workflows/java-tests.yaml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/minimal-build.yaml | 2 +- .github/workflows/qemu.yaml | 4 +- .github/workflows/release_artifacts.yaml | 4 +- .github/workflows/smoketest-android.yaml | 2 +- .github/workflows/tests.yaml | 4 +- .github/workflows/unit_integration_test.yaml | 2 +- .github/workflows/zap_regeneration.yaml | 2 +- .github/workflows/zap_templates.yaml | 2 +- .../.nrfconnect-recommended-revision | 2 +- config/nrfconnect/chip-module/CMakeLists.txt | 4 +- config/nrfconnect/chip-module/Kconfig | 16 + .../nrfconnect/chip-module/Kconfig.defaults | 373 +++++++++--------- ...pmsg.defaults => Kconfig.hci_ipc.defaults} | 2 +- ...ig.hci_rpmsg.root => Kconfig.hci_ipc.root} | 4 +- .../nrfconnect/main/AppTask.cpp | 23 ++ .../nrfconnect/main/AppTask.cpp | 24 ++ examples/chef/nrfconnect/main.cpp | 16 +- .../nrfconnect/main/AppTask.cpp | 24 ++ .../lighting-app/nrfconnect/main/AppTask.cpp | 23 ++ .../lit-icd-app/nrfconnect/main/AppTask.cpp | 23 ++ examples/lock-app/nrfconnect/main/AppTask.cpp | 24 ++ .../nrfconnect/util/MigrationManager.cpp | 57 +++ .../util/include/MigrationManager.h | 41 ++ examples/pump-app/nrfconnect/main/AppTask.cpp | 23 ++ .../window-app/nrfconnect/main/AppTask.cpp | 24 ++ src/platform/Zephyr/BLEManagerImpl.cpp | 4 - src/platform/Zephyr/PlatformManagerImpl.cpp | 16 +- src/platform/nrfconnect/CHIPPlatformConfig.h | 10 + src/platform/nrfconnect/FactoryDataParser.c | 4 +- .../nrfconnect/FactoryDataProvider.cpp | 64 ++- src/platform/nrfconnect/FactoryDataProvider.h | 4 + src/platform/nrfconnect/Reboot.cpp | 8 +- 61 files changed, 643 insertions(+), 266 deletions(-) rename config/nrfconnect/chip-module/{Kconfig.hci_rpmsg.defaults => Kconfig.hci_ipc.defaults} (96%) rename config/nrfconnect/chip-module/{Kconfig.hci_rpmsg.root => Kconfig.hci_ipc.root} (84%) create mode 100644 examples/platform/nrfconnect/util/MigrationManager.cpp create mode 100644 examples/platform/nrfconnect/util/include/MigrationManager.h diff --git a/.github/workflows/bloat_check.yaml b/.github/workflows/bloat_check.yaml index 8f04e49f961498..ef06507924cd01 100644 --- a/.github/workflows/bloat_check.yaml +++ b/.github/workflows/bloat_check.yaml @@ -33,7 +33,7 @@ jobs: runs-on: ubuntu-latest container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 steps: - name: Checkout diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e509aab995c682..1da7dd09665706 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -40,7 +40,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 volumes: - "/:/runner-root-volume" - "/tmp/log_output:/tmp/test_logs" @@ -136,7 +136,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 volumes: - "/:/runner-root-volume" - "/tmp/log_output:/tmp/test_logs" @@ -279,7 +279,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 volumes: - "/:/runner-root-volume" - "/tmp/log_output:/tmp/test_logs" @@ -340,7 +340,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 volumes: - "/:/runner-root-volume" - "/tmp/log_output:/tmp/test_logs" @@ -449,7 +449,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 volumes: - "/:/runner-root-volume" - "/tmp/log_output:/tmp/test_logs" diff --git a/.github/workflows/chef.yaml b/.github/workflows/chef.yaml index 3432fdc9a09779..c2c4a777bde7d3 100644 --- a/.github/workflows/chef.yaml +++ b/.github/workflows/chef.yaml @@ -33,7 +33,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 options: --user root steps: @@ -54,7 +54,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-esp32:35 + image: ghcr.io/project-chip/chip-build-esp32:41 options: --user root steps: @@ -75,7 +75,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-nrf-platform:35 + image: ghcr.io/project-chip/chip-build-nrf-platform:41 options: --user root steps: @@ -96,7 +96,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-telink:35 + image: ghcr.io/project-chip/chip-build-telink:41 options: --user root steps: diff --git a/.github/workflows/cirque.yaml b/.github/workflows/cirque.yaml index fcd49a2ab65337..919085ac77ee0f 100644 --- a/.github/workflows/cirque.yaml +++ b/.github/workflows/cirque.yaml @@ -40,7 +40,7 @@ jobs: # need to run with privilege, which isn't supported by job.XXX.contaner # https://github.com/actions/container-action/issues/2 # container: - # image: ghcr.io/project-chip/chip-build-cirque:35 + # image: ghcr.io/project-chip/chip-build-cirque:41 # volumes: # - "/tmp:/tmp" # - "/dev/pts:/dev/pts" diff --git a/.github/workflows/doxygen.yaml b/.github/workflows/doxygen.yaml index 13da9c2b4da1d6..cc93947c2c0937 100644 --- a/.github/workflows/doxygen.yaml +++ b/.github/workflows/doxygen.yaml @@ -81,7 +81,7 @@ jobs: runs-on: ubuntu-latest container: - image: ghcr.io/project-chip/chip-build-doxygen:35 + image: ghcr.io/project-chip/chip-build-doxygen:41 if: github.actor != 'restyled-io[bot]' diff --git a/.github/workflows/examples-ameba.yaml b/.github/workflows/examples-ameba.yaml index a10f4a84ac7d69..4716a44f76b1e1 100644 --- a/.github/workflows/examples-ameba.yaml +++ b/.github/workflows/examples-ameba.yaml @@ -36,7 +36,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-ameba:35 + image: ghcr.io/project-chip/chip-build-ameba:41 options: --user root steps: diff --git a/.github/workflows/examples-asr.yaml b/.github/workflows/examples-asr.yaml index 092f911a233642..52c63eda8fbbd0 100644 --- a/.github/workflows/examples-asr.yaml +++ b/.github/workflows/examples-asr.yaml @@ -34,7 +34,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-asr:35 + image: ghcr.io/project-chip/chip-build-asr:41 options: --user root steps: diff --git a/.github/workflows/examples-bouffalolab.yaml b/.github/workflows/examples-bouffalolab.yaml index ec90a39b26cf17..8d12696dcd8f89 100644 --- a/.github/workflows/examples-bouffalolab.yaml +++ b/.github/workflows/examples-bouffalolab.yaml @@ -35,7 +35,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-bouffalolab:35 + image: ghcr.io/project-chip/chip-build-bouffalolab:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: diff --git a/.github/workflows/examples-cc13x2x7_26x2x7.yaml b/.github/workflows/examples-cc13x2x7_26x2x7.yaml index 3c864525707a88..e9f47467b88f7f 100644 --- a/.github/workflows/examples-cc13x2x7_26x2x7.yaml +++ b/.github/workflows/examples-cc13x2x7_26x2x7.yaml @@ -36,7 +36,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-ti:35 + image: ghcr.io/project-chip/chip-build-ti:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: diff --git a/.github/workflows/examples-cc32xx.yaml b/.github/workflows/examples-cc32xx.yaml index 73f463b167707b..54f6025d850b85 100644 --- a/.github/workflows/examples-cc32xx.yaml +++ b/.github/workflows/examples-cc32xx.yaml @@ -37,7 +37,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-ti:35 + image: ghcr.io/project-chip/chip-build-ti:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: diff --git a/.github/workflows/examples-efr32.yaml b/.github/workflows/examples-efr32.yaml index 1dae68ced0f4a7..147dc957e130da 100644 --- a/.github/workflows/examples-efr32.yaml +++ b/.github/workflows/examples-efr32.yaml @@ -38,7 +38,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-efr32:36 + image: ghcr.io/project-chip/chip-build-efr32:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: diff --git a/.github/workflows/examples-esp32.yaml b/.github/workflows/examples-esp32.yaml index 2317da776e6a95..09d2b24d04aa47 100644 --- a/.github/workflows/examples-esp32.yaml +++ b/.github/workflows/examples-esp32.yaml @@ -34,7 +34,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-esp32:35 + image: ghcr.io/project-chip/chip-build-esp32:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" @@ -124,7 +124,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-esp32:35 + image: ghcr.io/project-chip/chip-build-esp32:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" diff --git a/.github/workflows/examples-infineon.yaml b/.github/workflows/examples-infineon.yaml index ff6f2e91a906b5..0f94ce1a2da631 100644 --- a/.github/workflows/examples-infineon.yaml +++ b/.github/workflows/examples-infineon.yaml @@ -35,7 +35,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-infineon:35 + image: ghcr.io/project-chip/chip-build-infineon:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: diff --git a/.github/workflows/examples-linux-arm.yaml b/.github/workflows/examples-linux-arm.yaml index a346bec097fa81..562279a0d35375 100644 --- a/.github/workflows/examples-linux-arm.yaml +++ b/.github/workflows/examples-linux-arm.yaml @@ -34,7 +34,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-crosscompile:35 + image: ghcr.io/project-chip/chip-build-crosscompile:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" diff --git a/.github/workflows/examples-linux-imx.yaml b/.github/workflows/examples-linux-imx.yaml index f6fee89fa966e4..843e76b9b14c41 100644 --- a/.github/workflows/examples-linux-imx.yaml +++ b/.github/workflows/examples-linux-imx.yaml @@ -34,7 +34,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-imx:35 + image: ghcr.io/project-chip/chip-build-imx:41 steps: - name: Checkout diff --git a/.github/workflows/examples-linux-standalone.yaml b/.github/workflows/examples-linux-standalone.yaml index ad1dc349163c7b..ea60e6adbcb022 100644 --- a/.github/workflows/examples-linux-standalone.yaml +++ b/.github/workflows/examples-linux-standalone.yaml @@ -34,7 +34,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" diff --git a/.github/workflows/examples-mbed.yaml b/.github/workflows/examples-mbed.yaml index 54d33b9f91d3c2..4b5748b4d0abe7 100644 --- a/.github/workflows/examples-mbed.yaml +++ b/.github/workflows/examples-mbed.yaml @@ -40,7 +40,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-mbed-os:35 + image: ghcr.io/project-chip/chip-build-mbed-os:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" diff --git a/.github/workflows/examples-mw320.yaml b/.github/workflows/examples-mw320.yaml index d0e7ce3122c06b..a16ea66c148aca 100644 --- a/.github/workflows/examples-mw320.yaml +++ b/.github/workflows/examples-mw320.yaml @@ -37,7 +37,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: diff --git a/.github/workflows/examples-nrfconnect.yaml b/.github/workflows/examples-nrfconnect.yaml index 64bb5a319eafa8..eb62c69299fc85 100644 --- a/.github/workflows/examples-nrfconnect.yaml +++ b/.github/workflows/examples-nrfconnect.yaml @@ -37,7 +37,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-nrf-platform:35 + image: ghcr.io/project-chip/chip-build-nrf-platform:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" diff --git a/.github/workflows/examples-openiotsdk.yaml b/.github/workflows/examples-openiotsdk.yaml index 254e5688d62435..b6f69791d2270a 100644 --- a/.github/workflows/examples-openiotsdk.yaml +++ b/.github/workflows/examples-openiotsdk.yaml @@ -38,7 +38,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-openiotsdk:35 + image: ghcr.io/project-chip/chip-build-openiotsdk:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" options: --privileged diff --git a/.github/workflows/examples-qpg.yaml b/.github/workflows/examples-qpg.yaml index e012eb9b02d065..b0af5ab2141ef0 100644 --- a/.github/workflows/examples-qpg.yaml +++ b/.github/workflows/examples-qpg.yaml @@ -37,7 +37,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: diff --git a/.github/workflows/examples-stm32.yaml b/.github/workflows/examples-stm32.yaml index 84442f85294d31..9a30af500112ce 100644 --- a/.github/workflows/examples-stm32.yaml +++ b/.github/workflows/examples-stm32.yaml @@ -38,7 +38,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: diff --git a/.github/workflows/examples-telink.yaml b/.github/workflows/examples-telink.yaml index 9131b3969df075..285ba96aceda07 100644 --- a/.github/workflows/examples-telink.yaml +++ b/.github/workflows/examples-telink.yaml @@ -36,7 +36,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-telink:35 + image: ghcr.io/project-chip/chip-build-telink:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" diff --git a/.github/workflows/examples-tizen.yaml b/.github/workflows/examples-tizen.yaml index a289e159dc6318..65c15e5ea4114d 100644 --- a/.github/workflows/examples-tizen.yaml +++ b/.github/workflows/examples-tizen.yaml @@ -34,7 +34,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-tizen:35 + image: ghcr.io/project-chip/chip-build-tizen:41 options: --user root volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" diff --git a/.github/workflows/examples-tv-app.yaml b/.github/workflows/examples-tv-app.yaml index fff6cbb39d1acb..c59a9292a1261b 100644 --- a/.github/workflows/examples-tv-app.yaml +++ b/.github/workflows/examples-tv-app.yaml @@ -37,7 +37,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-android:35 + image: ghcr.io/project-chip/chip-build-android:41 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" diff --git a/.github/workflows/full-android.yaml b/.github/workflows/full-android.yaml index f7a14d1f5d3d1b..b1116fa09f239b 100644 --- a/.github/workflows/full-android.yaml +++ b/.github/workflows/full-android.yaml @@ -36,7 +36,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-android:35 + image: ghcr.io/project-chip/chip-build-android:41 volumes: - "/tmp/log_output:/tmp/test_logs" diff --git a/.github/workflows/fuzzing-build.yaml b/.github/workflows/fuzzing-build.yaml index aedbe66ddb0ae5..f354ce369c27fd 100644 --- a/.github/workflows/fuzzing-build.yaml +++ b/.github/workflows/fuzzing-build.yaml @@ -33,7 +33,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 volumes: - "/tmp/log_output:/tmp/test_logs" diff --git a/.github/workflows/java-tests.yaml b/.github/workflows/java-tests.yaml index 8d21ca0bd4c56d..4356924024c9fe 100644 --- a/.github/workflows/java-tests.yaml +++ b/.github/workflows/java-tests.yaml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest container: - image: ghcr.io/project-chip/chip-build-java:35 + image: ghcr.io/project-chip/chip-build-java:41 options: --privileged --sysctl "net.ipv6.conf.all.disable_ipv6=0 net.ipv4.conf.all.forwarding=0 net.ipv6.conf.all.forwarding=0" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7e6206bc6374ff..15d0d3ed1be5f7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build:39 + image: ghcr.io/project-chip/chip-build:41 steps: - name: Checkout diff --git a/.github/workflows/minimal-build.yaml b/.github/workflows/minimal-build.yaml index 189c8db3159951..029f3d759771fc 100644 --- a/.github/workflows/minimal-build.yaml +++ b/.github/workflows/minimal-build.yaml @@ -31,7 +31,7 @@ jobs: runs-on: ubuntu-latest container: - image: ghcr.io/project-chip/chip-build-minimal:35 + image: ghcr.io/project-chip/chip-build-minimal:41 steps: - name: Checkout diff --git a/.github/workflows/qemu.yaml b/.github/workflows/qemu.yaml index 714b5b8782f1ae..dc356586b4dd96 100644 --- a/.github/workflows/qemu.yaml +++ b/.github/workflows/qemu.yaml @@ -38,7 +38,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-esp32-qemu:35 + image: ghcr.io/project-chip/chip-build-esp32-qemu:41 volumes: - "/tmp/log_output:/tmp/test_logs" @@ -78,7 +78,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-tizen-qemu:35 + image: ghcr.io/project-chip/chip-build-tizen-qemu:41 volumes: - "/tmp/log_output:/tmp/test_logs" diff --git a/.github/workflows/release_artifacts.yaml b/.github/workflows/release_artifacts.yaml index 78b0c34275430e..57a1ce7b325dc3 100644 --- a/.github/workflows/release_artifacts.yaml +++ b/.github/workflows/release_artifacts.yaml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest container: - image: ghcr.io/project-chip/chip-build-esp32:35 + image: ghcr.io/project-chip/chip-build-esp32:41 steps: - name: Checkout @@ -64,7 +64,7 @@ jobs: runs-on: ubuntu-latest container: - image: ghcr.io/project-chip/chip-build-efr32:36 + image: ghcr.io/project-chip/chip-build-efr32:41 steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/smoketest-android.yaml b/.github/workflows/smoketest-android.yaml index 64efaeb82ff20f..d1dd509fd5aa63 100644 --- a/.github/workflows/smoketest-android.yaml +++ b/.github/workflows/smoketest-android.yaml @@ -37,7 +37,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-android:35 + image: ghcr.io/project-chip/chip-build-android:41 volumes: - "/:/runner-root-volume" - "/tmp/log_output:/tmp/test_logs" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 41138dd787360c..2907a4a89711ea 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -47,7 +47,7 @@ jobs: runs-on: ubuntu-latest container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 options: --privileged --sysctl "net.ipv6.conf.all.disable_ipv6=0 net.ipv4.conf.all.forwarding=1 net.ipv6.conf.all.forwarding=1" @@ -437,7 +437,7 @@ jobs: runs-on: ubuntu-latest container: - image: ghcr.io/project-chip/chip-build:32 + image: ghcr.io/project-chip/chip-build:41 options: --privileged --sysctl "net.ipv6.conf.all.disable_ipv6=0 net.ipv4.conf.all.forwarding=0 net.ipv6.conf.all.forwarding=0" diff --git a/.github/workflows/unit_integration_test.yaml b/.github/workflows/unit_integration_test.yaml index be602f2ee9f142..478d347be25ab9 100644 --- a/.github/workflows/unit_integration_test.yaml +++ b/.github/workflows/unit_integration_test.yaml @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 volumes: - "/:/runner-root-volume" - "/tmp/log_output:/tmp/test_logs" diff --git a/.github/workflows/zap_regeneration.yaml b/.github/workflows/zap_regeneration.yaml index a0ab3653d5ecb1..4488e23258cd65 100644 --- a/.github/workflows/zap_regeneration.yaml +++ b/.github/workflows/zap_regeneration.yaml @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-20.04 container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 defaults: run: shell: sh diff --git a/.github/workflows/zap_templates.yaml b/.github/workflows/zap_templates.yaml index cb8bff6172a119..f14b914ae7f88a 100644 --- a/.github/workflows/zap_templates.yaml +++ b/.github/workflows/zap_templates.yaml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-20.04 container: - image: ghcr.io/project-chip/chip-build:35 + image: ghcr.io/project-chip/chip-build:41 defaults: run: shell: sh diff --git a/config/nrfconnect/.nrfconnect-recommended-revision b/config/nrfconnect/.nrfconnect-recommended-revision index 21222ceed22ae6..8a965c116821a9 100644 --- a/config/nrfconnect/.nrfconnect-recommended-revision +++ b/config/nrfconnect/.nrfconnect-recommended-revision @@ -1 +1 @@ -v2.5.0 +v2.6.0 diff --git a/config/nrfconnect/chip-module/CMakeLists.txt b/config/nrfconnect/chip-module/CMakeLists.txt index 9b81d7cf30282e..d132d8249d76b1 100644 --- a/config/nrfconnect/chip-module/CMakeLists.txt +++ b/config/nrfconnect/chip-module/CMakeLists.txt @@ -60,13 +60,15 @@ if (CONFIG_ARM) matter_add_cflags(--specs=nosys.specs) endif() -if (CONFIG_NORDIC_SECURITY_BACKEND) +if (CONFIG_NRF_SECURITY) zephyr_include_directories($) zephyr_include_directories($) if(TARGET platform_cc3xx) zephyr_include_directories($) endif() matter_add_flags(-DMBEDTLS_CONFIG_FILE=) + matter_add_flags(-DMBEDTLS_PSA_CRYPTO_CONFIG_FILE=) + matter_add_flags(-DMBEDTLS_PSA_CRYPTO_USER_CONFIG_FILE=) elseif(CONFIG_MBEDTLS) zephyr_include_directories($) zephyr_compile_definitions($) diff --git a/config/nrfconnect/chip-module/Kconfig b/config/nrfconnect/chip-module/Kconfig index c72080b7005409..5d03e82530c84e 100644 --- a/config/nrfconnect/chip-module/Kconfig +++ b/config/nrfconnect/chip-module/Kconfig @@ -286,4 +286,20 @@ config CHIP_ENABLE_READ_CLIENT This config can be disabled for device types that do not require Read Client functionality. Disabling this config can save flash and RAM space. +config CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS + bool "Operational keys migration feature" + depends on CHIP_CRYPTO_PSA + help + Enables migration of the operational keys stored in the persistent storage to the PSA ITS secure storage. + Enable this feature while updating the firmware of in-field devices that run Mbed TLS cryptography backend + to the firmware based on PSA Crypto API. + +config CHIP_FACTORY_RESET_ON_KEY_MIGRATION_FAILURE + bool "Perform factory reset if the operational key migration failed" + default y + depends on CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS + help + Perform factory reset of the device if the operational key for Fabric has not been migrated + properly to PSA ITS storage. + endif # CHIP diff --git a/config/nrfconnect/chip-module/Kconfig.defaults b/config/nrfconnect/chip-module/Kconfig.defaults index 8684adb2be3c52..f1c1f71e49acaf 100644 --- a/config/nrfconnect/chip-module/Kconfig.defaults +++ b/config/nrfconnect/chip-module/Kconfig.defaults @@ -20,32 +20,9 @@ if CHIP -config LOG - default y - -if LOG - -choice LOG_MODE - default LOG_MODE_MINIMAL -endchoice - -choice MATTER_LOG_LEVEL_CHOICE - default MATTER_LOG_LEVEL_DBG -endchoice - -config CHIP_APP_LOG_LEVEL - default 4 # debug - -config LOG_DEFAULT_LEVEL - default 1 # error - -config CHIP_LOG_SIZE_OPTIMIZATION - default y - -endif - -config PRINTK_SYNC - default y +# ============================================================================== +# System configuration +# ============================================================================== config ASSERT default y @@ -62,43 +39,52 @@ config HW_STACK_PROTECTION config FPU default y -config SHELL - default y - -config SHELL_MINIMAL - default y - -# Enable getting reboot reasons information -config HWINFO - bool - default y - -config HWINFO_SHELL - bool - default n - -config PTHREAD_IPC - bool - default n - config POSIX_MAX_FDS - int default 16 -# Application stack size config MAIN_STACK_SIZE default 6144 config INIT_STACKS default y +config SYSTEM_WORKQUEUE_STACK_SIZE + default 2560 if CHIP_WIFI + +config HEAP_MEM_POOL_SIZE + default 80000 if CHIP_WIFI + +config CHIP_MALLOC_SYS_HEAP_SIZE + default 30720 if CHIP_WIFI + default 8192 if NET_L2_OPENTHREAD + +# We use sys_heap based allocators, so make sure we don't reserve unused libc heap anyway +config COMMON_LIBC_MALLOC_ARENA_SIZE + default -1 + +config NVS_LOOKUP_CACHE_SIZE + default 512 + +# ============================================================================== +# Zephyr networking configuration +# ============================================================================== + config NET_IPV6_MLD default y +config NET_IPV6_NBR_CACHE + default y if CHIP_WIFI + default n if NET_L2_OPENTHREAD + +config NET_IF_UNICAST_IPV6_ADDR_COUNT + default 6 + config NET_IF_MCAST_IPV6_ADDR_COUNT default 14 -# Network buffers +config NET_IF_IPV6_PREFIX_COUNT + default NET_IF_UNICAST_IPV6_ADDR_COUNT if CHIP_WIFI + config NET_PKT_RX_COUNT default 8 @@ -111,7 +97,12 @@ config NET_BUF_RX_COUNT config NET_BUF_TX_COUNT default 16 -# Bluetooth Low Energy configs +config NET_SOCKETS_POLL_MAX + default 6 if CHIP_WIFI + +# ============================================================================== +# Bluetooth Low Energy configuration +# ============================================================================== config BT default y @@ -162,99 +153,95 @@ config BT_BUF_ACL_TX_SIZE config BT_RX_STACK_SIZE default 1200 +# Increase maximum data length of PDU supported in the Controller +config BT_CTLR_DATA_LENGTH_MAX + default 251 if SOC_SERIES_NRF52X + config BT_CTLR_ECDH - bool default n config BT_CTLR_LE_ENC - bool default n config BT_DEVICE_NAME_GATT_WRITABLE - bool default n config BT_GATT_CACHING - bool default n # Disable 2M PHY due to interoperability issues. config BT_CTLR_PHY_2M default n -# Enable NFC support +config MPSL_FEM_NRF21540_RUNTIME_PA_GAIN_CONTROL + default y if MPSL_FEM + +# ============================================================================== +# NFC configuration +# ============================================================================== config CHIP_NFC_COMMISSIONING default y # Disable not needed NFC callback to save flash config NFC_THREAD_CALLBACK - bool default n +# ============================================================================== +# DFU configuration +# ============================================================================== + config CHIP_OTA_REQUESTOR default y -# All boards besides nRF7002DK use QSPI NOR external flash -if BOARD_NRF5340DK_NRF5340_CPUAPP || BOARD_NRF52840DK_NRF52840 - +# All boards except nRF7002DK use QSPI NOR external flash config CHIP_QSPI_NOR - default y - -endif # BOARD_NRF5340DK_NRF5340_CPUAPP || BOARD_NRF52840DK_NRF52840 + default y if BOARD_NRF5340DK_NRF5340_CPUAPP || BOARD_NRF52840DK_NRF52840 # nRF7002DK uses SPI NOR external flash - -if BOARD_NRF7002DK_NRF5340_CPUAPP - config CHIP_SPI_NOR - default y + default y if BOARD_NRF7002DK_NRF5340_CPUAPP -endif # BOARD_NRF7002DK_NRF5340_CPUAPP - -# Enable extended discovery -config CHIP_EXTENDED_DISCOVERY - default n +config BOOT_IMAGE_ACCESS_HOOKS + default y if SOC_SERIES_NRF53X -config NVS_LOOKUP_CACHE_SIZE - default 512 +config UPDATEABLE_IMAGE_NUMBER + default 2 if SOC_SERIES_NRF53X -# Enable OpenThread +# ============================================================================== +# OpenThread configuration +# ============================================================================== config NET_L2_OPENTHREAD default y if !WIFI_NRF700X if NET_L2_OPENTHREAD -# Disable OpenThread shell -config OPENTHREAD_SHELL - default n - -# Disable certain parts of Zephyr IPv6 stack -config NET_IPV6_NBR_CACHE - bool - default n - # Increase the default RX stack size config IEEE802154_NRF5_RX_STACK_SIZE default 1024 config OPENTHREAD_THREAD_STACK_SIZE - default 4096 + default 6144 if PSA_CRYPTO_DRIVER_CC3XX && PSA_CRYPTO_DRIVER_OBERON + default 4096 -endif +config OPENTHREAD_DEFAULT_TX_POWER + default 20 if MPSL_FEM + default 3 if SOC_SERIES_NRF53X + default 8 if SOC_SERIES_NRF52X + +endif # NET_L2_OPENTHREAD + +# ============================================================================== +# Wi-Fi configuration +# ============================================================================== if CHIP_WIFI choice WPA_SUPP_LOG_LEVEL_CHOICE - default WPA_SUPP_LOG_LEVEL_ERR + default WPA_SUPP_LOG_LEVEL_ERR endchoice -# increase the prefixes limit to match -# maximum number of IPv6 addresses per interface -config NET_IF_IPV6_PREFIX_COUNT - default 6 - # it saves us 20kB of FLASH config WPA_SUPP_NO_DEBUG default y @@ -272,74 +259,73 @@ config NRF_WIFI_LOW_POWER config NRF700X_RX_NUM_BUFS default 16 -config NRF700X_TX_MAX_DATA_SIZE - default 1280 - -config NRF700X_RX_MAX_DATA_SIZE - default 1280 - config NRF700X_MAX_TX_TOKENS default 10 config NRF700X_MAX_TX_AGGREGATION default 1 -config MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG +# it saves 25kB of FLASH +config WPA_SUPP_ADVANCED_FEATURES default n -config SYSTEM_WORKQUEUE_STACK_SIZE - default 2560 +endif # CHIP_WIFI -# align these numbers to match the OpenThread config -config NET_IF_UNICAST_IPV6_ADDR_COUNT - default 6 +# ============================================================================== +# Crypto configuration +# ============================================================================== -config NET_IF_MCAST_IPV6_ADDR_COUNT - default 8 +choice OPENTHREAD_SECURITY + default OPENTHREAD_NRF_SECURITY_PSA_CHOICE if CHIP_CRYPTO_PSA + default OPENTHREAD_NRF_SECURITY_CHOICE + +endchoice -config NET_SOCKETS_POLL_MAX - default 6 +choice RNG_GENERATOR_CHOICE + default XOSHIRO_RANDOM_GENERATOR if SOC_SERIES_NRF53X +endchoice -config MBEDTLS_SSL_OUT_CONTENT_LEN - default 900 +config OBERON_BACKEND + default y -# options managed by IP4/IP6 simultaneous support -# aligned here to match OpenThread config -config NET_MAX_ROUTERS - default 1 +config MBEDTLS_ENABLE_HEAP + default y -config NET_MAX_CONN - default 4 +config MBEDTLS_HEAP_SIZE + default 8192 + +# Enable PSA Crypto dependencies for Matter -config SHELL_STACK_SIZE - default 2616 +config CHIP_CRYPTO_PSA + default y if !CHIP_WIFI -config HEAP_MEM_POOL_SIZE - default 80000 +if CHIP_CRYPTO_PSA -endif +config PSA_CRYPTO_DRIVER_CC3XX + default n -config CHIP_MALLOC_SYS_HEAP_SIZE - default 28672 if CHIP_WIFI - default 8192 if NET_L2_OPENTHREAD +config PSA_WANT_ALG_SHA_224 + default n -# Enable mbedTLS from nrf_security library +# Extend the maximum number of PSA key slots to fit Matter requirements +config MBEDTLS_PSA_KEY_SLOT_COUNT + default 64 -choice OPENTHREAD_SECURITY - default OPENTHREAD_NRF_SECURITY_CHOICE -endchoice +if PSA_CRYPTO_DRIVER_CC3XX && PSA_CRYPTO_DRIVER_OBERON -config PSA_CRYPTO_DRIVER_CC3XX - default n +# Do not use CC3XX hash driver when both Oberon and CC3xx are enabled. +config PSA_USE_CC3XX_HASH_DRIVER + default n -config OBERON_BACKEND - default y +endif -config MBEDTLS_ENABLE_HEAP +# Spake2+ support +config MBEDTLS_MD_C default y -config MBEDTLS_HEAP_SIZE - default 8192 +endif + +if !CHIP_CRYPTO_PSA config NRF_SECURITY_ADVANCED default y @@ -347,32 +333,46 @@ config NRF_SECURITY_ADVANCED config MBEDTLS_AES_C default y -config MBEDTLS_ECP_C - default y - -config MBEDTLS_ECP_DP_SECP256R1_ENABLED - default y - config MBEDTLS_CTR_DRBG_C default y config MBEDTLS_CIPHER_MODE_CTR default y +config MBEDTLS_SHA1_C + default y if CHIP_WIFI + config MBEDTLS_SHA256_C default y config MBEDTLS_PK_C default y +config MBEDTLS_PKCS5_C + default y + config MBEDTLS_PK_WRITE_C default y config MBEDTLS_X509_CREATE_C - default y if !CHIP_CRYPTO_PSA + default y config MBEDTLS_X509_CSR_WRITE_C - default y if !CHIP_CRYPTO_PSA + default y + +config MBEDTLS_ECP_C + default y + +config MBEDTLS_ECP_DP_SECP256R1_ENABLED + default y + +endif + +config MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG + default n if CHIP_WIFI + +config MBEDTLS_SSL_OUT_CONTENT_LEN + default 900 if CHIP_WIFI # Disable unneeded crypto operations @@ -406,17 +406,54 @@ config MBEDTLS_SSL_SRV_C config MBEDTLS_SSL_COOKIE_C default n -# Disable not used shell modules +# ============================================================================== +# Logging configuration +# ============================================================================== -config SHELL_WILDCARD - default n +config LOG + default y -config SHELL_VT100_COLORS - default n +if LOG -config SHELL_STATS +choice LOG_MODE + default LOG_MODE_MINIMAL +endchoice + +choice MATTER_LOG_LEVEL_CHOICE + default MATTER_LOG_LEVEL_DBG +endchoice + +config CHIP_APP_LOG_LEVEL + default 4 # debug + +config LOG_DEFAULT_LEVEL + default 1 # error + +config CHIP_LOG_SIZE_OPTIMIZATION + default y + +# disable synchronous printk to avoid blocking IRQs which +# may affect time sensitive components +config PRINTK_SYNC default n +endif # LOG + +# ============================================================================== +# Shell configuration +# ============================================================================== + +config SHELL + default y + +if SHELL + +config SHELL_STACK_SIZE + default 2616 if CHIP_WIFI + +config SHELL_MINIMAL + default y + config KERNEL_SHELL default n @@ -441,42 +478,12 @@ config CLOCK_CONTROL_NRF_SHELL config FLASH_SHELL default n -if MPSL_FEM - -config MPSL_FEM_NRF21540_RUNTIME_PA_GAIN_CONTROL - default y - -endif # MPSL_FEM - -config OPENTHREAD_DEFAULT_TX_POWER - default 20 if MPSL_FEM - default 3 if SOC_SERIES_NRF53X && !MPSL_FEM - default 8 if SOC_SERIES_NRF52X && !MPSL_FEM - -# SoC series related configuration - -if SOC_SERIES_NRF52X - -# Increase maximum data length of PDU supported in the Controller -config BT_CTLR_DATA_LENGTH_MAX - default 251 - -endif # SOC_SERIES_NRF52X - -if SOC_SERIES_NRF53X - -config BOOT_IMAGE_ACCESS_HOOKS - default y - -config UPDATEABLE_IMAGE_NUMBER - default 2 +config HWINFO_SHELL + default n -# Generate random numbers using Xoroshiro algorithm instead of direct calls -# to the cryptocell library to workaround firmware hangs. -choice RNG_GENERATOR_CHOICE - default XOROSHIRO_RANDOM_GENERATOR -endchoice +config OPENTHREAD_SHELL + default n -endif # SOC_SERIES_NRF53X +endif # SHELL -endif +endif \ No newline at end of file diff --git a/config/nrfconnect/chip-module/Kconfig.hci_rpmsg.defaults b/config/nrfconnect/chip-module/Kconfig.hci_ipc.defaults similarity index 96% rename from config/nrfconnect/chip-module/Kconfig.hci_rpmsg.defaults rename to config/nrfconnect/chip-module/Kconfig.hci_ipc.defaults index 17c7115e28b750..bede85fd2541d3 100644 --- a/config/nrfconnect/chip-module/Kconfig.hci_rpmsg.defaults +++ b/config/nrfconnect/chip-module/Kconfig.hci_ipc.defaults @@ -14,7 +14,7 @@ # limitations under the License. # -# The purpose of this file is to define new default values of settings used when building hci_rpmsg child image for Matter samples. +# The purpose of this file is to define new default values of settings used when building hci_ipc child image for Matter samples. config LOG default n diff --git a/config/nrfconnect/chip-module/Kconfig.hci_rpmsg.root b/config/nrfconnect/chip-module/Kconfig.hci_ipc.root similarity index 84% rename from config/nrfconnect/chip-module/Kconfig.hci_rpmsg.root rename to config/nrfconnect/chip-module/Kconfig.hci_ipc.root index 8c4f6eee49cbc2..1fe8ff85f43ee8 100644 --- a/config/nrfconnect/chip-module/Kconfig.hci_rpmsg.root +++ b/config/nrfconnect/chip-module/Kconfig.hci_ipc.root @@ -15,7 +15,7 @@ # # The purpose of this file is to create a wrapper Kconfig file that will be set as -# hci_rpmsg_KCONFIG_ROOT and processed before any other Kconfig for hci_rpmsg child image. +# hci_ipc_KCONFIG_ROOT and processed before any other Kconfig for hci_ipc child image. -rsource "Kconfig.hci_rpmsg.defaults" +rsource "Kconfig.hci_ipc.defaults" source "Kconfig.zephyr" diff --git a/examples/all-clusters-app/nrfconnect/main/AppTask.cpp b/examples/all-clusters-app/nrfconnect/main/AppTask.cpp index f8d117790fac3d..efdee7153b493c 100644 --- a/examples/all-clusters-app/nrfconnect/main/AppTask.cpp +++ b/examples/all-clusters-app/nrfconnect/main/AppTask.cpp @@ -45,6 +45,13 @@ #include "OTAUtil.h" #endif +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS +#include "MigrationManager.h" +#endif +#endif + #include #include #include @@ -88,6 +95,9 @@ bool sHaveBLEConnections = false; app::Clusters::TemperatureControl::AppSupportedTemperatureLevelsDelegate sAppSupportedTemperatureLevelsDelegate; +#ifdef CONFIG_CHIP_CRYPTO_PSA +chip::Crypto::PSAOperationalKeystore sPSAOperationalKeystore{}; +#endif } // namespace namespace LedConsts { @@ -209,11 +219,24 @@ CHIP_ERROR AppTask::Init() static OTATestEventTriggerHandler sOtaTestEventTriggerHandler{}; VerifyOrDie(sTestEventTriggerDelegate.Init(ByteSpan(sTestEventTriggerEnableKey)) == CHIP_NO_ERROR); VerifyOrDie(sTestEventTriggerDelegate.AddHandler(&sOtaTestEventTriggerHandler) == CHIP_NO_ERROR); +#ifdef CONFIG_CHIP_CRYPTO_PSA + initParams.operationalKeystore = &sPSAOperationalKeystore; +#endif (void) initParams.InitializeStaticResourcesBeforeServerInit(); initParams.testEventTriggerDelegate = &sTestEventTriggerDelegate; ReturnErrorOnFailure(chip::Server::GetInstance().Init(initParams)); AppFabricTableDelegate::Init(); +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS + err = MoveOperationalKeysFromKvsToIts(sLocalInitData.mServerInitParams->persistentStorageDelegate, + sLocalInitData.mServerInitParams->operationalKeystore); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("MoveOperationalKeysFromKvsToIts() failed"); + return err; + } +#endif + gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage()); chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider); diff --git a/examples/all-clusters-minimal-app/nrfconnect/main/AppTask.cpp b/examples/all-clusters-minimal-app/nrfconnect/main/AppTask.cpp index 02f0704f080a58..b3c8b951372c0f 100644 --- a/examples/all-clusters-minimal-app/nrfconnect/main/AppTask.cpp +++ b/examples/all-clusters-minimal-app/nrfconnect/main/AppTask.cpp @@ -34,6 +34,13 @@ #include "OTAUtil.h" #endif +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#ifdef CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS +#include "MigrationManager.h" +#endif +#endif + #include #include #include @@ -59,6 +66,10 @@ FactoryResetLEDsWrapper<3> sFactoryResetLEDs{ { FACTORY_RESET_SIGNAL_LED, FACTOR bool sIsNetworkProvisioned = false; bool sIsNetworkEnabled = false; bool sHaveBLEConnections = false; + +#ifdef CONFIG_CHIP_CRYPTO_PSA +chip::Crypto::PSAOperationalKeystore sPSAOperationalKeystore{}; +#endif } // namespace namespace LedConsts { @@ -155,10 +166,23 @@ CHIP_ERROR AppTask::Init() #endif static chip::CommonCaseDeviceServerInitParams initParams; +#ifdef CONFIG_CHIP_CRYPTO_PSA + initParams.operationalKeystore = &sPSAOperationalKeystore; +#endif (void) initParams.InitializeStaticResourcesBeforeServerInit(); ReturnErrorOnFailure(chip::Server::GetInstance().Init(initParams)); AppFabricTableDelegate::Init(); +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS + err = MoveOperationalKeysFromKvsToIts(sLocalInitData.mServerInitParams->persistentStorageDelegate, + sLocalInitData.mServerInitParams->operationalKeystore); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("MoveOperationalKeysFromKvsToIts() failed"); + return err; + } +#endif + // We only have network commissioning on endpoint 0. emberAfEndpointEnableDisable(kNetworkCommissioningEndpointSecondary, false); ConfigurationMgr().LogDeviceConfig(); diff --git a/examples/chef/nrfconnect/main.cpp b/examples/chef/nrfconnect/main.cpp index d132252ecd87b3..c79694e2e792a7 100644 --- a/examples/chef/nrfconnect/main.cpp +++ b/examples/chef/nrfconnect/main.cpp @@ -43,6 +43,13 @@ #include "Rpc.h" #endif +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS +#include "MigrationManager.h" +#endif +#endif + LOG_MODULE_REGISTER(app, CONFIG_CHIP_APP_LOG_LEVEL); using namespace chip; @@ -51,7 +58,11 @@ using namespace chip::DeviceLayer; namespace { constexpr int kExtDiscoveryTimeoutSecs = 20; -} + +#ifdef CONFIG_CHIP_CRYPTO_PSA +chip::Crypto::PSAOperationalKeystore sPSAOperationalKeystore{}; +#endif +} // namespace int main() { @@ -110,6 +121,9 @@ int main() // Start IM server static chip::CommonCaseDeviceServerInitParams initParams; +#ifdef CONFIG_CHIP_CRYPTO_PSA + initParams.operationalKeystore = &sPSAOperationalKeystore; +#endif (void) initParams.InitializeStaticResourcesBeforeServerInit(); err = chip::Server::GetInstance().Init(initParams); if (err != CHIP_NO_ERROR) diff --git a/examples/light-switch-app/nrfconnect/main/AppTask.cpp b/examples/light-switch-app/nrfconnect/main/AppTask.cpp index 06ac797f6a6899..fc1c947150dd95 100644 --- a/examples/light-switch-app/nrfconnect/main/AppTask.cpp +++ b/examples/light-switch-app/nrfconnect/main/AppTask.cpp @@ -46,6 +46,13 @@ #include "OTAUtil.h" #endif +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS +#include "MigrationManager.h" +#endif +#endif + #include #include #include @@ -94,6 +101,10 @@ k_timer sDimmerPressKeyTimer; k_timer sDimmerTimer; chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; + +#ifdef CONFIG_CHIP_CRYPTO_PSA +chip::Crypto::PSAOperationalKeystore sPSAOperationalKeystore{}; +#endif } // namespace namespace LedConsts { @@ -220,11 +231,24 @@ CHIP_ERROR AppTask::Init() static OTATestEventTriggerHandler sOtaTestEventTriggerHandler{}; VerifyOrDie(sTestEventTriggerDelegate.Init(ByteSpan(sTestEventTriggerEnableKey)) == CHIP_NO_ERROR); VerifyOrDie(sTestEventTriggerDelegate.AddHandler(&sOtaTestEventTriggerHandler) == CHIP_NO_ERROR); +#ifdef CONFIG_CHIP_CRYPTO_PSA + initParams.operationalKeystore = &sPSAOperationalKeystore; +#endif (void) initParams.InitializeStaticResourcesBeforeServerInit(); initParams.testEventTriggerDelegate = &sTestEventTriggerDelegate; ReturnErrorOnFailure(chip::Server::GetInstance().Init(initParams)); AppFabricTableDelegate::Init(); +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS + err = MoveOperationalKeysFromKvsToIts(sLocalInitData.mServerInitParams->persistentStorageDelegate, + sLocalInitData.mServerInitParams->operationalKeystore); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("MoveOperationalKeysFromKvsToIts() failed"); + return err; + } +#endif + gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage()); chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider); diff --git a/examples/lighting-app/nrfconnect/main/AppTask.cpp b/examples/lighting-app/nrfconnect/main/AppTask.cpp index 5eaa1b1b401524..88964d9aa1d3c8 100644 --- a/examples/lighting-app/nrfconnect/main/AppTask.cpp +++ b/examples/lighting-app/nrfconnect/main/AppTask.cpp @@ -49,6 +49,13 @@ #include "OTAUtil.h" #endif +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS +#include "MigrationManager.h" +#endif +#endif + #include #include #include @@ -108,6 +115,9 @@ DeferredAttributePersistenceProvider gDeferredAttributePersister(Server::GetInst Span(&gCurrentLevelPersister, 1), System::Clock::Milliseconds32(5000)); +#ifdef CONFIG_CHIP_CRYPTO_PSA +chip::Crypto::PSAOperationalKeystore sPSAOperationalKeystore{}; +#endif } // namespace namespace LedConsts { @@ -248,11 +258,24 @@ CHIP_ERROR AppTask::Init() static OTATestEventTriggerHandler sOtaTestEventTriggerHandler{}; VerifyOrDie(sTestEventTriggerDelegate.Init(ByteSpan(sTestEventTriggerEnableKey)) == CHIP_NO_ERROR); VerifyOrDie(sTestEventTriggerDelegate.AddHandler(&sOtaTestEventTriggerHandler) == CHIP_NO_ERROR); +#ifdef CONFIG_CHIP_CRYPTO_PSA + initParams.operationalKeystore = &sPSAOperationalKeystore; +#endif (void) initParams.InitializeStaticResourcesBeforeServerInit(); initParams.testEventTriggerDelegate = &sTestEventTriggerDelegate; ReturnErrorOnFailure(chip::Server::GetInstance().Init(initParams)); AppFabricTableDelegate::Init(); +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS + err = MoveOperationalKeysFromKvsToIts(sLocalInitData.mServerInitParams->persistentStorageDelegate, + sLocalInitData.mServerInitParams->operationalKeystore); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("MoveOperationalKeysFromKvsToIts() failed"); + return err; + } +#endif + gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage()); chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider); app::SetAttributePersistenceProvider(&gDeferredAttributePersister); diff --git a/examples/lit-icd-app/nrfconnect/main/AppTask.cpp b/examples/lit-icd-app/nrfconnect/main/AppTask.cpp index 50622d0c88b96e..d7d35bb53fcc04 100644 --- a/examples/lit-icd-app/nrfconnect/main/AppTask.cpp +++ b/examples/lit-icd-app/nrfconnect/main/AppTask.cpp @@ -38,6 +38,13 @@ #include "OTAUtil.h" #endif +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS +#include "MigrationManager.h" +#endif +#endif + #include #include #include @@ -76,6 +83,9 @@ bool sIsNetworkProvisioned = false; bool sIsNetworkEnabled = false; bool sHaveBLEConnections = false; +#ifdef CONFIG_CHIP_CRYPTO_PSA +chip::Crypto::PSAOperationalKeystore sPSAOperationalKeystore{}; +#endif } // namespace namespace LedConsts { @@ -186,11 +196,24 @@ CHIP_ERROR AppTask::Init() static OTATestEventTriggerHandler sOtaTestEventTriggerHandler{}; VerifyOrDie(sTestEventTriggerDelegate.Init(ByteSpan(sTestEventTriggerEnableKey)) == CHIP_NO_ERROR); VerifyOrDie(sTestEventTriggerDelegate.AddHandler(&sOtaTestEventTriggerHandler) == CHIP_NO_ERROR); +#ifdef CONFIG_CHIP_CRYPTO_PSA + initParams.operationalKeystore = &sPSAOperationalKeystore; +#endif (void) initParams.InitializeStaticResourcesBeforeServerInit(); initParams.testEventTriggerDelegate = &sTestEventTriggerDelegate; ReturnErrorOnFailure(chip::Server::GetInstance().Init(initParams)); AppFabricTableDelegate::Init(); +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS + err = MoveOperationalKeysFromKvsToIts(sLocalInitData.mServerInitParams->persistentStorageDelegate, + sLocalInitData.mServerInitParams->operationalKeystore); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("MoveOperationalKeysFromKvsToIts() failed"); + return err; + } +#endif + gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage()); chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider); ConfigurationMgr().LogDeviceConfig(); diff --git a/examples/lock-app/nrfconnect/main/AppTask.cpp b/examples/lock-app/nrfconnect/main/AppTask.cpp index 07a96b6d6c29b2..1025a045184aee 100644 --- a/examples/lock-app/nrfconnect/main/AppTask.cpp +++ b/examples/lock-app/nrfconnect/main/AppTask.cpp @@ -47,6 +47,13 @@ #include "OTAUtil.h" #endif +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS +#include "MigrationManager.h" +#endif +#endif + #include #include #include @@ -90,6 +97,10 @@ bool sIsNetworkEnabled = false; bool sHaveBLEConnections = false; chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; + +#ifdef CONFIG_CHIP_CRYPTO_PSA +chip::Crypto::PSAOperationalKeystore sPSAOperationalKeystore{}; +#endif } // namespace namespace LedConsts { @@ -214,11 +225,24 @@ CHIP_ERROR AppTask::Init() static OTATestEventTriggerHandler sOtaTestEventTriggerHandler{}; VerifyOrDie(sTestEventTriggerDelegate.Init(ByteSpan(sTestEventTriggerEnableKey)) == CHIP_NO_ERROR); VerifyOrDie(sTestEventTriggerDelegate.AddHandler(&sOtaTestEventTriggerHandler) == CHIP_NO_ERROR); +#ifdef CONFIG_CHIP_CRYPTO_PSA + initParams.operationalKeystore = &sPSAOperationalKeystore; +#endif (void) initParams.InitializeStaticResourcesBeforeServerInit(); initParams.testEventTriggerDelegate = &sTestEventTriggerDelegate; ReturnErrorOnFailure(chip::Server::GetInstance().Init(initParams)); AppFabricTableDelegate::Init(); +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS + err = MoveOperationalKeysFromKvsToIts(sLocalInitData.mServerInitParams->persistentStorageDelegate, + sLocalInitData.mServerInitParams->operationalKeystore); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("MoveOperationalKeysFromKvsToIts() failed"); + return err; + } +#endif + gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage()); chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider); ConfigurationMgr().LogDeviceConfig(); diff --git a/examples/platform/nrfconnect/util/MigrationManager.cpp b/examples/platform/nrfconnect/util/MigrationManager.cpp new file mode 100644 index 00000000000000..364120863bc7e2 --- /dev/null +++ b/examples/platform/nrfconnect/util/MigrationManager.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2024 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 "migration_manager.h" + +#include +#include + +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS +CHIP_ERROR MoveOperationalKeysFromKvsToIts(chip::PersistentStorageDelegate * storage, chip::Crypto::OperationalKeystore * keystore) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrReturnError(keystore && storage, CHIP_ERROR_INVALID_ARGUMENT); + + /* Initialize the obsolete Operational Keystore*/ + chip::PersistentStorageOperationalKeystore obsoleteKeystore; + err = obsoleteKeystore.Init(storage); + VerifyOrReturnError(err == CHIP_NO_ERROR, err); + + /* Migrate all obsolete Operational Keys to PSA ITS */ + for (const chip::FabricInfo & fabric : chip::Server::GetInstance().GetFabricTable()) + { + err = keystore->MigrateOpKeypairForFabric(fabric.GetFabricIndex(), obsoleteKeystore); + if (CHIP_NO_ERROR != err) + { + break; + } + } + +#ifdef CONFIG_CHIP_FACTORY_RESET_ON_KEY_MIGRATION_FAILURE + if (CHIP_NO_ERROR != err) + { + chip::Server::GetInstance().ScheduleFactoryReset(); + /* Return a success to not block the Matter event Loop and allow to call scheduled factory + * reset. */ + err = CHIP_NO_ERROR; + } +#endif /* CONFIG_CHIP_FACTORY_RESET_ON_KEY_MIGRATION_FAILURE */ + + return err; +} +#endif /* CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS */ diff --git a/examples/platform/nrfconnect/util/include/MigrationManager.h b/examples/platform/nrfconnect/util/include/MigrationManager.h new file mode 100644 index 00000000000000..22b2c5de110de3 --- /dev/null +++ b/examples/platform/nrfconnect/util/include/MigrationManager.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 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. + */ + +#pragma once + +#include + +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS +/** + * @brief Migrate all stored Operational Keys from the persistent storage (KVS) to secure PSA ITS. + * + * This function will schedule a factory reset automatically if the + * CONFIG_CHIP_FACTORY_RESET_ON_KEY_MIGRATION_FAILURE + * Kconfig option is set to 'y'. In this case, the function returns CHIP_NO_ERROR to not block any further + * operations until the scheduled factory reset is done. + * + * @note This function should be called just after Matter Server Init to avoid problems with further CASE + * session re-establishments. + * @param storage + * @param keystore + * @retval CHIP_NO_ERROR if all keys have been migrated properly to PSA ITS or if the error occurs, but + * the CONFIG_CHIP_FACTORY_RESET_ON_KEY_MIGRATION_FAILURE kconfig is set to 'y'. + * @retval CHIP_ERROR_INVALID_ARGUMENT when keystore or storage are not defined. + * @retval Other CHIP_ERROR codes related to internal Migration operations. + */ +CHIP_ERROR MoveOperationalKeysFromKvsToIts(chip::PersistentStorageDelegate * storage, chip::Crypto::OperationalKeystore * keystore); +#endif diff --git a/examples/pump-app/nrfconnect/main/AppTask.cpp b/examples/pump-app/nrfconnect/main/AppTask.cpp index 3a6eb9023ded0a..d5f4c6dc760e0e 100644 --- a/examples/pump-app/nrfconnect/main/AppTask.cpp +++ b/examples/pump-app/nrfconnect/main/AppTask.cpp @@ -41,6 +41,13 @@ #include "OTAUtil.h" #endif +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS +#include "MigrationManager.h" +#endif +#endif + #include #include #include @@ -77,6 +84,9 @@ bool sHaveBLEConnections = false; chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; +#ifdef CONFIG_CHIP_CRYPTO_PSA +chip::Crypto::PSAOperationalKeystore sPSAOperationalKeystore{}; +#endif } // namespace namespace LedConsts { @@ -191,11 +201,24 @@ CHIP_ERROR AppTask::Init() static OTATestEventTriggerHandler sOtaTestEventTriggerHandler{}; VerifyOrDie(sTestEventTriggerDelegate.Init(ByteSpan(sTestEventTriggerEnableKey)) == CHIP_NO_ERROR); VerifyOrDie(sTestEventTriggerDelegate.AddHandler(&sOtaTestEventTriggerHandler) == CHIP_NO_ERROR); +#ifdef CONFIG_CHIP_CRYPTO_PSA + initParams.operationalKeystore = &sPSAOperationalKeystore; +#endif (void) initParams.InitializeStaticResourcesBeforeServerInit(); initParams.testEventTriggerDelegate = &sTestEventTriggerDelegate; ReturnErrorOnFailure(chip::Server::GetInstance().Init(initParams)); AppFabricTableDelegate::Init(); +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS + err = MoveOperationalKeysFromKvsToIts(sLocalInitData.mServerInitParams->persistentStorageDelegate, + sLocalInitData.mServerInitParams->operationalKeystore); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("MoveOperationalKeysFromKvsToIts() failed"); + return err; + } +#endif + gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage()); chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider); diff --git a/examples/window-app/nrfconnect/main/AppTask.cpp b/examples/window-app/nrfconnect/main/AppTask.cpp index 7c50aedc97e2bb..5f971b67f9f9bf 100644 --- a/examples/window-app/nrfconnect/main/AppTask.cpp +++ b/examples/window-app/nrfconnect/main/AppTask.cpp @@ -37,6 +37,13 @@ #include "OTAUtil.h" #endif +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS +#include "MigrationManager.h" +#endif +#endif + #include #include #include @@ -72,6 +79,10 @@ chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; bool sIsNetworkProvisioned = false; bool sIsNetworkEnabled = false; bool sHaveBLEConnections = false; + +#ifdef CONFIG_CHIP_CRYPTO_PSA +chip::Crypto::PSAOperationalKeystore sPSAOperationalKeystore{}; +#endif } // namespace namespace LedConsts { @@ -193,11 +204,24 @@ CHIP_ERROR AppTask::Init() static OTATestEventTriggerHandler sOtaTestEventTriggerHandler{}; VerifyOrDie(sTestEventTriggerDelegate.Init(ByteSpan(sTestEventTriggerEnableKey)) == CHIP_NO_ERROR); VerifyOrDie(sTestEventTriggerDelegate.AddHandler(&sOtaTestEventTriggerHandler) == CHIP_NO_ERROR); +#ifdef CONFIG_CHIP_CRYPTO_PSA + initParams.operationalKeystore = &sPSAOperationalKeystore; +#endif (void) initParams.InitializeStaticResourcesBeforeServerInit(); initParams.testEventTriggerDelegate = &sTestEventTriggerDelegate; ReturnErrorOnFailure(chip::Server::GetInstance().Init(initParams)); AppFabricTableDelegate::Init(); +#ifdef CONFIG_CHIP_MIGRATE_OPERATIONAL_KEYS_TO_ITS + err = MoveOperationalKeysFromKvsToIts(sLocalInitData.mServerInitParams->persistentStorageDelegate, + sLocalInitData.mServerInitParams->operationalKeystore); + if (err != CHIP_NO_ERROR) + { + LOG_ERR("MoveOperationalKeysFromKvsToIts() failed"); + return err; + } +#endif + gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage()); chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider); diff --git a/src/platform/Zephyr/BLEManagerImpl.cpp b/src/platform/Zephyr/BLEManagerImpl.cpp index 0b2336ea7a6cc9..54cf69dc5e50fc 100644 --- a/src/platform/Zephyr/BLEManagerImpl.cpp +++ b/src/platform/Zephyr/BLEManagerImpl.cpp @@ -40,11 +40,7 @@ #include #include #include -#if CHIP_DEVICE_LAYER_TARGET_NRFCONNECT -#include -#else #include -#endif #include #include diff --git a/src/platform/Zephyr/PlatformManagerImpl.cpp b/src/platform/Zephyr/PlatformManagerImpl.cpp index 59f3a0df45314b..df8a40a55d15cd 100644 --- a/src/platform/Zephyr/PlatformManagerImpl.cpp +++ b/src/platform/Zephyr/PlatformManagerImpl.cpp @@ -21,9 +21,9 @@ * for Zephyr platforms. */ -#if !defined(CONFIG_NORDIC_SECURITY_BACKEND) +#if !defined(CONFIG_NRF_SECURITY) #include // nogncheck -#endif // !defined(CONFIG_NORDIC_SECURITY_BACKEND) +#endif // !defined(CONFIG_NRF_SECURITY) #include @@ -45,7 +45,7 @@ PlatformManagerImpl PlatformManagerImpl::sInstance{ sChipThreadStack }; static k_timer sOperationalHoursSavingTimer; -#if !defined(CONFIG_NORDIC_SECURITY_BACKEND) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) +#if !defined(CONFIG_NRF_SECURITY) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) static bool sChipStackEntropySourceAdded = false; static int app_entropy_source(void * data, unsigned char * output, size_t len, size_t * olen) { @@ -72,7 +72,7 @@ static int app_entropy_source(void * data, unsigned char * output, size_t len, s return ret; } -#endif // !defined(CONFIG_NORDIC_SECURITY_BACKEND) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) +#endif // !defined(CONFIG_NRF_SECURITY) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) void PlatformManagerImpl::OperationalHoursSavingTimerEventHandler(k_timer * timer) { @@ -109,16 +109,16 @@ CHIP_ERROR PlatformManagerImpl::_InitChipStack(void) { CHIP_ERROR err; -#if !defined(CONFIG_NORDIC_SECURITY_BACKEND) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) +#if !defined(CONFIG_NRF_SECURITY) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) // Minimum required from source before entropy is released ( with mbedtls_entropy_func() ) (in bytes) const size_t kThreshold = 16; -#endif // !defined(CONFIG_NORDIC_SECURITY_BACKEND) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) +#endif // !defined(CONFIG_NRF_SECURITY) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) // Initialize the configuration system. err = Internal::ZephyrConfig::Init(); SuccessOrExit(err); -#if !defined(CONFIG_NORDIC_SECURITY_BACKEND) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) +#if !defined(CONFIG_NRF_SECURITY) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) if (!sChipStackEntropySourceAdded) { // Add entropy source based on Zephyr entropy driver @@ -126,7 +126,7 @@ CHIP_ERROR PlatformManagerImpl::_InitChipStack(void) SuccessOrExit(err); sChipStackEntropySourceAdded = true; } -#endif // !defined(CONFIG_NORDIC_SECURITY_BACKEND) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) +#endif // !defined(CONFIG_NRF_SECURITY) && !defined(CONFIG_MBEDTLS_ZEPHYR_ENTROPY) // Call _InitChipStack() on the generic implementation base class to finish the initialization process. err = Internal::GenericPlatformManagerImpl_Zephyr::_InitChipStack(); diff --git a/src/platform/nrfconnect/CHIPPlatformConfig.h b/src/platform/nrfconnect/CHIPPlatformConfig.h index 90af43c0a10c6b..3ece933d377996 100644 --- a/src/platform/nrfconnect/CHIPPlatformConfig.h +++ b/src/platform/nrfconnect/CHIPPlatformConfig.h @@ -49,6 +49,16 @@ #define CHIP_CONFIG_SHA256_CONTEXT_SIZE 208 #endif +#ifdef CONFIG_CHIP_CRYPTO_PSA +#ifndef CHIP_CONFIG_SHA256_CONTEXT_ALIGN +#define CHIP_CONFIG_SHA256_CONTEXT_ALIGN psa_hash_operation_t +#endif // CHIP_CONFIG_SHA256_CONTEXT_ALIGN +#endif // CONFIG_CHIP_CRYPTO_PSA + +#ifndef CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE +#define CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE 0x30000 +#endif // CHIP_CONFIG_CRYPTO_PSA_KEY_ID_BASE + // ==================== General Configuration Overrides ==================== #ifndef CHIP_CONFIG_MAX_UNSOLICITED_MESSAGE_HANDLERS diff --git a/src/platform/nrfconnect/FactoryDataParser.c b/src/platform/nrfconnect/FactoryDataParser.c index 610c78ab3e724f..3a079e6a2a1984 100644 --- a/src/platform/nrfconnect/FactoryDataParser.c +++ b/src/platform/nrfconnect/FactoryDataParser.c @@ -91,7 +91,7 @@ bool FindUserDataEntry(struct FactoryData * factoryData, const char * entry, voi return false; } - ZCBOR_STATE_D(states, MAX_FACTORY_DATA_NESTING_LEVEL - 1, factoryData->user.data, factoryData->user.len, 1); + ZCBOR_STATE_D(states, MAX_FACTORY_DATA_NESTING_LEVEL - 1, factoryData->user.data, factoryData->user.len, 1, 0); bool res = zcbor_map_start_decode(states); bool keyFound = false; @@ -124,7 +124,7 @@ bool FindUserDataEntry(struct FactoryData * factoryData, const char * entry, voi bool ParseFactoryData(uint8_t * buffer, uint16_t bufferSize, struct FactoryData * factoryData) { memset(factoryData, 0, sizeof(*factoryData)); - ZCBOR_STATE_D(states, MAX_FACTORY_DATA_NESTING_LEVEL, buffer, bufferSize, 1); + ZCBOR_STATE_D(states, MAX_FACTORY_DATA_NESTING_LEVEL, buffer, bufferSize, 1, 0); bool res = zcbor_map_start_decode(states); struct zcbor_string currentString; diff --git a/src/platform/nrfconnect/FactoryDataProvider.cpp b/src/platform/nrfconnect/FactoryDataProvider.cpp index caa1ad434f9254..11995faccd8272 100644 --- a/src/platform/nrfconnect/FactoryDataProvider.cpp +++ b/src/platform/nrfconnect/FactoryDataProvider.cpp @@ -157,20 +157,62 @@ CHIP_ERROR FactoryDataProvider::SignWithDeviceAttestationKey(c { Crypto::P256ECDSASignature signature; Crypto::P256Keypair keypair; + CHIP_ERROR err = CHIP_NO_ERROR; +#ifdef CONFIG_CHIP_CRYPTO_PSA + psa_key_id_t keyId = 0; +#endif - VerifyOrReturnError(outSignBuffer.size() >= signature.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL); - ReturnErrorCodeIf(!mFactoryData.dac_cert.data, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); - ReturnErrorCodeIf(!mFactoryData.dac_priv_key.data, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + VerifyOrExit(outSignBuffer.size() >= signature.Capacity(), err = CHIP_ERROR_BUFFER_TOO_SMALL); + VerifyOrExit(mFactoryData.dac_cert.data, err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + VerifyOrExit(mFactoryData.dac_priv_key.data, err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + +#ifdef CONFIG_CHIP_CRYPTO_PSA + { + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_reset_key_attributes(&attributes); + psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attributes, kDACPrivateKeyLength * 8); + psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_MESSAGE); + VerifyOrExit(psa_import_key(&attributes, reinterpret_cast(mFactoryData.dac_priv_key.data), kDACPrivateKeyLength, + &keyId) == PSA_SUCCESS, + err = CHIP_ERROR_INTERNAL); + + size_t outputLen = 0; + psa_status_t status = psa_sign_message(keyId, PSA_ALG_ECDSA(PSA_ALG_SHA_256), messageToSign.data(), messageToSign.size(), + signature.Bytes(), signature.Capacity(), &outputLen); + VerifyOrExit(!status, err = CHIP_ERROR_INTERNAL); + VerifyOrExit(outputLen == chip::Crypto::kP256_ECDSA_Signature_Length_Raw, err = CHIP_ERROR_INTERNAL); + err = signature.SetLength(outputLen); + VerifyOrExit(err == CHIP_NO_ERROR, ); + } +#else + { + // Extract public key from DAC cert. + ByteSpan dacCertSpan{ reinterpret_cast(mFactoryData.dac_cert.data), mFactoryData.dac_cert.len }; + chip::Crypto::P256PublicKey dacPublicKey; + + err = chip::Crypto::ExtractPubkeyFromX509Cert(dacCertSpan, dacPublicKey); + VerifyOrExit(err == CHIP_NO_ERROR, ); + err = + LoadKeypairFromRaw(ByteSpan(reinterpret_cast(mFactoryData.dac_priv_key.data), mFactoryData.dac_priv_key.len), + ByteSpan(dacPublicKey.Bytes(), dacPublicKey.Length()), keypair); + VerifyOrExit(err == CHIP_NO_ERROR, ); + err = keypair.ECDSA_sign_msg(messageToSign.data(), messageToSign.size(), signature); + VerifyOrExit(err == CHIP_NO_ERROR, ); + } +#endif + +exit: - // Extract public key from DAC cert. - ByteSpan dacCertSpan{ reinterpret_cast(mFactoryData.dac_cert.data), mFactoryData.dac_cert.len }; - chip::Crypto::P256PublicKey dacPublicKey; +#ifdef CONFIG_CHIP_CRYPTO_PSA + psa_destroy_key(keyId); +#endif - ReturnErrorOnFailure(chip::Crypto::ExtractPubkeyFromX509Cert(dacCertSpan, dacPublicKey)); - ReturnErrorOnFailure( - LoadKeypairFromRaw(ByteSpan(reinterpret_cast(mFactoryData.dac_priv_key.data), mFactoryData.dac_priv_key.len), - ByteSpan(dacPublicKey.Bytes(), dacPublicKey.Length()), keypair)); - ReturnErrorOnFailure(keypair.ECDSA_sign_msg(messageToSign.data(), messageToSign.size(), signature)); + if (err != CHIP_NO_ERROR) + { + return err; + } return CopySpanToMutableSpan(ByteSpan{ signature.ConstBytes(), signature.Length() }, outSignBuffer); } diff --git a/src/platform/nrfconnect/FactoryDataProvider.h b/src/platform/nrfconnect/FactoryDataProvider.h index 15dae3ab7e3a0e..bc1ef16ee6d161 100644 --- a/src/platform/nrfconnect/FactoryDataProvider.h +++ b/src/platform/nrfconnect/FactoryDataProvider.h @@ -21,6 +21,10 @@ #include #include +#ifdef CONFIG_CHIP_CRYPTO_PSA +#include +#endif + #include #include #include diff --git a/src/platform/nrfconnect/Reboot.cpp b/src/platform/nrfconnect/Reboot.cpp index 439adebddc0ea4..6d52facf740fc3 100644 --- a/src/platform/nrfconnect/Reboot.cpp +++ b/src/platform/nrfconnect/Reboot.cpp @@ -42,7 +42,7 @@ SoftwareRebootReason GetSoftwareRebootReason() #else -using RetainedReason = decltype(nrf_power_gpregret_get(NRF_POWER)); +using RetainedReason = decltype(nrf_power_gpregret_get(NRF_POWER, 0)); constexpr RetainedReason EncodeReason(SoftwareRebootReason reason) { @@ -56,17 +56,17 @@ void Reboot(SoftwareRebootReason reason) { const RetainedReason retainedReason = EncodeReason(reason); - nrf_power_gpregret_set(NRF_POWER, retainedReason); + nrf_power_gpregret_set(NRF_POWER, 0, retainedReason); sys_reboot(retainedReason); } SoftwareRebootReason GetSoftwareRebootReason() { - switch (nrf_power_gpregret_get(NRF_POWER)) + switch (nrf_power_gpregret_get(NRF_POWER, 0)) { case EncodeReason(SoftwareRebootReason::kSoftwareUpdate): - nrf_power_gpregret_set(NRF_POWER, 0); + nrf_power_gpregret_set(NRF_POWER, 0, 0); return SoftwareRebootReason::kSoftwareUpdate; default: return SoftwareRebootReason::kOther; From 16ab164996ed626657c9e5825af252b0209fbf28 Mon Sep 17 00:00:00 2001 From: Ricardo Casallas <77841255+rcasallas-silabs@users.noreply.github.com> Date: Wed, 20 Mar 2024 09:48:54 -0400 Subject: [PATCH 06/11] [Silabs] Wi-fi: Header duplication removed. (#32633) * [Silabs] Wi-fi: Header duplication removed. * Restyled by clang-format --------- Co-authored-by: Restyled.io --- .../platform/silabs/efr32/rs911x/wfx_rsi.h | 106 ------------------ .../silabs/{SiWx917/SiWx917 => }/wfx_rsi.h | 19 +++- src/platform/silabs/rs911x/wfx_sl_ble_init.h | 3 - 3 files changed, 13 insertions(+), 115 deletions(-) delete mode 100644 examples/platform/silabs/efr32/rs911x/wfx_rsi.h rename examples/platform/silabs/{SiWx917/SiWx917 => }/wfx_rsi.h (93%) diff --git a/examples/platform/silabs/efr32/rs911x/wfx_rsi.h b/examples/platform/silabs/efr32/rs911x/wfx_rsi.h deleted file mode 100644 index 86591df72d36d3..00000000000000 --- a/examples/platform/silabs/efr32/rs911x/wfx_rsi.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * - * Copyright (c) 2022 Project CHIP Authors - * - * 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. - */ - -#ifndef _WFX_RSI_H_ -#define _WFX_RSI_H_ -/* - * Interface to RSI Sapis - */ - -#define WFX_RSI_WLAN_TASK_SZ (1024 + 512 + 256) /* Unknown how big this should be */ -#define WFX_RSI_TASK_SZ (1024 + 1024) /* Stack for the WFX/RSI task */ -#define WFX_RSI_BUF_SZ (1024 * 10) /* May need tweak */ -#define WFX_RSI_CONFIG_MAX_JOIN (5) /* Max join retries */ -#define WFX_RSI_NUM_TIMERS (2) /* Number of RSI timers to alloc */ - -/* - * Various events fielded by the wfx_rsi task - * Make sure that we only use 8 bits (otherwise freeRTOS - may need some changes) - */ -#define WFX_EVT_STA_CONN (0x01) -#define WFX_EVT_STA_DISCONN (0x02) -#define WFX_EVT_AP_START (0x04) -#define WFX_EVT_AP_STOP (0x08) -#define WFX_EVT_SCAN (0x10) /* This is used as scan result and start */ -#define WFX_EVT_STA_START_JOIN (0x20) -#define WFX_EVT_STA_DO_DHCP (0x40) -#define WFX_EVT_STA_DHCP_DONE (0x80) - -#define WFX_RSI_ST_DEV_READY (0x01) -#define WFX_RSI_ST_AP_READY (0x02) -#define WFX_RSI_ST_STA_PROVISIONED (0x04) -#define WFX_RSI_ST_STA_CONNECTING (0x08) -#define WFX_RSI_ST_STA_CONNECTED (0x10) -#define WFX_RSI_ST_STA_DHCP_DONE (0x40) /* Requested to do DHCP after conn */ -#define WFX_RSI_ST_STA_MODE (0x80) /* Enable Station Mode */ -#define WFX_RSI_ST_AP_MODE (0x100) /* Enable AP Mode */ -#define WFX_RSI_ST_STA_READY (WFX_RSI_ST_STA_CONNECTED | WFX_RSI_ST_STA_DHCP_DONE) -#define WFX_RSI_ST_STARTED (0x200) /* RSI task started */ -#define WFX_RSI_ST_SCANSTARTED (0x400) /* Scan Started */ -#define WFX_RSI_ST_SLEEP_READY (0x800) /* Notify the M4 to go to sleep*/ - -typedef struct wfx_rsi_s -{ - EventGroupHandle_t events; - TaskHandle_t drv_task; - TaskHandle_t wlan_task; -#ifdef RSI_BLE_ENABLE - TaskHandle_t ble_task; -#endif - uint16_t dev_state; - uint16_t ap_chan; /* The chan our STA is using */ - wfx_wifi_provision_t sec; -#ifdef SL_WFX_CONFIG_SCAN - void (*scan_cb)(wfx_wifi_scan_result_t *); - char * scan_ssid; /* Which one are we scanning for */ -#endif -#ifdef SL_WFX_CONFIG_SOFTAP - sl_wfx_mac_address_t softap_mac; -#endif - sl_wfx_mac_address_t sta_mac; - sl_wfx_mac_address_t ap_mac; /* To which our STA is connected */ - sl_wfx_mac_address_t ap_bssid; /* To which our STA is connected */ - uint16_t join_retries; - uint8_t ip4_addr[4]; /* Not sure if this is enough */ -} WfxRsi_t; - -extern WfxRsi_t wfx_rsi; -#ifdef __cplusplus -extern "C" { -#endif -void wfx_rsidev_init(void); -void wfx_rsi_task(void * arg); -#if CHIP_DEVICE_CONFIG_ENABLE_IPV4 -void wfx_ip_changed_notify(int got_ip); -#endif /* CHIP_DEVICE_CONFIG_ENABLE_IPV4 */ -int32_t wfx_rsi_get_ap_info(wfx_wifi_scan_result_t * ap); -int32_t wfx_rsi_get_ap_ext(wfx_wifi_scan_ext_t * extra_info); -int32_t wfx_rsi_reset_count(); -int32_t wfx_rsi_disconnect(); -#if SL_ICD_ENABLED -#if SLI_SI917 -int32_t wfx_rsi_power_save(rsi_power_save_profile_mode_t sl_si91x_ble_state, sl_si91x_performance_profile_t sl_si91x_wifi_state); -#else -int32_t wfx_rsi_power_save(); -#endif /* SLI_SI917 */ -#endif /* SL_ICD_ENABLED */ - -#ifdef __cplusplus -} -#endif - -#endif /* _WFX_RSI_H_ */ diff --git a/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi.h b/examples/platform/silabs/wfx_rsi.h similarity index 93% rename from examples/platform/silabs/SiWx917/SiWx917/wfx_rsi.h rename to examples/platform/silabs/wfx_rsi.h index acafeb30d72b25..445671f4566b49 100644 --- a/examples/platform/silabs/SiWx917/SiWx917/wfx_rsi.h +++ b/examples/platform/silabs/wfx_rsi.h @@ -14,11 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#pragma once -#ifndef _WFX_RSI_H_ -#define _WFX_RSI_H_ +#include -#include "event_groups.h" +#ifndef RSI_BLE_ENABLE +#define RSI_BLE_ENABLE (1) +#endif /* * Interface to RSI Sapis @@ -30,6 +32,7 @@ #define WFX_RSI_CONFIG_MAX_JOIN (5) /* Max join retries */ // TODO: Default values are usually in minutes, but this is in ms. Confirm if this is correct #define WFX_RSI_DHCP_POLL_INTERVAL (250) /* Poll interval in ms for DHCP */ +#define WFX_RSI_NUM_TIMERS (2) /* Number of RSI timers to alloc */ typedef enum { @@ -78,7 +81,9 @@ typedef struct wfx_rsi_s TaskHandle_t drv_task; TaskHandle_t wlan_task; TaskHandle_t init_task; +#ifdef RSI_BLE_ENABLE TaskHandle_t ble_task; +#endif uint16_t dev_state; uint16_t ap_chan; /* The chan our STA is using */ wfx_wifi_provision_t sec; @@ -97,6 +102,7 @@ typedef struct wfx_rsi_s } WfxRsi_t; extern WfxRsi_t wfx_rsi; + #ifdef __cplusplus extern "C" { #endif @@ -112,11 +118,12 @@ int32_t wfx_rsi_disconnect(); int32_t wfx_wifi_rsi_init(void); #if SL_ICD_ENABLED void sl_wfx_host_si91x_sleep_wakeup(); +#if SLI_SI917 int32_t wfx_rsi_power_save(rsi_power_save_profile_mode_t sl_si91x_ble_state, sl_si91x_performance_profile_t sl_si91x_wifi_state); +#else +int32_t wfx_rsi_power_save(); +#endif /* SLI_SI917 */ #endif /* SL_ICD_ENABLED */ - #ifdef __cplusplus } #endif - -#endif /* _WFX_RSI_H_ */ diff --git a/src/platform/silabs/rs911x/wfx_sl_ble_init.h b/src/platform/silabs/rs911x/wfx_sl_ble_init.h index 19a28888f1864d..e3d11e0057a98f 100644 --- a/src/platform/silabs/rs911x/wfx_sl_ble_init.h +++ b/src/platform/silabs/rs911x/wfx_sl_ble_init.h @@ -27,9 +27,6 @@ #ifndef WFX_SL_BLE_INIT #define WFX_SL_BLE_INIT -#ifndef RSI_BLE_ENABLE -#define RSI_BLE_ENABLE (1) -#endif // RSI_BLE_ENABLE // BLE include file to refer BLE APIs #include "FreeRTOS.h" From 907883f9d4ee06f8537e8177298c8f9c91cfe520 Mon Sep 17 00:00:00 2001 From: dinabenamar <108664279+dinabenamar@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:05:20 +0100 Subject: [PATCH 07/11] [NXP] Integrating code updates for RW61X and K32W platforms (#32615) * [NXP][platform][common] Adding platform common code * [NXP][platform][k32w] Adding platform code changes for k32w * [NXP][platform][rw61x] Adding platform code changes for rw61x * [NXP][tools][common] Adding nxp tools updates * [NXP][examples][common] Adding examples common updates * [NXP][examples][rw61x] Adding examples updates * [NXP][examples][k32w] Adding examples updates * [NXP][doc][common] Adding documentation updates * [NXP][scripts][common] Add checkout_submodules NXP target All submodules that don't have a platforms field will be selected when the script is run. Additionally, if --platform nxp is used, submodules that have nxp under their platform field will also be checked out. Signed-off-by: marius-alex-tache * [NXP][CI][rw61x] Enabling CI workflow for RW61x examples (all-clusters-app, thermostat, laundry-washer) * [NXP][examples][common] Fix laundry-washer app by removing af-enums.h include in the operational-state-delegate-impl.h * [NXP][platform][k32w1] Fix clang formatting Signed-off-by: marius-alex-tache * [NXP][doc][rw61x] Adding information to run bootstrap script + submodule update script + Updating gn information to build Matter target that will support BR Signed-off-by: Gatien Chapon * [NXP][platform] Fix string keys saving failure The "NXPConfig" class member function which is supposed to save the string keys to persistent storage was, in fact, invoking the "NvSaveOnIdle" function using the INTEGER keys RAM buffer descriptor, instead of using the STRING keys RAM buffer descriptor. Most likely a typical copy-paste error. Signed-off-by: Marian Chereji * [NXP][platform][k32w] Small fixes for k32w platform code * correct typos * remove executable access permissions * remove unused files * change openthread_root to ot-nxp/openthread-k32w1 Signed-off-by: Andrei Menzopol * [NXP][examples][k32w] Fixes for k32w examples code * small changes for existing code Signed-off-by: Andrei Menzopol * [NXP][sdk][common] Remove chip_enable_icd_lit * argument has been moved to src/app/icd/icd.gni Signed-off-by: Andrei Menzopol * [NXP][examples][common] Fix missing QR code log * [NXP][platform][k32w1] Fix gn issue Signed-off-by: marius-alex-tache * [NXP][examples][k32w0] Update default args.gni for reference apps Signed-off-by: marius-alex-tache * [NXP][examples][k32w1] Update default args.gni for reference apps Signed-off-by: marius-alex-tache * [NXP][platform][k32w0] Move subscription and groups KVS keys to dedicated storages To avoid a large KVS RAM buffer and memory allocation issues when running TC-RR-1.1, move some specific keys in dedicated RAM storage instaces: * subscription keys will be stored in sSubscriptionStorage. * groups keys will be stored in sGroupsStorage. This RAM storage will use the extended search feature, because its size will be greater than current backend region size (2K). The key/value storage will be selected in each API based on some criteria: if the key is related to either subscription/groups, then both the key and the value will be stored in the same storage. Otherwise, the default keys and values storages are used. Signed-off-by: marius-alex-tache * [NXP][platform][k32w0] Add conversion for subscription and groups keys When CONFIG_CHIP_K32W0_KVS_MOVE_KEYS_TO_SPECIFIC_STORAGE is set to 1, the application can opt to move some keys from current KVS storage to a dedicated storage, based on the key name. Currently, the mechanism supports moving keys and values for: * subscriptions * groups Note: the system is meant to ensure backwards compatibility. This should only be used once, but, if this feature is still enabled after the first conversion, the corresponding keys will not be found in KVS default storage. This does not affect the functionality, but it introduces a delay in the initialization. Signed-off-by: marius-alex-tache * [NXP][examples][common] Updating laundry washer zap file to be consistent with current PICS * [NXP][examples][common] Updating thermostat zap files to be consistent with current PICS * [NXP][platform][rw61x] Add define for encrypted factory data CONFIG_CHIP_ENCRYPTED_FACTORY_DATA is defined by default when factory data is enabled. The common code should leverage this flag to make some actions when implicit encrypted factory data is enabled. Signed-off-by: marius-alex-tache * [NXP][examples][common][factory-data] Enclose encryption code Encrypted factory data related code should be enclosed based on the CONFIG_CHIP_ENCRYPTED_FACTORY_DATA flag. Signed-off-by: marius-alex-tache * [NXP][examples][common] Add a way to include a specific OTAImageProcessor header CONFIG_CHIP_OTA_IMAGE_PROCESSOR_HEADER should specify an OTA image processor implementation header. It can be a custom one, such as the header for OTA multi-image implementation. This will ease the transition of current reference apps that are not using the processor from the NXP common area. Signed-off-by: marius-alex-tache * [NXP][examples][common] Enclose binding usage based on EMBER_AF_PLUGIN_BINDING EMBER_AF_PLUGIN_BINDING is defined when the server side of the Binding cluster is used. The common app task code should initialize Binding handlers only if this flag is defined. Signed-off-by: marius-alex-tache * [NXP][examples][k32w][contact-sensor][zap] Enable Diagnostic Logs Cluster Signed-off-by: Doru Gucea * [NXP][platform][common] Add Diagnostic Logs common Signed-off-by: Doru Gucea * [NXP][platform][common] create nxp_diagnostic_logs Signed-off-by: Doru Gucea * [NXP][examples][k32w1] enable Diagnostic Logs for contact-sensor app Signed-off-by: Doru Gucea * [NXP][example][common] Fixing PLATFORM_InitTimerManager return value check issue Signed-off-by: Gatien Chapon * [NXP][examples][common] Fix contact-sensor zap files Signed-off-by: Doru Gucea * [NXP][examples][common] Fix laundry-washer-app build : remove zap_generated_dir from BUILD.gn as it does not exist anymore * [NXP][doc] Adding support for the laundry-washer example inside docs/examples/index.md * [NXP][doc][rw61x] Update readme to support SDK 2.13.3 * [NXP][examples][k32w][mw320] Remove not upstreamed and not supported apps * drop light-switch-combo-app as it isn't upstreamed yet * remove lock-app as it is not supported anymore (at least for the moment) * remove shell-app as it is not supported anymore (at least for the moment) Signed-off-by: Andrei Menzopol * [NXP][scripts] Remove unsused binaries Signed-off-by: marius-alex-tache * [NXP][platform][k32w] Remove duplicated code introduced in a merge conflict Signed-off-by: marius-alex-tache * [NXP][common] Update OT-NXP repo commit Signed-off-by: Andrei Menzopol * [NXP][CI][rw61x] Updating docker image version for SDK support * [NXP][CI][k32w] Update workflows and docker image version for SDK support Signed-off-by: Andrei Menzopol * [NXP][examples][k32w] Fix spelling Signed-off-by: Andrei Menzopol * [NXP][rw61x] Fix doc, spelling and zap errors * [NXP][rw61x] Fix doc spelling errors * [NXP][k32w0] Fix SDK path Signed-off-by: marius-alex-tache * Restyled by whitespace * Restyled by clang-format * Restyled by gn * Restyled by prettier-markdown * Restyled by autopep8 * Restyled by isort * [NXP][doc][k32w] Fix docs issues Signed-off-by: marius-alex-tache * [NXP][scripts][common] Fix lint errors Signed-off-by: marius-alex-tache * [NXP][CI][k32w] Remove deprecated examples Signed-off-by: marius-alex-tache * [NXP][docs] Remove lock app from commissioning guide Signed-off-by: marius-alex-tache * [NXP][platform][common] Remove unused header Signed-off-by: marius-alex-tache * [NXP][platform][k32w1] Remove custom openthread_root Signed-off-by: marius-alex-tache * [NXP][platform][rw61x] Fix lint code error * [NXP][k32w0] Fix gn import order Signed-off-by: marius-alex-tache * [NXP][sdk][k32w0] Fix SDK path condition When official repo CI is used, the docker image sets the west github SDK to /opt/sdk/core. This caused gn SDK files to mistakenly treat the SDK as a package SDK, which affected the paths. Signed-off-by: marius-alex-tache * [NXP][scripts][k32w] Take into account low power flags for k32w0 Pigweed tokenizer logging and expansion board gn args are enabled by default in the reference apps args.gni. Disable them explicitly for low power case. Signed-off-by: marius-alex-tache * [NXP][examples][k32w1] Move diagnostic_logs to a common examples area This type of app specific implementation should reside in the application space, instead of the device layer. Signed-off-by: marius-alex-tache --------- Signed-off-by: marius-alex-tache Signed-off-by: Gatien Chapon Signed-off-by: Marian Chereji Signed-off-by: Andrei Menzopol Signed-off-by: Doru Gucea Co-authored-by: marius-alex-tache Co-authored-by: Gatien Chapon Co-authored-by: Marian Chereji Co-authored-by: Andrei Menzopol Co-authored-by: Martin Girardot Co-authored-by: Doru Gucea Co-authored-by: Restyled.io --- .github/workflows/examples-k32w.yaml | 13 +- .github/workflows/examples-rw61x.yaml | 87 + .gitmodules | 6 +- docs/examples/index.md | 9 + docs/guides/nxp_k32w_android_commissioning.md | 23 +- docs/guides/nxp_manufacturing_flow.md | 86 +- docs/guides/nxp_rw61x_ota_software_update.md | 32 +- .../nxp/common/main/AppTask.cpp | 454 +- .../nxp/common/main/DeviceCallbacks.cpp | 158 +- .../nxp/common/main/include/AppEvent.h | 2 +- .../nxp/common/main/include/AppTask.h | 67 +- .../nxp/common/main/include/DeviceCallbacks.h | 31 +- .../all-clusters-app/nxp/common/main/main.cpp | 2 +- .../all-clusters-app/nxp/rt/rw61x/BUILD.gn | 145 +- .../all-clusters-app/nxp/rt/rw61x/README.md | 101 +- .../rw61x/include/config/CHIPProjectConfig.h | 5 + .../contact-sensor-app/nxp/k32w/k32w0/.gn | 3 + .../nxp/k32w/k32w0/BUILD.gn | 16 +- .../nxp/k32w/k32w0/README.md | 16 +- .../nxp/k32w/k32w0/args.gni | 12 +- .../k32w/k32w0/include/CHIPProjectConfig.h | 5 + .../nxp/k32w/k32w0/main/AppTask.cpp | 3 +- .../nxp/k32w/k32w1/BUILD.gn | 39 +- .../nxp/k32w/k32w1/README.md | 15 +- .../nxp/k32w/k32w1/args.gni | 8 + .../k32w/k32w1/include/CHIPProjectConfig.h | 12 +- .../nxp/k32w/k32w1/main/AppTask.cpp | 44 +- .../nxp/k32w/k32w1/main/ZclCallbacks.cpp | 16 + .../nxp/k32w/k32w1/main/include/AppTask.h | 5 + .../nxp/zap-lit/contact-sensor-app.matter | 60 + .../nxp/zap-lit/contact-sensor-app.zap | 154 +- .../nxp/zap-sit/contact-sensor-app.matter | 60 + .../nxp/zap-sit/contact-sensor-app.zap | 148 +- .../nxp/common/main/AppTask.cpp | 140 + .../nxp/common/main/DeviceCallbacks.cpp | 135 + .../nxp/common/main/ZclCallbacks.cpp | 49 + .../nxp/common/main}/include/AppEvent.h | 16 +- .../nxp/common/main/include/AppTask.h | 56 + .../nxp/common/main/include/DeviceCallbacks.h | 53 + .../include/operational-state-delegate-impl.h | 120 + .../nxp/common/main/main.cpp | 49 + .../main/operational-state-delegate-impl.cpp | 143 + .../nxp/rt/rw61x}/.gn | 5 + .../laundry-washer-app/nxp/rt/rw61x/BUILD.gn | 288 + .../laundry-washer-app/nxp/rt/rw61x/README.md | 5 + .../nxp/rt/rw61x/args.gni} | 12 +- .../nxp/rt/rw61x/build_overrides | 1 + .../rw61x/include/config/CHIPProjectConfig.h | 217 + .../nxp/rt/rw61x}/third_party/connectedhomeip | 0 .../laundry-washer-app/nxp/zap/BUILD.gn | 13 +- .../nxp/zap/laundry-washer-app.matter | 2342 ++++++++ .../nxp/zap/laundry-washer-app.zap | 5288 +++++++++++++++++ examples/lighting-app/nxp/k32w/k32w0/.gn | 3 + examples/lighting-app/nxp/k32w/k32w0/BUILD.gn | 15 +- .../lighting-app/nxp/k32w/k32w0/README.md | 28 +- examples/lighting-app/nxp/k32w/k32w0/args.gni | 10 +- .../k32w/k32w0/include/CHIPProjectConfig.h | 5 + .../nxp/k32w/k32w0/main/AppTask.cpp | 4 +- .../nxp/k32w/k32w0/main/include/AppTask.h | 1 - examples/lighting-app/nxp/k32w/k32w1/BUILD.gn | 33 +- .../lighting-app/nxp/k32w/k32w1/README.md | 29 +- examples/lighting-app/nxp/k32w/k32w1/args.gni | 7 + .../k32w/k32w1/include/CHIPProjectConfig.h | 5 + .../nxp/k32w/k32w1/main/AppTask.cpp | 51 +- .../nxp/k32w/k32w1/main/LightingManager.cpp | 49 +- .../nxp/k32w/k32w1/main/ZclCallbacks.cpp | 15 +- .../nxp/k32w/k32w1/main/include/AppTask.h | 2 +- .../k32w/k32w1/main/include/LightingManager.h | 12 +- examples/lock-app/nxp/k32w/k32w0/BUILD.gn | 163 - examples/lock-app/nxp/k32w/k32w0/README.md | 452 -- examples/lock-app/nxp/k32w/k32w0/args.gni | 27 - .../lock-app/nxp/k32w/k32w0/build_overrides | 1 - .../k32w/k32w0/include/CHIPProjectConfig.h | 220 - .../nxp/k32w/k32w0/include/FreeRTOSConfig.h | 184 - .../lock-app/nxp/k32w/k32w0/main/AppTask.cpp | 4 + .../nxp/k32w/k32w0/main/BoltLockManager.cpp | 235 - .../nxp/k32w/k32w0/main/ZclCallbacks.cpp | 50 - .../nxp/k32w/k32w0/main/include/AppTask.h | 104 - .../k32w/k32w0/main/include/BoltLockManager.h | 83 - .../nxp/k32w/k32w0/main/include/app_config.h | 56 - .../lock-app/nxp/k32w/k32w0/main/main.cpp | 209 - examples/lock-app/nxp/zap/lock-app.zap | 2 +- .../common/app_assert/source}/AppAssert.cpp | 0 .../nxp/common/app_task/include/AppTaskBase.h | 151 + .../common/app_task/include/AppTaskFreeRTOS.h | 93 + .../common/app_task/include/AppTaskZephyr.h | 62 + .../common/app_task/source/AppTaskBase.cpp | 252 + .../app_task/source/AppTaskFreeRTOS.cpp | 240 + .../common/app_task/source/AppTaskZephyr.cpp | 103 + .../include/CommonDeviceCallbacks.h | 69 + .../source/CommonDeviceCallbacks.cpp | 177 + .../include/CHIPDeviceManager.h | 0 .../source}/CHIPDeviceManager.cpp | 0 .../nxp/common/diagnostic_logs/BUILD.gn} | 15 +- .../DiagnosticLogsProviderDelegateImpl.cpp | 168 + .../DiagnosticLogsProviderDelegateImpl.h | 75 + .../factory_data}/include/AppFactoryData.h | 8 +- .../source}/AppFactoryDataDefaultImpl.cpp | 29 +- .../platform/nxp/common/icd/include/ICDUtil.h | 39 + .../nxp/common/icd/source/ICDUtil.cpp | 36 + .../matter_button}/include/AppMatterButton.h | 9 +- .../matter_button/source/AppMatterButton.cpp | 81 + .../source}/AppMatterButtonEmpty.cpp | 4 +- .../common/matter_cli}/include/AppMatterCli.h | 13 +- .../matter_cli/source}/AppMatterCli.cpp | 37 +- .../include}/OTARequestorInitiator.h | 19 +- .../source/OTARequestorInitiator.cpp | 54 + .../source/OTARequestorInitiatorCommon.cpp} | 37 +- .../source/OTARequestorInitiatorZephyr.cpp | 41 + examples/platform/nxp/k32w/k32w0/BUILD.gn | 6 +- examples/platform/nxp/k32w/k32w0/app/args.gni | 3 - .../k32w0/app/ldscripts/chip-k32w0x-linker.ld | 10 +- .../nxp/k32w/k32w0/app/support/BUILD.gn | 2 +- examples/platform/nxp/k32w/k32w0/args.gni | 5 - .../nxp/k32w/k32w0/scripts/sign-outdir.py | 3 +- .../nxp/k32w/k32w1/util/LEDWidget.cpp | 16 + .../nxp/k32w/k32w1/util/LED_Dimmer.cpp | 193 + .../nxp/k32w/k32w1/util/include/LEDWidget.h | 1 + .../nxp/k32w/k32w1/util/include/LED_Dimmer.h | 21 + .../nxp/rt/rw61x/app/ldscripts/RW610_flash.ld | 33 +- .../project_include/freeRTOS/FreeRTOSConfig.h | 2 +- .../openthread/OpenThreadConfig.h | 9 +- .../nxp/rt/rw61x/doc/images/mcuboot_demo.PNG | Bin 84377 -> 109544 bytes .../rt/rw61x/doc/images/mcux-sdk-download.PNG | Bin 110232 -> 127556 bytes .../source/AppFactoryDataExample.cpp | 59 + examples/shell/nxp/k32w/k32w0/BUILD.gn | 92 - examples/shell/nxp/k32w/k32w0/README.md | 10 - examples/shell/nxp/k32w/k32w0/build_overrides | 1 - .../shell/nxp/k32w/k32w0/include/AppTask.h | 89 - .../k32w/k32w0/include/CHIPProjectConfig.h | 152 - .../nxp/k32w/k32w0/include/FreeRTOSConfig.h | 165 - .../shell/nxp/k32w/k32w0/main/AppTask.cpp | 486 -- .../nxp/k32w/k32w0/main/include/app_config.h | 52 - examples/shell/nxp/k32w/k32w0/main/main.cpp | 175 - .../k32w/k32w0/third_party/connectedhomeip | 1 - .../thermostat/nxp/common/main/AppTask.cpp | 49 + .../nxp/common/main/DeviceCallbacks.cpp | 101 + .../nxp/common/main/ZclCallbacks.cpp | 41 + .../nxp/common}/main/include/AppEvent.h | 27 +- .../nxp/common/main/include/AppTask.h | 54 + .../nxp/common/main/include/DeviceCallbacks.h | 52 + examples/thermostat/nxp/common/main/main.cpp | 49 + .../k32w0 => thermostat/nxp/rt/rw61x}/.gn | 7 +- examples/thermostat/nxp/rt/rw61x/BUILD.gn | 278 + examples/thermostat/nxp/rt/rw61x/README.md | 5 + examples/thermostat/nxp/rt/rw61x/args.gni | 19 + .../thermostat/nxp/rt/rw61x/build_overrides | 1 + .../rw61x/include/config/CHIPProjectConfig.h | 217 + .../nxp/rt/rw61x/third_party/connectedhomeip | 1 + .../nxp/zap/thermostat_matter_thread.matter | 58 +- .../nxp/zap/thermostat_matter_thread.zap | 619 +- .../nxp/zap/thermostat_matter_wifi.matter | 67 +- .../nxp/zap/thermostat_matter_wifi.zap | 721 ++- scripts/build/BUILD.gn | 1 + scripts/build/build/targets.py | 21 + scripts/build/builders/k32w.py | 4 +- scripts/build/builders/rw61x.py | 124 + .../build/testdata/all_targets_linux_x64.txt | 1 + scripts/checkout_submodules.py | 3 +- .../nxp/factory_data_generator/custom.py | 59 + .../nxp/factory_data_generator/generate.py | 22 +- scripts/tools/nxp/generate_certs.py | 2 +- scripts/tools/nxp/ota/README.md | 2 +- .../ota/examples/ota_max_entries_example.json | 16 +- scripts/tools/nxp/ota/ota_image_tool.py | 12 +- src/credentials/tests/BUILD.gn | 2 +- src/lib/shell/BUILD.gn | 5 - src/lib/shell/streamer_k32w.cpp | 76 - src/lib/shell/streamer_nxp.cpp | 60 +- src/lwip/BUILD.gn | 15 +- src/lwip/k32w1/arch/cc.h | 90 + src/lwip/k32w1/arch/perf.h | 26 + src/lwip/k32w1/lwipopts.h | 172 + src/lwip/k32w1/lwippools.h | 1 + src/messaging/tests/BUILD.gn | 2 +- src/platform/BUILD.gn | 11 +- src/platform/device.gni | 9 +- .../nxp/common/CHIPNXPPlatformDefaultConfig.h | 2 +- .../nxp/common/ConnectivityManagerImpl.cpp | 99 +- .../nxp/common/ConnectivityManagerImpl.h | 16 +- .../nxp/common/DiagnosticDataProviderImpl.cpp | 4 +- src/platform/nxp/common/DnssdImpl.cpp | 797 +++ .../nxp/common/KeyValueStoreManagerImpl.cpp | 15 + src/platform/nxp/common/NXPConfig.cpp | 93 +- src/platform/nxp/common/NXPConfigKS.cpp | 494 ++ .../nxp/common/NetworkCommissioningDriver.h | 7 + .../common/NetworkCommissioningWiFiDriver.cpp | 40 +- .../factory_data/FactoryDataProvider.cpp | 70 +- .../common/factory_data/FactoryDataProvider.h | 6 +- .../nxp/k32w/common/BLEManagerCommon.cpp | 6 +- .../CHIPDevicePlatformRamStorageConfig.h | 37 +- .../nxp/k32w/common/FactoryDataProvider.cpp | 4 + .../nxp/k32w/common/FactoryDataProvider.h | 2 + .../nxp/k32w/common/K32W_OTA_README.md | 1 - .../nxp/k32w/common/OTAImageProcessorImpl.cpp | 17 +- src/platform/nxp/k32w/k32w0/BUILD.gn | 12 +- .../nxp/k32w/k32w0/CHIPDevicePlatformConfig.h | 21 +- .../k32w/k32w0/KeyValueStoreManagerImpl.cpp | 139 +- .../nxp/k32w/k32w0/KeyValueStoreManagerImpl.h | 4 + src/platform/nxp/k32w/k32w0/Logging.cpp | 7 - .../nxp/k32w/k32w0/ThreadStackManagerImpl.cpp | 2 - src/platform/nxp/k32w/k32w0/args.gni | 26 +- src/platform/nxp/k32w/k32w1/BUILD.gn | 6 +- .../k32w/k32w1/DiagnosticDataProviderImpl.cpp | 3 +- .../k32w/k32w1/FactoryDataProviderImpl.cpp | 7 - src/platform/nxp/k32w/k32w1/K32W1Config.cpp | 4 - src/platform/nxp/rt/rw61x/BUILD.gn | 59 +- .../nxp/rt/rw61x/CHIPPlatformConfig.h | 5 + src/platform/nxp/rt/rw61x/ELSFactoryData.c | 453 ++ src/platform/nxp/rt/rw61x/ELSFactoryData.h | 247 + .../nxp/rt/rw61x/FactoryDataProviderImpl.cpp | 900 +++ .../nxp/rt/rw61x/FactoryDataProviderImpl.h | 79 + src/platform/nxp/rt/rw61x/args.gni | 11 +- src/system/BUILD.gn | 5 - src/transport/tests/BUILD.gn | 3 +- third_party/nxp/k32w0_sdk/BUILD.gn | 11 +- third_party/nxp/k32w0_sdk/k32w0_sdk.gni | 37 +- third_party/nxp/k32w0_sdk/nxp_arm.gni | 22 + ...32w0_executable.gni => nxp_executable.gni} | 0 third_party/nxp/k32w1_sdk/k32w1_sdk.gni | 13 + third_party/nxp/rt_sdk/BUILD.gn | 87 +- third_party/nxp/rt_sdk/bt_ble/bt_ble.gni | 16 +- .../nxp/rt_sdk/lwip/common/lwipopts_common.h | 10 +- .../nxp/rt_sdk/lwip/ethernet/lwipopts.h | 39 + .../rt_sdk/lwip/wifi_openthread/lwipopts.h | 25 + third_party/nxp/rt_sdk/mbedtls/mbedtls.gni | 3 + third_party/nxp/rt_sdk/rt_executable.gni | 2 +- third_party/nxp/rt_sdk/rt_sdk.gni | 86 +- third_party/nxp/rt_sdk/rw61x/BUILD.gn | 87 +- third_party/nxp/rt_sdk/rw61x/rw61x.gni | 151 +- .../nxp/rt_sdk/sdk_hook/fatfs/config/ffconf.h | 302 + .../rt_sdk/sdk_hook/zephyr/bluetooth/hci.h | 17 + .../transceiver/app_transceiver_config.h | 14 +- third_party/openthread/ot-nxp | 2 +- .../platforms/nxp/k32w/k32w0/BUILD.gn | 10 +- .../platforms/nxp/k32w/k32w1/BUILD.gn | 1 - .../platforms/nxp/rt/rw61x/BUILD.gn | 35 +- 237 files changed, 19298 insertions(+), 4703 deletions(-) create mode 100644 .github/workflows/examples-rw61x.yaml create mode 100644 examples/laundry-washer-app/nxp/common/main/AppTask.cpp create mode 100644 examples/laundry-washer-app/nxp/common/main/DeviceCallbacks.cpp create mode 100644 examples/laundry-washer-app/nxp/common/main/ZclCallbacks.cpp rename examples/{shell/nxp/k32w/k32w0 => laundry-washer-app/nxp/common/main}/include/AppEvent.h (80%) create mode 100644 examples/laundry-washer-app/nxp/common/main/include/AppTask.h create mode 100644 examples/laundry-washer-app/nxp/common/main/include/DeviceCallbacks.h create mode 100644 examples/laundry-washer-app/nxp/common/main/include/operational-state-delegate-impl.h create mode 100644 examples/laundry-washer-app/nxp/common/main/main.cpp create mode 100644 examples/laundry-washer-app/nxp/common/main/operational-state-delegate-impl.cpp rename examples/{lock-app/nxp/k32w/k32w0 => laundry-washer-app/nxp/rt/rw61x}/.gn (85%) create mode 100644 examples/laundry-washer-app/nxp/rt/rw61x/BUILD.gn create mode 100644 examples/laundry-washer-app/nxp/rt/rw61x/README.md rename examples/{build_overrides/k32w0_sdk.gni => laundry-washer-app/nxp/rt/rw61x/args.gni} (69%) create mode 120000 examples/laundry-washer-app/nxp/rt/rw61x/build_overrides create mode 100644 examples/laundry-washer-app/nxp/rt/rw61x/include/config/CHIPProjectConfig.h rename examples/{lock-app/nxp/k32w/k32w0 => laundry-washer-app/nxp/rt/rw61x}/third_party/connectedhomeip (100%) rename build_overrides/k32w0_sdk.gni => examples/laundry-washer-app/nxp/zap/BUILD.gn (70%) create mode 100644 examples/laundry-washer-app/nxp/zap/laundry-washer-app.matter create mode 100644 examples/laundry-washer-app/nxp/zap/laundry-washer-app.zap delete mode 100644 examples/lock-app/nxp/k32w/k32w0/BUILD.gn delete mode 100644 examples/lock-app/nxp/k32w/k32w0/README.md delete mode 100644 examples/lock-app/nxp/k32w/k32w0/args.gni delete mode 120000 examples/lock-app/nxp/k32w/k32w0/build_overrides delete mode 100644 examples/lock-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h delete mode 100644 examples/lock-app/nxp/k32w/k32w0/include/FreeRTOSConfig.h delete mode 100644 examples/lock-app/nxp/k32w/k32w0/main/BoltLockManager.cpp delete mode 100644 examples/lock-app/nxp/k32w/k32w0/main/ZclCallbacks.cpp delete mode 100644 examples/lock-app/nxp/k32w/k32w0/main/include/AppTask.h delete mode 100644 examples/lock-app/nxp/k32w/k32w0/main/include/BoltLockManager.h delete mode 100644 examples/lock-app/nxp/k32w/k32w0/main/include/app_config.h delete mode 100644 examples/lock-app/nxp/k32w/k32w0/main/main.cpp rename examples/{all-clusters-app/nxp/common/main => platform/nxp/common/app_assert/source}/AppAssert.cpp (100%) create mode 100644 examples/platform/nxp/common/app_task/include/AppTaskBase.h create mode 100644 examples/platform/nxp/common/app_task/include/AppTaskFreeRTOS.h create mode 100644 examples/platform/nxp/common/app_task/include/AppTaskZephyr.h create mode 100644 examples/platform/nxp/common/app_task/source/AppTaskBase.cpp create mode 100644 examples/platform/nxp/common/app_task/source/AppTaskFreeRTOS.cpp create mode 100644 examples/platform/nxp/common/app_task/source/AppTaskZephyr.cpp create mode 100644 examples/platform/nxp/common/device_callbacks/include/CommonDeviceCallbacks.h create mode 100644 examples/platform/nxp/common/device_callbacks/source/CommonDeviceCallbacks.cpp rename examples/{all-clusters-app/nxp/common/main => platform/nxp/common/device_manager}/include/CHIPDeviceManager.h (100%) rename examples/{all-clusters-app/nxp/common/main => platform/nxp/common/device_manager/source}/CHIPDeviceManager.cpp (100%) rename examples/{shell/nxp/k32w/k32w0/args.gni => platform/nxp/common/diagnostic_logs/BUILD.gn} (62%) create mode 100644 examples/platform/nxp/common/diagnostic_logs/DiagnosticLogsProviderDelegateImpl.cpp create mode 100644 examples/platform/nxp/common/diagnostic_logs/DiagnosticLogsProviderDelegateImpl.h rename examples/{all-clusters-app/nxp/common/main => platform/nxp/common/factory_data}/include/AppFactoryData.h (89%) rename examples/{all-clusters-app/nxp/common/main => platform/nxp/common/factory_data/source}/AppFactoryDataDefaultImpl.cpp (75%) create mode 100644 examples/platform/nxp/common/icd/include/ICDUtil.h create mode 100644 examples/platform/nxp/common/icd/source/ICDUtil.cpp rename examples/{all-clusters-app/nxp/common/main => platform/nxp/common/matter_button}/include/AppMatterButton.h (87%) create mode 100644 examples/platform/nxp/common/matter_button/source/AppMatterButton.cpp rename examples/{all-clusters-app/nxp/common/main => platform/nxp/common/matter_button/source}/AppMatterButtonEmpty.cpp (89%) rename examples/{all-clusters-app/nxp/common/main => platform/nxp/common/matter_cli}/include/AppMatterCli.h (61%) rename examples/{all-clusters-app/nxp/common/main => platform/nxp/common/matter_cli/source}/AppMatterCli.cpp (80%) rename examples/platform/nxp/common/{ => ota_requestor/include}/OTARequestorInitiator.h (78%) create mode 100644 examples/platform/nxp/common/ota_requestor/source/OTARequestorInitiator.cpp rename examples/platform/nxp/common/{OTARequestorInitiator.cpp => ota_requestor/source/OTARequestorInitiatorCommon.cpp} (61%) create mode 100644 examples/platform/nxp/common/ota_requestor/source/OTARequestorInitiatorZephyr.cpp create mode 100644 examples/platform/nxp/k32w/k32w1/util/LED_Dimmer.cpp create mode 100644 examples/platform/nxp/k32w/k32w1/util/include/LED_Dimmer.h create mode 100644 examples/platform/nxp/rt/rw61x/factory_data/source/AppFactoryDataExample.cpp delete mode 100644 examples/shell/nxp/k32w/k32w0/BUILD.gn delete mode 100644 examples/shell/nxp/k32w/k32w0/README.md delete mode 120000 examples/shell/nxp/k32w/k32w0/build_overrides delete mode 100644 examples/shell/nxp/k32w/k32w0/include/AppTask.h delete mode 100644 examples/shell/nxp/k32w/k32w0/include/CHIPProjectConfig.h delete mode 100644 examples/shell/nxp/k32w/k32w0/include/FreeRTOSConfig.h delete mode 100644 examples/shell/nxp/k32w/k32w0/main/AppTask.cpp delete mode 100644 examples/shell/nxp/k32w/k32w0/main/include/app_config.h delete mode 100644 examples/shell/nxp/k32w/k32w0/main/main.cpp delete mode 120000 examples/shell/nxp/k32w/k32w0/third_party/connectedhomeip create mode 100644 examples/thermostat/nxp/common/main/AppTask.cpp create mode 100644 examples/thermostat/nxp/common/main/DeviceCallbacks.cpp create mode 100644 examples/thermostat/nxp/common/main/ZclCallbacks.cpp rename examples/{lock-app/nxp/k32w/k32w0 => thermostat/nxp/common}/main/include/AppEvent.h (65%) create mode 100644 examples/thermostat/nxp/common/main/include/AppTask.h create mode 100644 examples/thermostat/nxp/common/main/include/DeviceCallbacks.h create mode 100644 examples/thermostat/nxp/common/main/main.cpp rename examples/{shell/nxp/k32w/k32w0 => thermostat/nxp/rt/rw61x}/.gn (80%) create mode 100644 examples/thermostat/nxp/rt/rw61x/BUILD.gn create mode 100644 examples/thermostat/nxp/rt/rw61x/README.md create mode 100644 examples/thermostat/nxp/rt/rw61x/args.gni create mode 120000 examples/thermostat/nxp/rt/rw61x/build_overrides create mode 100644 examples/thermostat/nxp/rt/rw61x/include/config/CHIPProjectConfig.h create mode 120000 examples/thermostat/nxp/rt/rw61x/third_party/connectedhomeip create mode 100644 scripts/build/builders/rw61x.py delete mode 100644 src/lib/shell/streamer_k32w.cpp create mode 100644 src/lwip/k32w1/arch/cc.h create mode 100644 src/lwip/k32w1/arch/perf.h create mode 100644 src/lwip/k32w1/lwipopts.h create mode 100644 src/lwip/k32w1/lwippools.h create mode 100644 src/platform/nxp/common/DnssdImpl.cpp create mode 100644 src/platform/nxp/common/NXPConfigKS.cpp create mode 100644 src/platform/nxp/rt/rw61x/ELSFactoryData.c create mode 100644 src/platform/nxp/rt/rw61x/ELSFactoryData.h create mode 100644 src/platform/nxp/rt/rw61x/FactoryDataProviderImpl.cpp create mode 100644 src/platform/nxp/rt/rw61x/FactoryDataProviderImpl.h create mode 100644 third_party/nxp/k32w0_sdk/nxp_arm.gni rename third_party/nxp/k32w0_sdk/{k32w0_executable.gni => nxp_executable.gni} (100%) create mode 100644 third_party/nxp/rt_sdk/lwip/ethernet/lwipopts.h create mode 100644 third_party/nxp/rt_sdk/sdk_hook/fatfs/config/ffconf.h create mode 100644 third_party/nxp/rt_sdk/sdk_hook/zephyr/bluetooth/hci.h diff --git a/.github/workflows/examples-k32w.yaml b/.github/workflows/examples-k32w.yaml index be2d372e88c177..4121768bd4cac5 100644 --- a/.github/workflows/examples-k32w.yaml +++ b/.github/workflows/examples-k32w.yaml @@ -37,7 +37,7 @@ jobs: if: github.actor != 'restyled-io[bot]' container: - image: ghcr.io/project-chip/chip-build-k32w:33 + image: ghcr.io/project-chip/chip-build-k32w:38 volumes: - "/tmp/bloat_reports:/tmp/bloat_reports" steps: @@ -46,7 +46,7 @@ jobs: - name: Checkout submodules & Bootstrap uses: ./.github/actions/checkout-submodules-and-bootstrap with: - platform: k32w + platform: nxp - name: Set up environment for size reports uses: ./.github/actions/setup-size-reports @@ -59,11 +59,8 @@ jobs: scripts/run_in_build_env.sh "\ ./scripts/build/build_examples.py \ --target k32w-k32w0-light-crypto-platform-tokenizer \ - --target k32w-k32w0-lock-crypto-platform-tokenizer \ - --target k32w-k32w0-lock-crypto-platform-low-power-nologs \ --target k32w-k32w0-contact-crypto-platform-tokenizer \ --target k32w-k32w0-contact-crypto-platform-low-power-nologs \ - --target k32w-k32w0-shell-crypto-platform \ --target k32w-k32w1-light-crypto-platform-openthread-ftd \ --target k32w-k32w1-contact-crypto-platform-low-power-nologs \ build \ @@ -79,12 +76,6 @@ jobs: k32w k32w1+release light \ out/artifacts/k32w-k32w1-light-crypto-platform-openthread-ftd/chip-k32w1-light-example.elf \ /tmp/bloat_reports/ - - name: Get lock size stats - run: | - .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ - k32w k32w0+release lock \ - out/artifacts/k32w-k32w0-lock-crypto-platform-tokenizer/chip-k32w0x-lock-example.elf \ - /tmp/bloat_reports/ - name: Get contact size stats run: | .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ diff --git a/.github/workflows/examples-rw61x.yaml b/.github/workflows/examples-rw61x.yaml new file mode 100644 index 00000000000000..42df03382b9249 --- /dev/null +++ b/.github/workflows/examples-rw61x.yaml @@ -0,0 +1,87 @@ +# Copyright (c) 2023 Project CHIP Authors +# +# 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. + +name: Build example - RW61X + +on: + push: + pull_request: + merge_group: + +concurrency: + group: ${{ github.ref }}-${{ github.workflow }}-${{ (github.event_name == 'pull_request' && github.event.number) || (github.event_name == 'workflow_dispatch' && github.run_number) || github.sha }} + cancel-in-progress: true + +env: + CHIP_NO_LOG_TIMESTAMPS: true + +jobs: + rw61x: + name: RW61X + + env: + BUILD_TYPE: gn_rw61x + + runs-on: ubuntu-latest + if: github.actor != 'restyled-io[bot]' + + container: + image: ghcr.io/project-chip/chip-build-rw61x:37 + volumes: + - "/tmp/bloat_reports:/tmp/bloat_reports" + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Checkout submodules & Bootstrap + uses: ./.github/actions/checkout-submodules-and-bootstrap + with: + platform: rw61x + + - name: Set up environment for size reports + uses: ./.github/actions/setup-size-reports + if: ${{ !env.ACT }} + with: + gh-context: ${{ toJson(github) }} + + - name: Build RW61X all clusters example app + run: | + scripts/run_in_build_env.sh "\ + ./scripts/build/build_examples.py \ + --target rw61x-all-clusters-app-wifi \ + build \ + --copy-artifacts-to out/artifacts \ + " + + - name: Build RW61X thermostat example app + run: | + scripts/run_in_build_env.sh "\ + ./scripts/build/build_examples.py \ + --target rw61x-thermostat-wifi \ + build \ + --copy-artifacts-to out/artifacts \ + " + + - name: Build RW61X laundry-washer example app + run: | + scripts/run_in_build_env.sh "\ + ./scripts/build/build_examples.py \ + --target rw61x-laundry-washer-wifi \ + build \ + --copy-artifacts-to out/artifacts \ + " + - name: Uploading Size Reports + uses: ./.github/actions/upload-size-reports + if: ${{ !env.ACT }} + with: + platform-name: RW61X diff --git a/.gitmodules b/.gitmodules index 06fb7f0e347951..fa3dfcfdaea9aa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -54,16 +54,16 @@ path = third_party/freertos/repo url = https://github.com/FreeRTOS/FreeRTOS-Kernel.git branch = V10.3.1-kernel-only - platforms = ameba,cc13xx_26xx,bouffalolab,esp32,k32w,infineon,qpg,cc32xx + platforms = ameba,cc13xx_26xx,bouffalolab,esp32,infineon,qpg,cc32xx [submodule "simw-top-mini"] path = third_party/simw-top-mini/repo url = https://github.com/NXP/plug-and-trust.git branch = int/CHIPSE_Release - platforms = k32w + platforms = nxp [submodule "third_party/openthread/ot-nxp"] path = third_party/openthread/ot-nxp url = https://github.com/NXP/ot-nxp.git - platforms = k32w + platforms = nxp [submodule "third_party/openthread/ot-qorvo"] path = third_party/openthread/ot-qorvo url = https://github.com/openthread/ot-qorvo.git diff --git a/docs/examples/index.md b/docs/examples/index.md index 2c9a0a4659b3bc..fc82f2a0bc9016 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -112,6 +112,15 @@ kotlin-matter-controller/README virtual-device-app/**/README ``` +## Laundry washer example + +```{toctree} +:glob: +:maxdepth: 1 + +laundry-washer-app/**/README +``` + ## Lighting example ```{toctree} diff --git a/docs/guides/nxp_k32w_android_commissioning.md b/docs/guides/nxp_k32w_android_commissioning.md index 3f1c6d1171df3c..b39a2f2afd2c8c 100644 --- a/docs/guides/nxp_k32w_android_commissioning.md +++ b/docs/guides/nxp_k32w_android_commissioning.md @@ -3,7 +3,7 @@ This article describes how to use [CHIPTool](../../examples/android/CHIPTool/README.md) for Android smartphones to commission an NXP K32W061 DK6 running -[NXP K32W Lock/Light Example Application](#building-and-programming-nxp-k32w-locklight-example-application) +[NXP K32W Light Example Application](#building-and-programming-nxp-k32w-light-example-application) onto a CHIP-enabled Thread network.
@@ -12,7 +12,7 @@ onto a CHIP-enabled Thread network. - [Requirements](#requirements) - [Building and programming OpenThread RCP firmware](#building-and-programming-openthread-rcp-firmware) - [Configuring PC as Thread Border Router](#configuring-pc-as-a-thread-border-router) -- [Building and programming NXP K32W Lock/Light Example Application](#building-and-programming-nxp-k32w-locklight-example-application) +- [Building and programming NXP K32W Light Example Application](#building-and-programming-nxp-k32w-light-example-application) - [Building and installing Android CHIPTool](#building-and-installing-android-chiptool) - [Forming a Thread network on the Border Router](#forming-a-thread-network-on-the-border-router) - [Preparing accessory device](#preparing-accessory-device) @@ -44,7 +44,7 @@ with a spare Wi-Fi card and an device. The following diagram shows the connectivity between network components required -to allow communication between devices running the CHIPTool and Lock/Light +to allow communication between devices running the CHIPTool and Light applications: ![nxp_hw_connectivity](../../examples/platform/nxp/k32w/k32w0/doc/images/nxp_hw_connectivity.JPG) @@ -345,11 +345,7 @@ To make your PC work as a Thread Border Router, complete the following tasks:
-## Building and programming NXP K32W Lock/Light Example Application - -See -[NXP K32W Lock Example Application README](../../examples/lock-app/nxp/k32w/k32w0/README.md) -to learn how to build and program the lock example onto an K32W061 DK6. +## Building and programming NXP K32W Light Example Application See [NXP K32W Light Example Application README](../../examples/lighting-app/nxp/k32w/k32w0/README.md) @@ -491,11 +487,10 @@ section, complete the following steps: 2. Verify that the text box on the screen is not empty and contains the IPv6 address of the accessory device. -3. Tap the following buttons to change the lock/light state: +3. Tap the following buttons to change the light state: - - _ON_ and _OFF_ buttons lock/turn on and unlock/turn off the door/light - bulb, respectively. - - _TOGGLE_ changes the lock/light state to the opposite. + - _ON_ and _OFF_ buttons turn on and turn off the light bulb, respectively. + - _TOGGLE_ changes the light state to the opposite. -The _LED D3_ on the device turns on or off based on the changes of the -lock/light state. +The _LED D3_ on the device turns on or off based on the changes of the light +state. diff --git a/docs/guides/nxp_manufacturing_flow.md b/docs/guides/nxp_manufacturing_flow.md index 64bafd53c89329..6cc3005b412289 100644 --- a/docs/guides/nxp_manufacturing_flow.md +++ b/docs/guides/nxp_manufacturing_flow.md @@ -1,8 +1,4 @@ ---- -orphan: true ---- - -# NXP manufacturing data guide +# NXP Manufacturing data By default, the example application is configured to use generic test certificates and provisioning data embedded with the application code. It is @@ -112,30 +108,30 @@ Here is the interpretation of the **required** parameters: --pai_cert -> path to the PAI (der format) location --spake2p_path -> path to the spake2p tool --out -> name of the binary that will be used for storing all the generated data - - ``` Here is the interpretation of the **optional** parameters: ```shell ---dac_key_password -> Password to decode DAC key ---dac_key_use_sss_blob -> Used when --dac_key contains a path to an encrypted blob, instead of the - actual DAC private key. The blob metadata size is 24, so the total length - of the resulting value is private key length (32) + 24 = 56. False by default. ---spake2p_verifier -> SPAKE2+ verifier (passed as base64 encoded string). If this option is set, - all SPAKE2+ inputs will be encoded in the final binary. The spake2p tool - will not be used to generate a new verifier on the fly. ---aes128_key -> 128 bits AES key used to encrypt the whole dataset. Please make sure - that the target application/board supports this feature: it has access to - the private key and implements a mechanism which can be used to decrypt - the factory data information. ---date -> Manufacturing Date (YYYY-MM-DD format) ---part_number -> Part number as string ---product_url -> Product URL as string ---product_label -> Product label as string ---serial_num -> Serial Number ---unique_id -> Unique id used for rotating device id generation +--dac_key_password -> Password to decode DAC key +--dac_key_use_sss_blob -> Used when --dac_key contains a path to an encrypted blob, instead of the + actual DAC private key. The blob metadata size is 24, so the total length + of the resulting value is private key length (32) + 24 = 56. False by default. +--spake2p_verifier -> SPAKE2+ verifier (passed as base64 encoded string). If this option is set, + all SPAKE2+ inputs will be encoded in the final binary. The spake2p tool + will not be used to generate a new verifier on the fly. +--aes128_key -> 128 bits AES key used to encrypt the whole dataset. Please make sure + that the target application/board supports this feature: it has access to + the private key and implements a mechanism which can be used to decrypt + the factory data information. +--date -> Manufacturing Date (YYYY-MM-DD format) +--part_number -> Part number as string +--product_url -> Product URL as string +--product_label -> Product label as string +--serial_num -> Serial Number +--unique_id -> Unique id used for rotating device id generation +--product_finish -> Visible finish of the product +--product_primary_color -> Representative color of the visible parts of the product ``` ## 3. Write provisioning data @@ -157,8 +153,18 @@ loadfile factory_data.bin 0xf4000 where `0xf4000` is the value of `__MATTER_FACTORY_DATA_START` in the corresponding .map file (can be different if using a custom linker script). -For the **RT1060**, **RT1170** and **RW61X** platform, the binary needs to be -written using `MCUXpresso Flash Tool GUI` at the address value corresponding to +For **RW61X** platform, the binary needs to be written in the internal flash at +location given by `__MATTER_FACTORY_DATA_START`, using `JLink`: + +``` +loadfile factory_data.bin 0xBFFF000 +``` + +where `0xBFFF000` is the value of `__FACTORY_DATA_START` in the corresponding +.map file (can be different if using a custom linker script). + +For the **RT1060** and **RT1170** platform, the binary needs to be written using +`MCUXpresso Flash Tool GUI` at the address value corresponding to `__FACTORY_DATA_START` (the map file of the application should be checked to get the exact value). @@ -198,6 +204,8 @@ Also, demo **DAC**, **PAI** and **PAA** certificates needed in case ## 6. Increased security for DAC private key +### 6.1 K32W1 + Supported platforms: - K32W1 - `src/plaftorm/nxp/k32w/k32w1/FactoryDataProviderImpl.h` @@ -242,3 +250,29 @@ python3 ./scripts/tools/nxp/factory_data_generator/generate.py -i 10000 -s UXKLz Please note that `--dac_key` now points to a binary file that contains the encrypted blob. + +### 6.2 RW61X + +Supported platforms: + +- RW61X - `src/plaftorm/nxp/rt/rw61x/FactoryDataProviderImpl.h` + +For platforms that have a secure subsystem (`SE50`), the DAC private key can be +converted to an encrypted blob. This blob will overwrite the DAC private key in +factory data and will be imported in the `SE50` before to sign, by the factory +data provider instance. + +The conversion process shall happen at manufacturing time and should be run one +time only: + +- Write factory data binary. +- Build the application with + `chip_with_factory_data=1 chip_convert_dac_private_key=1` set. +- Write the application to the board and let it run. + +After the conversion process: + +- Make sure the application is built with `chip_with_factory_data=1`, but + without `chip_convert_dac_private_key` arg, since conversion already + happened. +- Write the application to the board. diff --git a/docs/guides/nxp_rw61x_ota_software_update.md b/docs/guides/nxp_rw61x_ota_software_update.md index c3bd5227a054ed..9922909c5d1f7a 100644 --- a/docs/guides/nxp_rw61x_ota_software_update.md +++ b/docs/guides/nxp_rw61x_ota_software_update.md @@ -87,7 +87,8 @@ J-Link > erase 0x8000000, 0x88a0000 ``` - Using MCUXPresso, import the `mcuboot_opensource` demo example from the SDK - previously downloaded. + previously downloaded. The example can be found under the `ota_examples` + folder. ![mcuboot_demo](../../examples/platform/nxp/rt/rw61x/doc/images/mcuboot_demo.PNG) - Before building the demo example, it should be specified that the application to be run by the bootloader is monolithic. As a result, only one @@ -100,7 +101,20 @@ Right click on the Project -> Properties -> C/C++ Build -> Settings -> Tool Sett ![rw610_mcuboot_monolithic](../../examples/platform/nxp/rt/rw61x/doc/images/mcuboot_monolithic_app.PNG) -- Build the demo example project and program it to the target board. +- Build the demo example project. + +``` +Right click on the Project -> Build Project +``` + +- Program the demo example to the target board. + +``` +Right click on the Project -> Debug -> As->SEGGER JLink probes -> OK -> Select elf file +``` + +Note : The mcuboot binary is loaded in flash at address 0x8000000. + - To run the flashed demo, either press the reset button of the device or use the debugger IDE of MCUXpresso. If it runs successfully, the following logs will be displayed on the terminal : @@ -161,14 +175,14 @@ user@ubuntu: python3 imgtool.py sign --key ~/Desktop/SDK_RW612/boards/rdrw612bga Notes : -- If internal SDK is used instead, the key can be found in : - "`~/Desktop/SDK_RW612/middleware/mcuboot_opensource/boot/nxp_mcux_sdk/keys/sign-rsa2048-priv.pem`". -- The arguments `slot-size` and `max-sectors` should be adjusted to the size - of the partitions reserved for the primary and the secondary applications. - (By default the size considered is 4.4 MB) +- The arguments `slot-size` and `max-sectors` are aligned with the size of the + partitions reserved for the primary and the secondary applications. (By + default the size considered is 4.4 MB for each application). If the size of + these partitions are modified, the `slot-size` and `max-sectors` should be + adjusted accordingly. - In this example, the image is signed with the private key provided by the SDK as an example - (`/path_to_sdk/middleware/mcuboot_opensource/boot/nxp_mcux_sdk/keys/sign-rsa2048-priv.pem`), + (`SDK_RW612/boards/rdrw612bga/ota_examples/mcuboot_opensource/keys/sign-rsa2048-priv.pem`), MCUBoot is built with its corresponding public key which would be used to verify the integrity of the image. It is possible to generate a new pair of keys using the following commands. This procedure should be done prior to @@ -187,7 +201,7 @@ user@ubuntu: python3 imgtool.py getpub -k priv_key.pem ``` - The extracted public key can then be copied to the - `/path_to_sdk/middleware/mcuboot_opensource/boot/nxp_mcux_sdk/keys/sign-rsa2048-pub.c`, + `SDK_RW612/boards/rdrw612bga/ota_examples/mcuboot_opensource/keys/sign-rsa2048-pub.c`, given as a value to the rsa_pub_key[] array. The resulting output is the signed binary of the application version "1.0". diff --git a/examples/all-clusters-app/nxp/common/main/AppTask.cpp b/examples/all-clusters-app/nxp/common/main/AppTask.cpp index 9dc60db217cf9a..7f4c1f257d0405 100644 --- a/examples/all-clusters-app/nxp/common/main/AppTask.cpp +++ b/examples/all-clusters-app/nxp/common/main/AppTask.cpp @@ -2,7 +2,7 @@ * * Copyright (c) 2021-2023 Project CHIP Authors * Copyright (c) 2021 Google LLC. - * Copyright 2023 NXP + * Copyright 2023-2024 NXP * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,463 +17,41 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + #include "AppTask.h" -#include "AppEvent.h" #include "CHIPDeviceManager.h" -#include "DeviceCallbacks.h" -#include "binding-handler.h" -#include "lib/core/ErrorStr.h" -#include -#include -#include - -#if CHIP_DEVICE_CONFIG_ENABLE_WPA -#include -#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA - -#include -#include -#include -#include -#include -#include -#include -#include - +#include "ICDUtil.h" +#include #include -#include "AppFactoryData.h" -#include "AppMatterButton.h" -#include "AppMatterCli.h" - -#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR -#include "OTARequestorInitiator.h" -#endif - -#if ENABLE_OTA_PROVIDER -#include -#endif - -#if CHIP_ENABLE_OPENTHREAD -#include -#endif - -#if TCP_DOWNLOAD -#include "TcpDownload.h" -#endif - -#ifndef APP_TASK_STACK_SIZE -#define APP_TASK_STACK_SIZE ((configSTACK_DEPTH_TYPE) 6144 / sizeof(portSTACK_TYPE)) -#endif -#ifndef APP_TASK_PRIORITY -#define APP_TASK_PRIORITY 2 -#endif -#define APP_EVENT_QUEUE_SIZE 10 - -static QueueHandle_t sAppEventQueue; - using namespace chip; -using namespace chip::TLV; -using namespace ::chip::Credentials; -using namespace ::chip::DeviceLayer; -using namespace ::chip::DeviceManager; -using namespace ::chip::app::Clusters; - -chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; - -#if CHIP_DEVICE_CONFIG_ENABLE_WPA -namespace { -chip::app::Clusters::NetworkCommissioning::Instance - sWiFiNetworkCommissioningInstance(0 /* Endpoint Id */, - &(::chip::DeviceLayer::NetworkCommissioning::NXPWiFiDriver::GetInstance())); -} // namespace - -void NetWorkCommissioningInstInit() -{ - sWiFiNetworkCommissioningInstance.Init(); -} -#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA - -AppTask AppTask::sAppTask; - -CHIP_ERROR AppTask::StartAppTask() -{ - CHIP_ERROR err = CHIP_NO_ERROR; - TaskHandle_t taskHandle; - - sAppEventQueue = xQueueCreate(APP_EVENT_QUEUE_SIZE, sizeof(AppEvent)); - if (sAppEventQueue == NULL) - { - err = CHIP_ERROR_NO_MEMORY; - ChipLogError(DeviceLayer, "Failed to allocate app event queue"); - assert(err == CHIP_NO_ERROR); - } - - if (xTaskCreate(&AppTask::AppTaskMain, "AppTaskMain", APP_TASK_STACK_SIZE, &sAppTask, APP_TASK_PRIORITY, &taskHandle) != pdPASS) - { - err = CHIP_ERROR_NO_MEMORY; - ChipLogError(DeviceLayer, "Failed to start app task"); - assert(err == CHIP_NO_ERROR); - } - - return err; -} +using namespace chip::app::Clusters; -#if CHIP_ENABLE_OPENTHREAD -void LockOpenThreadTask(void) +void AllClustersApp::AppTask::PreInitMatterStack() { - chip::DeviceLayer::ThreadStackMgr().LockThreadStack(); + ChipLogProgress(DeviceLayer, "Welcome to NXP All Clusters Demo App"); } -void UnlockOpenThreadTask(void) +void AllClustersApp::AppTask::PostInitMatterStack() { - chip::DeviceLayer::ThreadStackMgr().UnlockThreadStack(); + chip::app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&chip::NXP::App::GetICDUtil()); } -#endif -void AppTask::InitServer(intptr_t arg) +void AllClustersApp::AppTask::PostInitMatterServerInstance() { - static chip::CommonCaseDeviceServerInitParams initParams; - (void) initParams.InitializeStaticResourcesBeforeServerInit(); - -#if CHIP_ENABLE_OPENTHREAD - // Init ZCL Data Model and start server - chip::Inet::EndPointStateOpenThread::OpenThreadEndpointInitParam nativeParams; - nativeParams.lockCb = LockOpenThreadTask; - nativeParams.unlockCb = UnlockOpenThreadTask; - nativeParams.openThreadInstancePtr = chip::DeviceLayer::ThreadStackMgrImpl().OTInstance(); - initParams.endpointNativeParams = static_cast(&nativeParams); -#endif - VerifyOrDie((chip::Server::GetInstance().Init(initParams)) == CHIP_NO_ERROR); - - gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage()); - chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider); - -#ifdef DEVICE_TYPE_ALL_CLUSTERS // Disable last fixed endpoint, which is used as a placeholder for all of the // supported clusters so that ZAP will generated the requisite code. emberAfEndpointEnableDisable(emberAfEndpointFromIndex(static_cast(emberAfFixedEndpointCount() - 1)), false); -#endif /* DEVICE_TYPE_ALL_CLUSTERS */ - -#if ENABLE_OTA_PROVIDER - InitOTAServer(); -#endif -} - -CHIP_ERROR AppTask::Init() -{ - CHIP_ERROR err = CHIP_NO_ERROR; - - /* Init Chip memory management before the stack */ - chip::Platform::MemoryInit(); - - /* Initialize Matter factory data before initializing the Matter stack */ - err = AppFactoryData_PreMatterStackInit(); - - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Pre Factory Data Provider init failed"); - goto exit; - } - - /* - * Initialize the CHIP stack. - * Would also initialize all required platform modules - */ - err = PlatformMgr().InitChipStack(); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "PlatformMgr().InitChipStack() failed: %s", ErrorStr(err)); - goto exit; - } - - /* Initialize Matter factory data after initializing the Matter stack */ - err = AppFactoryData_PostMatterStackInit(); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Post Factory Data Provider init failed"); - goto exit; - } - - /* - * Register all application callbacks allowing to be informed of stack events - */ - err = CHIPDeviceManager::GetInstance().Init(&deviceCallbacks); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "CHIPDeviceManager.Init() failed: %s", ErrorStr(err)); - goto exit; - } - -#if CHIP_DEVICE_CONFIG_ENABLE_WPA - ConnectivityMgrImpl().StartWiFiManagement(); -#endif - -#if CHIP_ENABLE_OPENTHREAD - err = ThreadStackMgr().InitThreadStack(); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Error during ThreadStackMgr().InitThreadStack()"); - goto exit; - } - - err = ConnectivityMgr().SetThreadDeviceType(ConnectivityManager::kThreadDeviceType_Router); - if (err != CHIP_NO_ERROR) - { - goto exit; - } -#endif - - /* - * Schedule an event to the Matter stack to initialize - * the ZCL Data Model and start server - */ - PlatformMgr().ScheduleWork(InitServer, 0); - - /* Init binding handlers */ - err = InitBindingHandlers(); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "InitBindingHandlers failed: %s", ErrorStr(err)); - goto exit; - } - -#if CHIP_DEVICE_CONFIG_ENABLE_WPA - NetWorkCommissioningInstInit(); -#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA - -#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR - /* If an update is under test make it permanent */ - OTARequestorInitiator::Instance().HandleSelfTest(); -#endif - - /* Register Matter CLI cmds */ - err = AppMatterCli_RegisterCommands(); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Error during AppMatterCli_RegisterCommands"); - goto exit; - } - /* Register Matter buttons */ - err = AppMatterButton_registerButtons(); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Error during AppMatterButton_registerButtons"); - goto exit; - } - - err = DisplayDeviceInformation(); - if (err != CHIP_NO_ERROR) - goto exit; - - /* Start a task to run the CHIP Device event loop. */ - err = PlatformMgr().StartEventLoopTask(); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Error during PlatformMgr().StartEventLoopTask()"); - goto exit; - } - -#if CHIP_ENABLE_OPENTHREAD - // Start OpenThread task - err = ThreadStackMgrImpl().StartThreadTask(); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Error during ThreadStackMgrImpl().StartThreadTask()"); - goto exit; - } -#endif - -#if TCP_DOWNLOAD - EnableTcpDownloadComponent(); -#endif - -exit: - return err; -} - -void AppTask::AppTaskMain(void * pvParameter) -{ - AppTask * task = (AppTask *) pvParameter; - CHIP_ERROR err; - AppEvent event; - - ChipLogProgress(DeviceLayer, "Welcome to NXP All Clusters Demo App"); - - err = task->Init(); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "AppTask.Init() failed"); - assert(err == CHIP_NO_ERROR); - } - - while (true) - { - BaseType_t eventReceived = xQueueReceive(sAppEventQueue, &event, portMAX_DELAY); - while (eventReceived == pdTRUE) - { - sAppTask.DispatchEvent(&event); - eventReceived = xQueueReceive(sAppEventQueue, &event, 0); - } - } -} - -CHIP_ERROR AppTask::DisplayDeviceInformation(void) -{ - CHIP_ERROR err = CHIP_NO_ERROR; - uint16_t discriminator; - uint32_t setupPasscode; - uint16_t vendorId; - uint16_t productId; - char currentSoftwareVer[ConfigurationManager::kMaxSoftwareVersionStringLength + 1]; - - err = GetCommissionableDataProvider()->GetSetupDiscriminator(discriminator); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Couldn't get discriminator: %s", ErrorStr(err)); - goto exit; - } - ChipLogProgress(DeviceLayer, "Setup discriminator: %u (0x%x)", discriminator, discriminator); - - err = GetCommissionableDataProvider()->GetSetupPasscode(setupPasscode); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Couldn't get setupPasscode: %s", ErrorStr(err)); - goto exit; - } - ChipLogProgress(DeviceLayer, "Setup passcode: %lu (0x%lx)", setupPasscode, setupPasscode); - - err = GetDeviceInstanceInfoProvider()->GetVendorId(vendorId); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Couldn't get vendorId: %s", ErrorStr(err)); - goto exit; - } - ChipLogProgress(DeviceLayer, "Vendor ID: %u (0x%x)", vendorId, vendorId); - - err = GetDeviceInstanceInfoProvider()->GetProductId(productId); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Couldn't get productId: %s", ErrorStr(err)); - goto exit; - } - ChipLogProgress(DeviceLayer, "nProduct ID: %u (0x%x)", productId, productId); - - // QR code will be used with CHIP Tool -#if CONFIG_NETWORK_LAYER_BLE - PrintOnboardingCodes(chip::RendezvousInformationFlag(chip::RendezvousInformationFlag::kBLE)); -#else - PrintOnboardingCodes(chip::RendezvousInformationFlag(chip::RendezvousInformationFlag::kOnNetwork)); -#endif /* CONFIG_NETWORK_LAYER_BLE */ - - err = ConfigurationMgr().GetSoftwareVersionString(currentSoftwareVer, sizeof(currentSoftwareVer)); - if (err != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Get current software version error"); - goto exit; - } - - ChipLogProgress(DeviceLayer, "Current Software Version: %s", currentSoftwareVer); - -exit: - return err; -} - -void AppTask::PostEvent(const AppEvent * aEvent) -{ - if (sAppEventQueue != NULL) - { - if (!xQueueSend(sAppEventQueue, aEvent, 0)) - { - ChipLogError(DeviceLayer, "Failed to post event to app task event queue"); - } - } -} - -void AppTask::DispatchEvent(AppEvent * aEvent) -{ - if (aEvent->Handler) - { - aEvent->Handler(aEvent); - } - else - { - ChipLogProgress(DeviceLayer, "Event received with no handler. Dropping event."); - } -} - -void AppTask::StartCommissioning(intptr_t arg) -{ - /* Check the status of the commissioning */ - if (ConfigurationMgr().IsFullyProvisioned()) - { - ChipLogProgress(DeviceLayer, "Device already commissioned"); - } - else if (chip::Server::GetInstance().GetCommissioningWindowManager().IsCommissioningWindowOpen()) - { - ChipLogProgress(DeviceLayer, "Commissioning window already opened"); - } - else - { - chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow(); - } -} - -void AppTask::StopCommissioning(intptr_t arg) -{ - /* Check the status of the commissioning */ - if (ConfigurationMgr().IsFullyProvisioned()) - { - ChipLogProgress(DeviceLayer, "Device already commissioned"); - } - else if (!chip::Server::GetInstance().GetCommissioningWindowManager().IsCommissioningWindowOpen()) - { - ChipLogProgress(DeviceLayer, "Commissioning window not opened"); - } - else - { - chip::Server::GetInstance().GetCommissioningWindowManager().CloseCommissioningWindow(); - } -} - -void AppTask::SwitchCommissioningState(intptr_t arg) -{ - /* Check the status of the commissioning */ - if (ConfigurationMgr().IsFullyProvisioned()) - { - ChipLogProgress(DeviceLayer, "Device already commissioned"); - } - else if (!chip::Server::GetInstance().GetCommissioningWindowManager().IsCommissioningWindowOpen()) - { - chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow(); - } - else - { - chip::Server::GetInstance().GetCommissioningWindowManager().CloseCommissioningWindow(); - } -} - -void AppTask::StartCommissioningHandler(void) -{ - /* Publish an event to the Matter task to always set the commissioning state in the Matter task context */ - PlatformMgr().ScheduleWork(StartCommissioning, 0); -} - -void AppTask::StopCommissioningHandler(void) -{ - /* Publish an event to the Matter task to always set the commissioning state in the Matter task context */ - PlatformMgr().ScheduleWork(StopCommissioning, 0); } -void AppTask::SwitchCommissioningStateHandler(void) +// This returns an instance of this class. +AllClustersApp::AppTask & AllClustersApp::AppTask::GetDefaultInstance() { - /* Publish an event to the Matter task to always set the commissioning state in the Matter task context */ - PlatformMgr().ScheduleWork(SwitchCommissioningState, 0); + static AllClustersApp::AppTask sAppTask; + return sAppTask; } -void AppTask::FactoryResetHandler(void) +chip::NXP::App::AppTaskBase & chip::NXP::App::GetAppTask() { - /* Emit the ShutDown event before factory reset */ - chip::Server::GetInstance().GenerateShutDownEvent(); - chip::Server::GetInstance().ScheduleFactoryReset(); + return AllClustersApp::AppTask::GetDefaultInstance(); } diff --git a/examples/all-clusters-app/nxp/common/main/DeviceCallbacks.cpp b/examples/all-clusters-app/nxp/common/main/DeviceCallbacks.cpp index 419ad3b3003b2e..527fa79702f6fb 100644 --- a/examples/all-clusters-app/nxp/common/main/DeviceCallbacks.cpp +++ b/examples/all-clusters-app/nxp/common/main/DeviceCallbacks.cpp @@ -24,154 +24,96 @@ **/ #include "DeviceCallbacks.h" +#include +#include #include +#include #include #include #include -#if CHIP_ENABLE_OPENTHREAD && CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED -#include "openthread-system.h" -#include "ot_platform_common.h" -#endif /* CHIP_ENABLE_OPENTHREAD && CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED */ -#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR -#include "OTARequestorInitiator.h" -#endif +using namespace chip::app; +void OnTriggerEffect(::Identify * identify) +{ + switch (identify->mCurrentEffectIdentifier) + { + case Clusters::Identify::EffectIdentifierEnum::kBlink: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kBlink"); + break; + case Clusters::Identify::EffectIdentifierEnum::kBreathe: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kBreathe"); + break; + case Clusters::Identify::EffectIdentifierEnum::kOkay: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kOkay"); + break; + case Clusters::Identify::EffectIdentifierEnum::kChannelChange: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kChannelChange"); + break; + default: + ChipLogProgress(Zcl, "No identifier effect"); + return; + } +} Identify gIdentify0 = { chip::EndpointId{ 1 }, [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStart"); }, [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStop"); }, - chip::app::Clusters::Identify::IdentifyTypeEnum::kVisibleIndicator, - [](Identify *) { ChipLogProgress(Zcl, "onIdentifyTriggerEffect"); }, + chip::app::Clusters::Identify::IdentifyTypeEnum::kNone, + OnTriggerEffect, }; Identify gIdentify1 = { chip::EndpointId{ 1 }, [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStart"); }, [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStop"); }, - chip::app::Clusters::Identify::IdentifyTypeEnum::kVisibleIndicator, - [](Identify *) { ChipLogProgress(Zcl, "onIdentifyTriggerEffect"); }, + chip::app::Clusters::Identify::IdentifyTypeEnum::kNone, + OnTriggerEffect, }; using namespace ::chip; using namespace ::chip::Inet; using namespace ::chip::System; using namespace ::chip::DeviceLayer; +using namespace chip::app::Clusters; -void DeviceCallbacks::DeviceEventCallback(const ChipDeviceEvent * event, intptr_t arg) -{ - ChipLogDetail(DeviceLayer, "DeviceEventCallback: 0x%04x", event->Type); - switch (event->Type) - { - case DeviceEventType::kWiFiConnectivityChange: - OnWiFiConnectivityChange(event); - break; - - case DeviceEventType::kInternetConnectivityChange: - OnInternetConnectivityChange(event); - break; - - case DeviceEventType::kInterfaceIpAddressChanged: - OnInterfaceIpAddressChanged(event); - break; - -#if CHIP_ENABLE_OPENTHREAD && CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED - case DeviceEventType::kCommissioningComplete: - DeviceCallbacks::OnComissioningComplete(event); - break; -#endif - case DeviceLayer::DeviceEventType::kDnssdInitialized: - ChipLogProgress(DeviceLayer, "kDnssdInitialized"); -#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR - /* Initialize OTA Requestor */ - OTARequestorInitiator::Instance().InitOTA(reinterpret_cast(&OTARequestorInitiator::Instance())); -#endif - break; - } -} - -void DeviceCallbacks::PostAttributeChangeCallback(EndpointId endpointId, ClusterId clusterId, AttributeId attributeId, uint8_t type, - uint16_t size, uint8_t * value) +void AllClustersApp::DeviceCallbacks::PostAttributeChangeCallback(EndpointId endpointId, ClusterId clusterId, + AttributeId attributeId, uint8_t type, uint16_t size, + uint8_t * value) { ChipLogProgress(DeviceLayer, "endpointId " ChipLogFormatMEI " clusterId " ChipLogFormatMEI " attribute ID: " ChipLogFormatMEI " Type: %u Value: %u, length %u", ChipLogValueMEI(endpointId), ChipLogValueMEI(clusterId), ChipLogValueMEI(attributeId), type, *value, size); -} - -void DeviceCallbacks::OnWiFiConnectivityChange(const ChipDeviceEvent * event) -{ - if (event->WiFiConnectivityChange.Result == kConnectivity_Established) - { - ChipLogProgress(DeviceLayer, "WiFi connection established"); - } - else if (event->WiFiConnectivityChange.Result == kConnectivity_Lost) + switch (clusterId) { - ChipLogProgress(DeviceLayer, "WiFi connection lost"); + case Clusters::OnOff::Id: + OnOnOffPostAttributeChangeCallback(endpointId, attributeId, value); + break; } } -void DeviceCallbacks::OnInternetConnectivityChange(const ChipDeviceEvent * event) +void AllClustersApp::DeviceCallbacks::OnOnOffPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, + uint8_t * value) { - if (event->InternetConnectivityChange.IPv4 == kConnectivity_Established) - { - char ip_addr[Inet::IPAddress::kMaxStringLength]; - event->InternetConnectivityChange.ipAddress.ToString(ip_addr); - ChipLogProgress(DeviceLayer, "Server ready at: %s:%d", ip_addr, CHIP_PORT); - } - else if (event->InternetConnectivityChange.IPv4 == kConnectivity_Lost) - { - ChipLogProgress(DeviceLayer, "Lost IPv4 connectivity..."); - } - if (event->InternetConnectivityChange.IPv6 == kConnectivity_Established) - { - char ip_addr[Inet::IPAddress::kMaxStringLength]; - event->InternetConnectivityChange.ipAddress.ToString(ip_addr); - ChipLogProgress(DeviceLayer, "IPv6 Server ready at: [%s]:%d", ip_addr, CHIP_PORT); - } - else if (event->InternetConnectivityChange.IPv6 == kConnectivity_Lost) + switch (attributeId) { - ChipLogProgress(DeviceLayer, "Lost IPv6 connectivity..."); + case Clusters::OnOff::Attributes::OnOff::Id: + break; + + default:; } } -void DeviceCallbacks::OnInterfaceIpAddressChanged(const ChipDeviceEvent * event) +// This returns an instance of this class. +AllClustersApp::DeviceCallbacks & AllClustersApp::DeviceCallbacks::GetDefaultInstance() { - switch (event->InterfaceIpAddressChanged.Type) - { - case InterfaceIpChangeType::kIpV4_Assigned: - ChipLogProgress(DeviceLayer, "Interface IPv4 address assigned"); - break; - case InterfaceIpChangeType::kIpV4_Lost: - ChipLogProgress(DeviceLayer, "Interface IPv4 address lost"); - break; - case InterfaceIpChangeType::kIpV6_Assigned: - ChipLogProgress(DeviceLayer, "Interface IPv6 address assigned"); - break; - case InterfaceIpChangeType::kIpV6_Lost: - ChipLogProgress(DeviceLayer, "Interface IPv6 address lost"); - break; - } + static AllClustersApp::DeviceCallbacks sDeviceCallbacks; + return sDeviceCallbacks; } -#if CHIP_ENABLE_OPENTHREAD && CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED -void DeviceCallbacks::OnComissioningComplete(const chip::DeviceLayer::ChipDeviceEvent * event) +chip::DeviceManager::CHIPDeviceManagerCallbacks & chip::NXP::App::GetDeviceCallbacks() { - /* - * If a transceiver supporting a multiprotocol scenario is used, a check of the provisioning state is required, - * so that we can inform the transceiver to stop BLE to give the priority to another protocol. - * For example it is the case when a K32W0 transceiver supporting OT+BLE+Zigbee is used. When the device is already provisioned, - * BLE is no more required and the transceiver needs to be informed so that Zigbee can be switched on and BLE switched off. - * - * If a transceiver does not support such vendor property the cmd would be ignored. - */ - if (ConfigurationMgr().IsFullyProvisioned()) - { - ChipLogDetail(DeviceLayer, "Provisioning complete, stopping BLE\n"); - ThreadStackMgrImpl().LockThreadStack(); - PlatformMgrImpl().StopBLEConnectivity(); - ThreadStackMgrImpl().UnlockThreadStack(); - } + return AllClustersApp::DeviceCallbacks::GetDefaultInstance(); } -#endif diff --git a/examples/all-clusters-app/nxp/common/main/include/AppEvent.h b/examples/all-clusters-app/nxp/common/main/include/AppEvent.h index 8047da99982e23..a0dad141a27055 100644 --- a/examples/all-clusters-app/nxp/common/main/include/AppEvent.h +++ b/examples/all-clusters-app/nxp/common/main/include/AppEvent.h @@ -19,7 +19,7 @@ #pragma once struct AppEvent; -typedef void (*EventHandler)(AppEvent *); +using EventHandler = void (*)(const AppEvent &); struct AppEvent { diff --git a/examples/all-clusters-app/nxp/common/main/include/AppTask.h b/examples/all-clusters-app/nxp/common/main/include/AppTask.h index e276e287aa92a6..9bda003e25344a 100644 --- a/examples/all-clusters-app/nxp/common/main/include/AppTask.h +++ b/examples/all-clusters-app/nxp/common/main/include/AppTask.h @@ -2,6 +2,7 @@ * * Copyright (c) 2020 Project CHIP Authors * Copyright (c) 2021-2023 Google LLC. + * Copyright 2024 NXP * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,47 +20,31 @@ #pragma once -#include -#include - -#include "AppEvent.h" -#include "DeviceCallbacks.h" - -class AppTask +#if CONFIG_APP_FREERTOS_OS +#include "AppTaskFreeRTOS.h" +#else +#include "AppTaskZephyr.h" +#endif +namespace AllClustersApp { +#if CONFIG_APP_FREERTOS_OS +class AppTask : public chip::NXP::App::AppTaskFreeRTOS +#else +class AppTask : public chip::NXP::App::AppTaskZephyr +#endif { public: - CHIP_ERROR StartAppTask(); - static void AppTaskMain(void * pvParameter); - - void PostEvent(const AppEvent * event); - - /* Commissioning handlers */ - void StartCommissioningHandler(void); - void StopCommissioningHandler(void); - void SwitchCommissioningStateHandler(void); - - /* FactoryResetHandler */ - void FactoryResetHandler(void); - -private: - DeviceCallbacks deviceCallbacks; - - friend AppTask & GetAppTask(void); - - CHIP_ERROR Init(); - void DispatchEvent(AppEvent * event); - CHIP_ERROR DisplayDeviceInformation(void); - - /* Functions that would be called in the Matter task context */ - static void StartCommissioning(intptr_t arg); - static void StopCommissioning(intptr_t arg); - static void SwitchCommissioningState(intptr_t arg); - static void InitServer(intptr_t arg); - - static AppTask sAppTask; + ~AppTask() override{}; + void PreInitMatterStack(void) override; + void PostInitMatterStack(void) override; + void PostInitMatterServerInstance(void) override; + // This returns an instance of this class. + static AppTask & GetDefaultInstance(); }; - -inline AppTask & GetAppTask(void) -{ - return AppTask::sAppTask; -} +} // namespace AllClustersApp +/** + * Returns the application-specific implementation of the AppTaskBase object. + * + * Applications can use this to gain access to features of the AppTaskBase + * that are specific to the selected application. + */ +chip::NXP::App::AppTaskBase & GetAppTask(); diff --git a/examples/all-clusters-app/nxp/common/main/include/DeviceCallbacks.h b/examples/all-clusters-app/nxp/common/main/include/DeviceCallbacks.h index 2a37a2dfb99a94..748905b52ad736 100644 --- a/examples/all-clusters-app/nxp/common/main/include/DeviceCallbacks.h +++ b/examples/all-clusters-app/nxp/common/main/include/DeviceCallbacks.h @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020-2023 Project CHIP Authors * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,24 +26,27 @@ #pragma once #include "CHIPDeviceManager.h" -#include -#include +#include "CommonDeviceCallbacks.h" -class DeviceCallbacks : public chip::DeviceManager::CHIPDeviceManagerCallbacks +namespace AllClustersApp { +class DeviceCallbacks : public chip::NXP::App::CommonDeviceCallbacks { public: - virtual void DeviceEventCallback(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); - virtual void PostAttributeChangeCallback(chip::EndpointId endpointId, chip::ClusterId clusterId, chip::AttributeId attributeId, - uint8_t type, uint16_t size, uint8_t * value); + // This returns an instance of this class. + static DeviceCallbacks & GetDefaultInstance(); + void PostAttributeChangeCallback(chip::EndpointId endpointId, chip::ClusterId clusterId, chip::AttributeId attributeId, + uint8_t type, uint16_t size, uint8_t * value); private: - void OnWiFiConnectivityChange(const chip::DeviceLayer::ChipDeviceEvent * event); - void OnInternetConnectivityChange(const chip::DeviceLayer::ChipDeviceEvent * event); - void OnSessionEstablished(const chip::DeviceLayer::ChipDeviceEvent * event); - void OnInterfaceIpAddressChanged(const chip::DeviceLayer::ChipDeviceEvent * event); void OnOnOffPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, uint8_t * value); void OnIdentifyPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, uint8_t * value); -#if CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED - void OnComissioningComplete(const chip::DeviceLayer::ChipDeviceEvent * event); -#endif }; + +/** + * Returns the application-specific implementation of the CommonDeviceCallbacks object. + * + * Applications can use this to gain access to features of the CommonDeviceCallbacks + * that are specific to the selected application. + */ +chip::DeviceManager::CHIPDeviceManagerCallbacks & GetDeviceCallbacks(); +} // namespace AllClustersApp diff --git a/examples/all-clusters-app/nxp/common/main/main.cpp b/examples/all-clusters-app/nxp/common/main/main.cpp index c2305cf9ac4eb6..b2aadab98ceb8b 100644 --- a/examples/all-clusters-app/nxp/common/main/main.cpp +++ b/examples/all-clusters-app/nxp/common/main/main.cpp @@ -37,7 +37,7 @@ extern "C" int main(int argc, char * argv[]) TaskHandle_t taskHandle; PlatformMgrImpl().HardwareInit(); - GetAppTask().StartAppTask(); + chip::NXP::App::GetAppTask().Start(); vTaskStartScheduler(); } diff --git a/examples/all-clusters-app/nxp/rt/rw61x/BUILD.gn b/examples/all-clusters-app/nxp/rt/rw61x/BUILD.gn index 6f167c92ce5be2..02cce50f266eb1 100644 --- a/examples/all-clusters-app/nxp/rt/rw61x/BUILD.gn +++ b/examples/all-clusters-app/nxp/rt/rw61x/BUILD.gn @@ -35,24 +35,42 @@ assert(target_os == "freertos") assert(nxp_platform == "rt/rw61x") declare_args() { - # This defines the device type as a "thermostat" by default, - # use "all-clusters" in order to build the all-clusters-app - nxp_device_type = "thermostat" + # Allows to start the tcp download test app + tcp_download = false + + # Allows to start the wifi connect test app + wifi_connect = false + + # The 2 params below are used only if tcp_download or wifi_connect are true, otherwise they're unused. + wifi_ssid = "" + wifi_password = "" + + # Setup discriminator as argument + setup_discriminator = 3840 } example_platform_dir = "${chip_root}/examples/platform/nxp/${nxp_platform}" +common_example_dir = "${chip_root}/examples/platform/nxp/common" -if (nxp_device_type == "thermostat") { - app_common_folder = "${nxp_device_type}/nxp/zap" -} else { - app_common_folder = "${nxp_device_type}-app/${nxp_device_type}-common" +if (tcp_download == true && wifi_connect == true) { + assert("Cannot enable tcp_download and wifi_connect at the same time!") } +app_common_folder = "all-clusters-app/all-clusters-common" + # Create here the SDK instance. # Particular sources/defines/includes could be added/changed depending on the target application. rt_sdk("sdk") { defines = [] + # To be moved, temporary mbedtls config fix to build app with factory data + if (chip_enable_secure_dac_private_key_storage == 1) { + defines += [ + "MBEDTLS_NIST_KW_C", + "MBEDTLS_PSA_CRYPTO_CLIENT", + ] + } + cflags = [] public_deps = [] public_configs = [] @@ -75,6 +93,15 @@ rt_sdk("sdk") { # Indicate the default path to OpenThreadConfig.h include_dirs += [ "${example_platform_dir}/app/project_include/openthread" ] + + # For matter with BR feature, increase FreeRTOS heap size + if (chip_enable_wifi && chip_enable_openthread) { + defines += [ "configTOTAL_HEAP_SIZE=(size_t)(160 * 1024)" ] + } + + defines += [ + "CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR=${setup_discriminator}", + ] } # Create the SDK driver instance. @@ -85,12 +112,18 @@ rw61x_sdk_drivers("sdk_driver") { rt_executable("all_cluster_app") { output_name = "chip-rw61x-all-cluster-example" - defines = [ "CONFIG_RENDEZVOUS_MODE=7" ] + defines = [ + "CONFIG_RENDEZVOUS_MODE=7", + "CONFIG_APP_FREERTOS_OS=1", + ] + + if (chip_enable_openthread) { + defines += [ "CONFIG_NET_L2_OPENTHREAD=1" ] + } include_dirs = [ "../../common/main/include", "../../common/main", - "${chip_root}/examples/all-clusters-app/all-clusters-common/include", "${chip_root}/examples/providers/", ] @@ -98,34 +131,54 @@ rt_executable("all_cluster_app") { sources = [ "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp", "${chip_root}/examples/providers/DeviceInfoProviderImpl.cpp", - "../../common/main/AppFactoryDataDefaultImpl.cpp", - "../../common/main/AppMatterButtonEmpty.cpp", - "../../common/main/AppMatterCli.cpp", "../../common/main/AppTask.cpp", - "../../common/main/CHIPDeviceManager.cpp", "../../common/main/DeviceCallbacks.cpp", "../../common/main/ZclCallbacks.cpp", "../../common/main/include/AppEvent.h", "../../common/main/include/AppTask.h", - "../../common/main/include/CHIPDeviceManager.h", "../../common/main/include/DeviceCallbacks.h", "../../common/main/main.cpp", ] - deps = [ "${chip_root}/examples/${app_common_folder}" ] - - if (nxp_device_type == "all-clusters") { - defines += [ "DEVICE_TYPE_ALL_CLUSTERS" ] + if (chip_enable_secure_dac_private_key_storage == 1) { + sources += [ + "${example_platform_dir}/factory_data/source/AppFactoryDataExample.cpp", + ] + } else { sources += [ - "${chip_root}/examples/${app_common_folder}/src/EnergyEvseDelegateImpl.cpp", - "${chip_root}/examples/${app_common_folder}/src/EnergyEvseManager.cpp", - "${chip_root}/examples/${app_common_folder}/src/bridged-actions-stub.cpp", - "${chip_root}/examples/${app_common_folder}/src/energy-evse-stub.cpp", - "${chip_root}/examples/${app_common_folder}/src/smco-stub.cpp", - "${chip_root}/examples/${app_common_folder}/src/static-supported-modes-manager.cpp", + "${common_example_dir}/factory_data/source/AppFactoryDataDefaultImpl.cpp", ] } + # App common files + include_dirs += [ + "${common_example_dir}/icd/include", + "${common_example_dir}/matter_button/include", + "${common_example_dir}/matter_cli/include", + "${common_example_dir}/device_manager/include", + "${common_example_dir}/device_callbacks/include", + "${common_example_dir}/app_task/include", + "${common_example_dir}/factory_data/include", + ] + + sources += [ + "${common_example_dir}/app_task/source/AppTaskBase.cpp", + "${common_example_dir}/app_task/source/AppTaskFreeRTOS.cpp", + "${common_example_dir}/device_callbacks/source/CommonDeviceCallbacks.cpp", + "${common_example_dir}/device_manager/source/CHIPDeviceManager.cpp", + "${common_example_dir}/icd/source/ICDUtil.cpp", + "${common_example_dir}/matter_button/source/AppMatterButtonEmpty.cpp", + "${common_example_dir}/matter_cli/source/AppMatterCli.cpp", + ] + + deps = [ "${chip_root}/examples/${app_common_folder}" ] + + sources += [ + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/smco-stub.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", + ] + if (chip_enable_matter_cli) { defines += [ "ENABLE_CHIP_SHELL" ] deps += [ @@ -135,17 +188,53 @@ rt_executable("all_cluster_app") { } if (chip_enable_ota_requestor) { + include_dirs += [ "${common_example_dir}/ota_requestor/include" ] sources += [ - "${chip_root}/examples/platform/nxp/common/OTARequestorInitiator.cpp", - "${chip_root}/examples/platform/nxp/common/OTARequestorInitiator.h", + "${common_example_dir}/ota_requestor/source/OTARequestorInitiator.cpp", + "${common_example_dir}/ota_requestor/source/OTARequestorInitiatorCommon.cpp", + ] + } + + if (wifi_connect) { + defines += [ + "WIFI_CONNECT_TASK=1", + "WIFI_CONNECT=1", + ] + + if (!chip_enable_matter_cli) { + assert(wifi_ssid != "" && wifi_password != "", + "WiFi SSID and password must be specified at build time!") + } + + if (wifi_ssid != "") { + defines += [ "WIFI_SSID=\"${wifi_ssid}\"" ] + } + + if (wifi_password != "") { + defines += [ "WIFI_PASSWORD=\"${wifi_password}\"" ] + } + + include_dirs += [ "${common_example_dir}/wifi_connect/include" ] + sources += [ "${common_example_dir}/wifi_connect/source/WifiConnect.cpp" ] + } + + if (tcp_download) { + defines += [ "CONFIG_CHIP_TCP_DOWNLOAD=1" ] + defines += [ + "WIFI_CONNECT=1", + "WIFI_SSID=\"${wifi_ssid}\"", + "WIFI_PASSWORD=\"${wifi_password}\"", ] - include_dirs += [ "${chip_root}/examples/platform/nxp/common" ] + + include_dirs += [ "${common_example_dir}/tcp_download_test/include" ] + sources += + [ "${common_example_dir}/tcp_download_test/source/TcpDownload.cpp" ] } # In case a dedicated assert function needs to be supported the flag sdk_fsl_assert_support should be set to false # The would add to the build a dedicated application assert implementation. if (!sdk_fsl_assert_support) { - sources += [ "../../common/main/AppAssert.cpp" ] + sources += [ "${common_example_dir}/app_assert/source/AppAssert.cpp" ] } cflags = [ "-Wconversion" ] diff --git a/examples/all-clusters-app/nxp/rt/rw61x/README.md b/examples/all-clusters-app/nxp/rt/rw61x/README.md index d5b436d9fee8e1..8a6889909dffa3 100644 --- a/examples/all-clusters-app/nxp/rt/rw61x/README.md +++ b/examples/all-clusters-app/nxp/rt/rw61x/README.md @@ -25,13 +25,13 @@ commissioning and different cluster control. The RW61x all-cluster application provides a working demonstration of the RW610/RW612 board integration, built using the Project CHIP codebase and the NXP -RW612 SDK. The example supports basic ZCL commands and acts as a thermostat -device-type. +RW612 SDK. The example supports: - Matter over Wi-Fi - Matter over Openthread +- Matter over Wi-Fi with OpenThread Border Router support. ### Hardware requirements @@ -46,6 +46,12 @@ For Matter over WiFi configuration : - BLE antenna (to plug in Ant1) - Wi-Fi antenna (to plug in Ant2) +For Matter over Wi-Fi with OpenThread Border Router : + +- [`NXP RD-RW612-BGA`] board +- BLE/15.4 antenna (to plug in Ant1) +- Wi-Fi antenna (to plug in Ant2) + ## Building @@ -56,22 +62,26 @@ distribution (the demo-application was compiled on Ubuntu 20.04). - Follow instruction in [BUILDING.md](../../../../../docs/guides/BUILDING.md) to setup the environment to be able to build Matter. -- Download [RD-RW612 SDK v2.13.1](https://mcuxpresso.nxp.com/en/select). +- Download + [RD-RW612 SDK for Project CHIP](https://mcuxpresso.nxp.com/en/select). Creating an nxp.com account is required before being able to download the SDK. Once the account is created, login and follow the steps for downloading SDK. The SDK Builder UI selection should be similar with the one from the - image below. In case you do not have access to the SDK, please ask your NXP - representative. + image below. ![MCUXpresso SDK Download](../../../../platform/nxp/rt/rw61x/doc/images/mcux-sdk-download.PNG) (Note: All SDK components should be selected. If size is an issue Azure RTOS component can be omitted.) + Please refer to Matter release notes for getting the latest released SDK. + - Start building the application. ``` user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_SDK_ROOT=/home/user/Desktop/SDK_RW612/ +user@ubuntu:~/Desktop/git/connectedhomeip$ scripts/checkout_submodules.py --shallow --platform nxp --recursive +user@ubuntu:~/Desktop/git/connectedhomeip$ source ./scripts/bootstrap.sh user@ubuntu:~/Desktop/git/connectedhomeip$ source ./scripts/activate.sh user@ubuntu:~/Desktop/git/connectedhomeip$ cd examples/all-clusters-app/nxp/rt/rw61x/ ``` @@ -87,13 +97,26 @@ user@ubuntu:~/Desktop/git/connectedhomeip/examples/all-clusters-app/nxp/rt/rw61x #### Building with Matter over Thread configuration on RW612 -- Build the Openthread configuration with BLE commissioning. +- Build Matter-over-Thread configuration with BLE commissioning. ``` user@ubuntu:~/Desktop/git/connectedhomeip/examples/all-clusters-app/nxp/rt/rw61x$ gn gen --args="chip_enable_openthread=true chip_inet_config_enable_ipv4=false chip_config_network_layer_ble=true is_sdk_package=true" out/debug user@ubuntu:~/Desktop/git/connectedhomeip/examples/all-clusters-app/nxp/rt/rw61x$ ninja -C out/debug ``` +#### Building with Matter over Wifi + OpenThread Border Router configuration on RW612 + +This configuration requires enabling the Matter CLI in order to control the +Thread network on the Border Router. + +- Build Matter with Border Router configuration with BLE commissioning + (ble-wifi) : + +``` +user@ubuntu:~/Desktop/git/connectedhomeip/examples/all-clusters-app/nxp/rt/rw610$ gn gen --args="chip_enable_wifi=true chip_enable_openthread=true chip_enable_matter_cli=true is_sdk_package=true openthread_root=\"//third_party/connectedhomeip/third_party/openthread/ot-nxp/openthread-br\"" out/debug +user@ubuntu:~/Desktop/git/connectedhomeip/examples/all-clusters-app/nxp/rt/rw610$ ninja -C out/debug +``` + #### General information The resulting output file can be found in @@ -108,8 +131,8 @@ Optional GN options that can be added when building an application: - To switch the SDK type used, the argument `is_=true` must be added to the _gn gen_ command (with being either sdk_package or sdk_internal). -- By default, the RW612 A1 board revision will be chosen. To switch to an A0 - revision, the argument `board_version=\"A0\"` must be added to the _gn gen_ +- By default, the RW612 A1 board revision will be chosen. To switch to an A2 + revision, the argument `board_version=\"A2\"` must be added to the _gn gen_ command. - To build the application in debug mode, the argument `is_debug=true optimize_debug=false` must be added to the _gn gen_ command. @@ -120,7 +143,7 @@ Optional GN options that can be added when building an application: - To build the application with the OTA Requestor enabled, the arguments `chip_enable_ota_requestor=true no_mcuboot=false` must be added to the _gn gen_ command. (More information about the OTA Requestor feature in - [OTA Requestor README](README_OTA_Requestor.md))) + [OTA Requestor README](../../../../../docs/guides/nxp_rw61x_ota_software_update.md) ## Manufacturing data @@ -133,6 +156,21 @@ The all cluster app demonstrates the usage of encrypted Matter manufacturing data storage. Matter manufacturing data should be encrypted using an AES 128 software key before flashing them to the device flash. +Using DAC private key secure usage: Experimental feature, contain some +limitation: potential concurrent access issue during sign with dac key operation +due to the lack of protection between multiple access to `ELS` crypto module. +The argument `chip_enable_secure_dac_private_key_storage=1` must be added to the +_gn gen_ command to enable secure private DAC key usage with S50. +`chip_with_factory_data=1` must have been added to the _gn gen_ command + +DAC private key generation: The argument `chip_convert_dac_private_key=1` must +be added to the _gn gen_ command to enable DAC private plain key conversion to +blob with S50. `chip_enable_secure_dac_private_key_storage=1` must have been +added to the _gn gen_ command + +`ELS` contain concurrent access risks. They must be fixed before enabling it by +default. + ## Flashing and debugging @@ -213,6 +251,16 @@ The "ble-wifi" pairing method can be used in order to commission the device. The "ble-thread" pairing method can be used in order to commission the device. +#### Matter over wifi with openthread border router configuration : + +In order to create or join a Thread network on the Matter Border Router, the +`otcli` commands from the matter CLI can be used. For more information about +using the matter shell, follow instructions from +['Testing the all-clusters application with Matter CLI'](#testing-the-all-clusters-application-with-matter-cli-enabled). + +In this configuration, the device can be commissioned over Wi-Fi with the +'ble-wifi' pairing method. + ### Testing the all-clusters application without Matter CLI: 1. Prepare the board with the flashed `All-cluster application` (as shown @@ -284,6 +332,41 @@ Here are described steps to use the all-cluster-app with the Matter CLI enabled it is described [here](../../../../chip-tool/README.md#using-the-client-to-send-matter-commands). +For Matter with OpenThread Border Router support, the matter CLI can be used to +start/join the Thread network, using the following ot-cli commands. (Note that +setting channel, panid, and network key is not enough anymore because of an Open +Thread stack update. We first need to initialize a new dataset.) + +``` +> otcli dataset init new +Done +> otcli dataset +Active Timestamp: 1 +Channel: 25 +Channel Mask: 0x07fff800 +Ext PAN ID: 42af793f623aab54 +Mesh Local Prefix: fd6e:c358:7078:5a8d::/64 +Network Key: f824658f79d8ca033fbb85ecc3ca91cc +Network Name: OpenThread-b870 +PAN ID: 0xb870 +PSKc: f438a194a5e968cc43cc4b3a6f560ca4 +Security Policy: 672 onrc 0 +Done +> otcli dataset panid 0xabcd +Done +> otcli dataset channel 25 +Done +> otcli dataset commit active +Done +> otcli ifconfig up +Done +> otcli thread start +Done +> otcli state +leader +Done +``` + ## OTA Software Update diff --git a/examples/all-clusters-app/nxp/rt/rw61x/include/config/CHIPProjectConfig.h b/examples/all-clusters-app/nxp/rt/rw61x/include/config/CHIPProjectConfig.h index 9fe84849fd753f..2653e97705fe39 100644 --- a/examples/all-clusters-app/nxp/rt/rw61x/include/config/CHIPProjectConfig.h +++ b/examples/all-clusters-app/nxp/rt/rw61x/include/config/CHIPProjectConfig.h @@ -50,8 +50,13 @@ #if !CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA // Use a default pairing code if one hasn't been provisioned in flash. +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#endif + +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 +#endif // Use a default pairing code if one hasn't been provisioned in flash. #define CHIP_DEVICE_CONFIG_USE_TEST_PAIRING_CODE "CHIPUS" diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/.gn b/examples/contact-sensor-app/nxp/k32w/k32w0/.gn index 3d48789e30ab3d..363727423ce903 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/.gn +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/.gn @@ -25,4 +25,7 @@ default_args = { target_os = "freertos" import("//args.gni") + + # Import default platform configs + import("${chip_root}/src/platform/nxp/k32w/k32w0/args.gni") } diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn b/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn index c9b50a84ceb770..4b9f501690d732 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/BUILD.gn @@ -13,11 +13,13 @@ # limitations under the License. import("//build_overrides/chip.gni") -import("//build_overrides/k32w0_sdk.gni") +import("//build_overrides/nxp_sdk.gni") import("//build_overrides/openthread.gni") -import("${k32w0_sdk_build_root}/k32w0_executable.gni") -import("${k32w0_sdk_build_root}/k32w0_sdk.gni") +import("${nxp_sdk_build_root}/nxp_sdk.gni") + +import("${nxp_sdk_build_root}/${nxp_sdk_name}/${nxp_sdk_name}.gni") +import("${nxp_sdk_build_root}/${nxp_sdk_name}/nxp_executable.gni") import("${chip_root}/src/app/icd/icd.gni") import("${chip_root}/src/crypto/crypto.gni") @@ -27,6 +29,9 @@ import("${chip_root}/src/platform/device.gni") declare_args() { chip_software_version = 0 chip_simple_hash_verification = 0 + + # Setup discriminator as argument + setup_discriminator = 3840 } if (chip_pw_tokenizer_logging) { @@ -71,6 +76,10 @@ k32w0_sdk("sdk") { "CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION=${chip_software_version}", ] } + + defines += [ + "CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR=${setup_discriminator}", + ] } k32w0_executable("contact_sensor_app") { @@ -140,6 +149,7 @@ k32w0_executable("contact_sensor_app") { ldflags = [ "-T" + rebase_path(ldscript, root_build_dir), + "-Wl,--defsym=__stack_size__=0x480", "-Wl,-print-memory-usage", ] diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/README.md b/examples/contact-sensor-app/nxp/k32w/k32w0/README.md index c325895886a093..b7ec329d7650d1 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/README.md +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/README.md @@ -40,9 +40,7 @@ network. - [OTA Testing](#ota-testing) - [Known issues ota](#known-issues-ota) - [Low power](#low-power) - - - [Known issues power](#known-issues-low-power) - + - [Known issues low power](#known-issues-low-power) - [Removing SSBL Upgrade region](#removing-ssbl-upgrade-region) @@ -222,7 +220,7 @@ Start building the application: ```bash user@ubuntu:~/Desktop/git/connectedhomeip$ cd examples/contact-sensor-app/nxp/k32w/k32w0 -user@ubuntu:~/Desktop/git/connectedhomeip/examples/contact-sensor-app/nxp/k32w/k32w0$ gn gen out/debug --args="chip_with_OM15082=1 chip_with_ot_cli=0 is_debug=false chip_crypto=\"platform\" chip_with_se05x=0 chip_pw_tokenizer_logging=true" +user@ubuntu:~/Desktop/git/connectedhomeip/examples/contact-sensor-app/nxp/k32w/k32w0$ gn gen out/debug user@ubuntu:~/Desktop/git/connectedhomeip/examples/contact-sensor-app/nxp/k32w/k32w0$ ninja -C out/debug ``` @@ -248,6 +246,16 @@ In case the board doesn't have 32KHz crystal fitted, one can use the 32KHz free running oscillator as a clock source. In this case one must set the use_fro_32k argument to 1. +K32W0x1 supports antenna diversity feature, which is a technique that maximizes +the performance of an antenna system, allowing the radio signal to be switched +between two antennas that have very low correlation between their received +signals. Typically, this is achieved by spacing two antennas around 0.25 +wavelengths apart or by using 2 orthogonal types of polarization. This is +controlled by software. K32W0x1 provides an output (`ADO`) on one of `DIO7`, +`DIO9` or `DIO19` and optionally its complement (`ADE`) on `DIO6` that can be +used to control an antenna switch. In order to use this feature, user must set +`use_antenna_diversity` to 1. + In case signing errors are encountered when running the "sign_images.sh" script (run automatically) install the recommanded packages (python version > 3, pip3, pycrypto, pycryptodome): diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/args.gni b/examples/contact-sensor-app/nxp/k32w/k32w0/args.gni index 6e931d00452fcd..e328a6ede9011f 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/args.gni +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/args.gni @@ -14,12 +14,14 @@ import("//build_overrides/chip.gni") import("${chip_root}/config/standalone/args.gni") -import("${chip_root}/examples/platform/nxp/k32w/k32w0/args.gni") # SDK target. This is overridden to add our SDK app_config.h & defines. k32w0_sdk_target = get_label_info(":sdk", "label_no_toolchain") chip_enable_ota_requestor = true +chip_with_ota_encryption = 1 +chip_with_ota_key = "1234567890ABCDEFA1B2C3D4E5F6F1B4" + chip_stack_lock_tracking = "fatal" chip_enable_ble = true @@ -28,3 +30,11 @@ chip_enable_icd_lit = false icd_enforce_sit_slow_poll_limit = true chip_persist_subscriptions = true chip_subscription_timeout_resumption = true + +is_debug = false + +chip_crypto = "platform" +chip_crypto_flavor = "NXP-Ultrafast-P256" +chip_with_ot_cli = 0 +chip_with_OM15082 = 1 +chip_pw_tokenizer_logging = true diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h b/examples/contact-sensor-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h index 72f94878cf328f..301d53824a7e55 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h @@ -92,8 +92,13 @@ #else // Use a default setup PIN code if one hasn't been provisioned in flash. +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#endif + +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 +#endif /** * CHIP_DEVICE_CONFIG_TEST_SERIAL_NUMBER diff --git a/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp b/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp index 17aea9d5897907..81694523876d7e 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp +++ b/examples/contact-sensor-app/nxp/k32w/k32w0/main/AppTask.cpp @@ -45,7 +45,7 @@ #include #endif -#include "BLEManagerImpl.h" +#include #include "Keyboard.h" #include "LED.h" @@ -452,6 +452,7 @@ void AppTask::ButtonEventHandler(uint8_t pin_no, uint8_t button_action) button_event.Handler = ResetActionEventHandler; } #endif + #if CHIP_ENABLE_LIT if (button_action == USER_ACTIVE_MODE_TRIGGER_PUSH) { diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/BUILD.gn b/examples/contact-sensor-app/nxp/k32w/k32w1/BUILD.gn index e44c1a9c71bd40..400b4003c3d1b9 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w1/BUILD.gn +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/BUILD.gn @@ -31,10 +31,15 @@ import("${chip_root}/src/platform/nxp/${nxp_platform}/args.gni") declare_args() { chip_software_version = 0 + + # Setup discriminator as argument + setup_discriminator = 3840 + chip_with_diag_logs_demo = true } assert(current_os == "freertos") +common_example_dir = "${chip_root}/examples/platform/nxp/common" k32w1_platform_dir = "${chip_root}/examples/platform/nxp/k32w/k32w1" k32w1_sdk_root = getenv("NXP_K32W1_SDK_ROOT") @@ -70,6 +75,14 @@ k32w1_sdk("sdk") { "CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION=${chip_software_version}", ] } + + defines += [ + "CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR=${setup_discriminator}", + ] + + if (chip_with_diag_logs_demo) { + defines += [ "CONFIG_DIAG_LOGS_DEMO=1" ] + } } k32w1_executable("contact_sensor_app") { @@ -89,6 +102,8 @@ k32w1_executable("contact_sensor_app") { "main/main.cpp", ] + include_dirs = [ "${common_example_dir}/diagnostic_logs" ] + deps = [ ":sdk", "${chip_root}/examples/common/QRCode", @@ -99,15 +114,24 @@ k32w1_executable("contact_sensor_app") { "${k32w1_platform_dir}/app/support:freertos_mbedtls_utils", ] + #lit and sit are using different zap files + if (chip_enable_icd_lit) { + deps += [ "${chip_root}/examples/contact-sensor-app/nxp/zap-lit/" ] + + defines += [ "CHIP_ENABLE_LIT=1" ] + } else { + deps += [ "${chip_root}/examples/contact-sensor-app/nxp/zap-sit/" ] + } + if (chip_openthread_ftd) { deps += [ - "${chip_root}/third_party/openthread/repo:libopenthread-cli-ftd", - "${chip_root}/third_party/openthread/repo:libopenthread-ftd", + "${openthread_root}:libopenthread-cli-ftd", + "${openthread_root}:libopenthread-ftd", ] } else { deps += [ - "${chip_root}/third_party/openthread/repo:libopenthread-cli-mtd", - "${chip_root}/third_party/openthread/repo:libopenthread-mtd", + "${openthread_root}:libopenthread-cli-mtd", + "${openthread_root}:libopenthread-mtd", ] } @@ -142,6 +166,13 @@ k32w1_executable("contact_sensor_app") { ldflags += [ "-Wl,--defsym=gUseFactoryData_d=1" ] } + if (chip_with_diag_logs_demo) { + sources += [ + "${common_example_dir}/diagnostic_logs/DiagnosticLogsProviderDelegateImpl.cpp", + "${common_example_dir}/diagnostic_logs/DiagnosticLogsProviderDelegateImpl.h", + ] + } + output_dir = root_out_dir } diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/README.md b/examples/contact-sensor-app/nxp/k32w/k32w1/README.md index ff3496d7b862ac..5079e01c6e2400 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w1/README.md +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/README.md @@ -32,7 +32,7 @@ into an existing Matter network and can be controlled by this network. - [Known issues](#known-issues) - [Low power](#low-power) - + ## Introduction @@ -93,7 +93,7 @@ states are depicted: service connectivity. NOTE: LED2 will be disabled when CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR is -enabled. On K32W1 EVK board, `PTB0` is wired to LED2 also is wired to CS (Chip +enabled. On K32W1 EVK board, `PTB0` is wired to `LED2` also is wired to CS (Chip Select) External Flash Memory. OTA image is stored in external memory because of it's size. If LED2 is enabled then it will affect External Memory CS and OTA will not work. @@ -131,8 +131,9 @@ In order to build the Matter example, we recommend using a Linux distribution ``` user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_K32W1_SDK_ROOT=/home/user/Desktop/SDK_K32W1/ user@ubuntu:~/Desktop/git/connectedhomeip$ source ./scripts/activate.sh +user@ubuntu:~/Desktop/git/connectedhomeip$ scripts/checkout_submodules.py --shallow --platform nxp --recursive user@ubuntu:~/Desktop/git/connectedhomeip$ cd examples/contact-sensor-app/nxp/k32w/k32w1 -user@ubuntu:~/Desktop/git/connectedhomeip/examples/contact-sensor-app/nxp/k32w/k32w1$ gn gen out/debug --args="chip_with_ot_cli=0 is_debug=false chip_openthread_ftd=false chip_crypto=\"platform\"" +user@ubuntu:~/Desktop/git/connectedhomeip/examples/contact-sensor-app/nxp/k32w/k32w1$ gn gen out/debug user@ubuntu:~/Desktop/git/connectedhomeip/examples/contact-sensor-app/nxp/k32w/k32w1$ ninja -C out/debug ``` @@ -140,8 +141,6 @@ In case that Openthread CLI is needed, chip_with_ot_cli build argument must be set to 1. After a successful build, the `elf` and `srec` files are found in `out/debug/` - -`see the files prefixed with chip-k32w1-contact-example`. After a successful -build, the `elf` and `srec` files are found in `out/debug/` - `see the files prefixed with chip-k32w1-contact-example`. ## Long Idle Time ICD Support @@ -205,7 +204,7 @@ path - [K32W148 board quick start guide](https://www.nxp.com/document/guide/getting-started-with-the-k32w148-development-platform:GS-K32W148EVK) can be used for updating the `NBU/radio` core: -- Section 2.4 – Get Software – install `SPSDK` (Secure Provisioning Command +- Section 2.5 – Get Software – install `SPSDK` (Secure Provisioning Command Line Tool) - Section 3.3 – Updating `NBU` for Wireless examples - use the corresponding `.sb3` file found in the SDK package at path @@ -308,7 +307,7 @@ In `OTAP` application the image only for the CM33 core - keep other settings at default values -### Convert sb3 into ota file +### Convert `sb3` into `ota` file In order to build an OTA image, use NXP wrapper over the standard tool `src/app/ota_image_tool.py`: @@ -325,7 +324,7 @@ Please see more in the [OTA image tool guide](../../../../../scripts/tools/nxp/ota/README.md). Here is an example that generates an OTA image with application update TLV from -a sb3 file: +a `sb3` file: ``` ./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 43033 -vs "1.0" -da sha256 --app-input-file ~/binaries/chip-k32w1-43033.sb3 ~/binaries/chip-k32w1-43033.ota diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/args.gni b/examples/contact-sensor-app/nxp/k32w/k32w1/args.gni index b25f000cd85d1c..23e6730b79045c 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w1/args.gni +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/args.gni @@ -28,3 +28,11 @@ chip_enable_icd_lit = false icd_enforce_sit_slow_poll_limit = true chip_persist_subscriptions = true chip_subscription_timeout_resumption = true + +is_debug = false + +chip_crypto = "platform" +chip_openthread_ftd = false +chip_with_ot_cli = 0 + +chip_with_diag_logs_demo = true diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h b/examples/contact-sensor-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h index 808b70aad0cab7..0d9a4e3b503bee 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h @@ -94,8 +94,13 @@ #define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0x8006 // Use a default setup PIN code if one hasn't been provisioned in flash. +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#endif + +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 +#endif // Use a default pairing code if one hasn't been provisioned in flash. #define CHIP_DEVICE_CONFIG_USE_TEST_PAIRING_CODE "CHIPUS" @@ -186,8 +191,6 @@ #define CHIP_CONFIG_MAX_FABRICS 5 // 5 is the minimum number of supported fabrics #define CHIP_DEVICE_CONFIG_ENABLE_SED 1 -#define CHIP_DEVICE_CONFIG_SED_IDLE_INTERVAL 1000_ms32 -#define CHIP_DEVICE_CONFIG_SED_ACTIVE_INTERVAL 100_ms32 /** * @def CHIP_IM_MAX_NUM_COMMAND_HANDLER @@ -217,3 +220,8 @@ #endif // BUILD_RELEASE #define CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY 1 + +#if CONFIG_DIAG_LOGS_DEMO +#define CHIP_CONFIG_ENABLE_BDX_LOG_TRANSFER 1 +#define CHIP_DEVICE_CONFIG_MAX_DIAG_LOG_SIZE 1024 +#endif diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp b/examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp index 3e9380254bd428..6f0459178cd173 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/AppTask.cpp @@ -30,6 +30,9 @@ #include #include #include +#if CONFIG_DIAG_LOGS_DEMO +#include "DiagnosticLogsProviderDelegateImpl.h" +#endif #include #include @@ -95,6 +98,9 @@ using namespace ::chip::Credentials; using namespace ::chip::DeviceLayer; using namespace chip; using namespace chip::app; +#if CONFIG_DIAG_LOGS_DEMO +using namespace chip::app::Clusters::DiagnosticLogs; +#endif AppTask AppTask::sAppTask; #if CONFIG_CHIP_LOAD_REAL_FACTORY_DATA @@ -114,6 +120,11 @@ static BDXDownloader gDownloader __attribute__((section(".data"))); constexpr uint16_t requestedOtaBlockSize = 1024; #endif +static pm_notify_element_t appNotifyElement = { + .notifyCallback = AppTask::LowPowerCallback, + .data = NULL, +}; + static void app_gap_callback(gapGenericEvent_t * event) { /* This callback is called in the context of BLE task, so event processing @@ -148,7 +159,15 @@ CHIP_ERROR AppTask::Init() if (ContactSensorMgr().Init() != 0) { K32W_LOG("ContactSensorMgr().Init() failed"); - assert(status == 0); + assert(0); + } + + // Register enter/exit low power application callback. + status_t status = PM_RegisterNotify(kPM_NotifyGroup2, &appNotifyElement); + if (status != kStatus_Success) + { + K32W_LOG("Failed to register low power app callback.") + return APP_ERROR_PM_REGISTER_LP_CALLBACK_FAILED; } PlatformMgr().AddEventHandler(MatterEventHandler, 0); @@ -250,6 +269,24 @@ void AppTask::InitServer(intptr_t arg) nativeParams.openThreadInstancePtr = chip::DeviceLayer::ThreadStackMgrImpl().OTInstance(); initParams.endpointNativeParams = static_cast(&nativeParams); VerifyOrDie((chip::Server::GetInstance().Init(initParams)) == CHIP_NO_ERROR); + +#if CONFIG_DIAG_LOGS_DEMO + char diagLog[CHIP_DEVICE_CONFIG_MAX_DIAG_LOG_SIZE]; + uint16_t diagLogSize = CHIP_DEVICE_CONFIG_MAX_DIAG_LOG_SIZE; + + StorageKeyName keyUser = LogProvider::GetKeyDiagUserSupport(); + StorageKeyName keyNwk = LogProvider::GetKeyDiagNetwork(); + StorageKeyName keyCrash = LogProvider::GetKeyDiagCrashLog(); + + memset(diagLog, 0, diagLogSize); + Server::GetInstance().GetPersistentStorage().SyncSetKeyValue(keyUser.KeyName(), diagLog, diagLogSize); + + memset(diagLog, 1, diagLogSize); + Server::GetInstance().GetPersistentStorage().SyncSetKeyValue(keyNwk.KeyName(), diagLog, diagLogSize); + + memset(diagLog, 2, diagLogSize); + Server::GetInstance().GetPersistentStorage().SyncSetKeyValue(keyCrash.KeyName(), diagLog, diagLogSize); +#endif } void AppTask::PrintOnboardingInfo() @@ -768,6 +805,11 @@ void AppTask::OnIdentifyStop(Identify * identify) } } +status_t AppTask::LowPowerCallback(pm_event_type_t eventType, uint8_t powerState, void * data) +{ + return kStatus_Success; +} + void AppTask::PostContactActionRequest(ContactSensorManager::Action aAction) { AppEvent event; diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp b/examples/contact-sensor-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp index e1e35b4cb4ec7e..5937d134194c30 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp @@ -30,10 +30,18 @@ #include #include +#if CONFIG_DIAG_LOGS_DEMO +#include "DiagnosticLogsProviderDelegateImpl.h" +#include +#endif + using namespace ::chip; using namespace ::chip::app; using namespace ::chip::app::Clusters; using namespace ::chip::app::Clusters::BooleanState; +#if CONFIG_DIAG_LOGS_DEMO +using namespace ::chip::app::Clusters::DiagnosticLogs; +#endif void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & path, uint8_t type, uint16_t size, uint8_t * value) { @@ -96,3 +104,11 @@ void logBooleanStateEvent(bool state) ChipLogProgress(Zcl, "booleanstate: failed to reacord state-change event"); } } + +#if CONFIG_DIAG_LOGS_DEMO +void emberAfDiagnosticLogsClusterInitCallback(chip::EndpointId endpoint) +{ + auto & logProvider = LogProvider::GetInstance(); + DiagnosticLogsServer::Instance().SetDiagnosticLogsProviderDelegate(endpoint, &logProvider); +} +#endif diff --git a/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppTask.h b/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppTask.h index 2338131f06c4b0..64842e310561b2 100644 --- a/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppTask.h +++ b/examples/contact-sensor-app/nxp/k32w/k32w1/main/include/AppTask.h @@ -36,6 +36,7 @@ #include "FreeRTOS.h" #include "fsl_component_button.h" +#include "fsl_pm_core.h" #include "timers.h" // Application-defined error codes in the CHIP_ERROR space. @@ -45,6 +46,7 @@ #define APP_ERROR_CREATE_TIMER_FAILED CHIP_APPLICATION_ERROR(0x04) #define APP_ERROR_START_TIMER_FAILED CHIP_APPLICATION_ERROR(0x05) #define APP_ERROR_STOP_TIMER_FAILED CHIP_APPLICATION_ERROR(0x06) +#define APP_ERROR_PM_REGISTER_LP_CALLBACK_FAILED CHIP_APPLICATION_ERROR(0x07) class AppTask { @@ -67,6 +69,8 @@ class AppTask static void OnIdentifyStart(Identify * identify); static void OnIdentifyStop(Identify * identify); + static status_t LowPowerCallback(pm_event_type_t eventType, uint8_t powerState, void * data); + private: friend AppTask & GetAppTask(void); @@ -96,6 +100,7 @@ class AppTask static void TimerEventHandler(TimerHandle_t xTimer); static void MatterEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + void StartTimer(uint32_t aTimeoutInMs); #if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR diff --git a/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter b/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter index fcec6408acfcb8..b3c6233dc7a8e9 100644 --- a/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter +++ b/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.matter @@ -659,6 +659,53 @@ cluster NetworkCommissioning = 49 { command access(invoke: administer) QueryIdentity(QueryIdentityRequest): QueryIdentityResponse = 9; } +/** The cluster provides commands for retrieving unstructured diagnostic logs from a Node that may be used to aid in diagnostics. */ +cluster DiagnosticLogs = 50 { + revision 1; // NOTE: Default/not specifically set + + enum IntentEnum : enum8 { + kEndUserSupport = 0; + kNetworkDiag = 1; + kCrashLogs = 2; + } + + enum StatusEnum : enum8 { + kSuccess = 0; + kExhausted = 1; + kNoLogs = 2; + kBusy = 3; + kDenied = 4; + } + + enum TransferProtocolEnum : enum8 { + kResponsePayload = 0; + kBDX = 1; + } + + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct RetrieveLogsRequestRequest { + IntentEnum intent = 0; + TransferProtocolEnum requestedProtocol = 1; + optional char_string<32> transferFileDesignator = 2; + } + + response struct RetrieveLogsResponse = 1 { + StatusEnum status = 0; + long_octet_string logContent = 1; + optional epoch_us UTCTimeStamp = 2; + optional systime_us timeSinceBoot = 3; + } + + /** Retrieving diagnostic logs from a Node */ + command RetrieveLogsRequest(RetrieveLogsRequestRequest): RetrieveLogsResponse = 0; +} + /** The General Diagnostics Cluster, along with other diagnostics clusters, provide a means to acquire standardized diagnostics metrics that MAY be used by a Node to assist a user or Administrative Node in diagnosing potential problems. */ cluster GeneralDiagnostics = 51 { revision 2; @@ -1371,6 +1418,7 @@ endpoint 0 { emits event AccessControlEntryChanged; emits event AccessControlExtensionChanged; callback attribute acl; + callback attribute extension; callback attribute subjectsPerAccessControlEntry; callback attribute targetsPerAccessControlEntry; callback attribute accessControlEntriesPerFabric; @@ -1471,6 +1519,18 @@ endpoint 0 { handle command ReorderNetwork; } + server cluster DiagnosticLogs { + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command RetrieveLogsRequest; + handle command RetrieveLogsResponse; + } + server cluster GeneralDiagnostics { emits event BootReason; callback attribute networkInterfaces; diff --git a/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.zap b/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.zap index 1e9f3e8bee2875..68b94ef250a67a 100644 --- a/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.zap +++ b/examples/contact-sensor-app/nxp/zap-lit/contact-sensor-app.zap @@ -190,6 +190,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "Extension", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "SubjectsPerAccessControlEntry", "code": 2, @@ -248,7 +264,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -264,7 +280,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -280,7 +296,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -1480,6 +1496,130 @@ } ] }, + { + "name": "Diagnostic Logs", + "code": 50, + "mfgCode": null, + "define": "DIAGNOSTIC_LOGS_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "RetrieveLogsRequest", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RetrieveLogsResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, { "name": "General Diagnostics", "code": 51, @@ -2805,7 +2945,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2821,7 +2961,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2837,7 +2977,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2853,7 +2993,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "", "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter b/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter index 3cd88fd42fb0c7..fd15a473ac447b 100644 --- a/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter +++ b/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.matter @@ -659,6 +659,53 @@ cluster NetworkCommissioning = 49 { command access(invoke: administer) QueryIdentity(QueryIdentityRequest): QueryIdentityResponse = 9; } +/** The cluster provides commands for retrieving unstructured diagnostic logs from a Node that may be used to aid in diagnostics. */ +cluster DiagnosticLogs = 50 { + revision 1; // NOTE: Default/not specifically set + + enum IntentEnum : enum8 { + kEndUserSupport = 0; + kNetworkDiag = 1; + kCrashLogs = 2; + } + + enum StatusEnum : enum8 { + kSuccess = 0; + kExhausted = 1; + kNoLogs = 2; + kBusy = 3; + kDenied = 4; + } + + enum TransferProtocolEnum : enum8 { + kResponsePayload = 0; + kBDX = 1; + } + + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct RetrieveLogsRequestRequest { + IntentEnum intent = 0; + TransferProtocolEnum requestedProtocol = 1; + optional char_string<32> transferFileDesignator = 2; + } + + response struct RetrieveLogsResponse = 1 { + StatusEnum status = 0; + long_octet_string logContent = 1; + optional epoch_us UTCTimeStamp = 2; + optional systime_us timeSinceBoot = 3; + } + + /** Retrieving diagnostic logs from a Node */ + command RetrieveLogsRequest(RetrieveLogsRequestRequest): RetrieveLogsResponse = 0; +} + /** The General Diagnostics Cluster, along with other diagnostics clusters, provide a means to acquire standardized diagnostics metrics that MAY be used by a Node to assist a user or Administrative Node in diagnosing potential problems. */ cluster GeneralDiagnostics = 51 { revision 2; @@ -1371,6 +1418,7 @@ endpoint 0 { emits event AccessControlEntryChanged; emits event AccessControlExtensionChanged; callback attribute acl; + callback attribute extension; callback attribute subjectsPerAccessControlEntry; callback attribute targetsPerAccessControlEntry; callback attribute accessControlEntriesPerFabric; @@ -1471,6 +1519,18 @@ endpoint 0 { handle command ReorderNetwork; } + server cluster DiagnosticLogs { + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command RetrieveLogsRequest; + handle command RetrieveLogsResponse; + } + server cluster GeneralDiagnostics { emits event BootReason; callback attribute networkInterfaces; diff --git a/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.zap b/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.zap index e0b5e7995d96fe..a433e8e7bd9c9a 100644 --- a/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.zap +++ b/examples/contact-sensor-app/nxp/zap-sit/contact-sensor-app.zap @@ -1,6 +1,6 @@ { "fileFormat": 2, - "featureLevel": 100, + "featureLevel": 99, "creator": "zap", "keyValuePairs": [ { @@ -190,6 +190,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "Extension", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "SubjectsPerAccessControlEntry", "code": 2, @@ -1480,6 +1496,130 @@ } ] }, + { + "name": "Diagnostic Logs", + "code": 50, + "mfgCode": null, + "define": "DIAGNOSTIC_LOGS_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "RetrieveLogsRequest", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RetrieveLogsResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, { "name": "General Diagnostics", "code": 51, @@ -4010,16 +4150,14 @@ "endpointTypeIndex": 0, "profileId": 259, "endpointId": 0, - "networkId": 0, - "parentEndpointIdentifier": null + "networkId": 0 }, { "endpointTypeName": "MA-dimmablelight", "endpointTypeIndex": 1, "profileId": 259, "endpointId": 1, - "networkId": 0, - "parentEndpointIdentifier": null + "networkId": 0 } ] } \ No newline at end of file diff --git a/examples/laundry-washer-app/nxp/common/main/AppTask.cpp b/examples/laundry-washer-app/nxp/common/main/AppTask.cpp new file mode 100644 index 00000000000000..1137db82338b6f --- /dev/null +++ b/examples/laundry-washer-app/nxp/common/main/AppTask.cpp @@ -0,0 +1,140 @@ +/* + * + * Copyright (c) 2021-2023 Project CHIP Authors + * Copyright (c) 2021 Google LLC. + * Copyright 2023-2024 NXP + * 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 "AppTask.h" +#include "CHIPDeviceManager.h" +#include "ICDUtil.h" +#include +#include + +#include "static-supported-temperature-levels.h" +#include + +#ifdef ENABLE_CHIP_SHELL +#include + +using namespace chip::Shell; +#endif /* ENABLE_CHIP_SHELL */ + +using namespace chip; +using namespace chip::app::Clusters; + +app::Clusters::TemperatureControl::AppSupportedTemperatureLevelsDelegate sAppSupportedTemperatureLevelsDelegate; + +CHIP_ERROR cliOpState(int argc, char * argv[]) +{ + if ((argc != 1) && (argc != 2)) + { + ChipLogError(Shell, "Target State is missing"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + if (!strcmp(argv[0], "stop")) + { + ChipLogDetail(Shell, "OpSState : Set to %s state", argv[0]); + OperationalState::GetOperationalStateInstance()->SetOperationalState( + to_underlying(OperationalState::OperationalStateEnum::kStopped)); + } + else if (!strcmp(argv[0], "run")) + { + ChipLogDetail(Shell, "OpSState : Set to %s state", argv[0]); + OperationalState::GetOperationalStateInstance()->SetOperationalState( + to_underlying(OperationalState::OperationalStateEnum::kRunning)); + } + else if (!strcmp(argv[0], "pause")) + { + ChipLogDetail(Shell, "OpSState : Set to %s state", argv[0]); + OperationalState::GetOperationalStateInstance()->SetOperationalState( + to_underlying(OperationalState::OperationalStateEnum::kPaused)); + } + else if (!strcmp(argv[0], "error")) + { + OperationalState::Structs::ErrorStateStruct::Type err; + ChipLogDetail(Shell, "OpSState : Set to %s state", argv[0]); + if (!strcmp(argv[1], "no_error")) + { + ChipLogDetail(Shell, "OpSState_error : Error: %s state", argv[1]); + err.errorStateID = (uint8_t) OperationalState::ErrorStateEnum::kNoError; + } + else if (!strcmp(argv[1], "unable_to_start_or_resume")) + { + ChipLogDetail(Shell, "OpSState_error : Error: %s state", argv[1]); + err.errorStateID = (uint8_t) OperationalState::ErrorStateEnum::kUnableToStartOrResume; + } + else if (!strcmp(argv[1], "unable_to_complete_operation")) + { + ChipLogDetail(Shell, "OpSState_error : Error: %s state", argv[1]); + err.errorStateID = (uint8_t) OperationalState::ErrorStateEnum::kUnableToCompleteOperation; + } + else if (!strcmp(argv[1], "command_invalid_in_state")) + { + ChipLogDetail(Shell, "OpSState_error : Error: %s state", argv[1]); + err.errorStateID = (uint8_t) OperationalState::ErrorStateEnum::kCommandInvalidInState; + } + else + { + ChipLogError(Shell, "Invalid Error State to set"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + OperationalState::GetOperationalStateInstance()->OnOperationalErrorDetected(err); + OperationalState::GetOperationalStateInstance()->SetOperationalState( + to_underlying(OperationalState::OperationalStateEnum::kError)); + } + else + { + ChipLogError(Shell, "Invalid State to set"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + return CHIP_NO_ERROR; +} + +void LaundryWasherApp::AppTask::PreInitMatterStack() +{ + ChipLogProgress(DeviceLayer, "Welcome to NXP laundry washer Demo App"); +} + +void LaundryWasherApp::AppTask::PostInitMatterStack() +{ + chip::app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&chip::NXP::App::GetICDUtil()); + + app::Clusters::TemperatureControl::SetInstance(&sAppSupportedTemperatureLevelsDelegate); +} + +void LaundryWasherApp::AppTask::AppMatter_RegisterCustomCliCommands() +{ +#ifdef ENABLE_CHIP_SHELL + /* Register application commands */ + static const shell_command_t kCommands[] = { + { .cmd_func = cliOpState, .cmd_name = "opstate", .cmd_help = "Set the Operational State" }, + }; + Engine::Root().RegisterCommands(kCommands, sizeof(kCommands) / sizeof(kCommands[0])); +#endif +} + +// This returns an instance of this class. +LaundryWasherApp::AppTask & LaundryWasherApp::AppTask::GetDefaultInstance() +{ + static LaundryWasherApp::AppTask sAppTask; + return sAppTask; +} + +chip::NXP::App::AppTaskBase & chip::NXP::App::GetAppTask() +{ + return LaundryWasherApp::AppTask::GetDefaultInstance(); +} diff --git a/examples/laundry-washer-app/nxp/common/main/DeviceCallbacks.cpp b/examples/laundry-washer-app/nxp/common/main/DeviceCallbacks.cpp new file mode 100644 index 00000000000000..0d20cf015a7954 --- /dev/null +++ b/examples/laundry-washer-app/nxp/common/main/DeviceCallbacks.cpp @@ -0,0 +1,135 @@ +/* + * + * Copyright (c) 2020-2023 Project CHIP Authors + * Copyright 2024 NXP + * 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. + */ + +/** + * @file DeviceCallbacks.cpp + * + * Implements all the callbacks to the application from the CHIP Stack + * + **/ +#include "DeviceCallbacks.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace chip::app; +void OnTriggerEffect(::Identify * identify) +{ + switch (identify->mCurrentEffectIdentifier) + { + case Clusters::Identify::EffectIdentifierEnum::kBlink: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kBlink"); + break; + case Clusters::Identify::EffectIdentifierEnum::kBreathe: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kBreathe"); + break; + case Clusters::Identify::EffectIdentifierEnum::kOkay: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kOkay"); + break; + case Clusters::Identify::EffectIdentifierEnum::kChannelChange: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kChannelChange"); + break; + default: + ChipLogProgress(Zcl, "No identifier effect"); + return; + } +} + +Identify gIdentify0 = { + chip::EndpointId{ 1 }, + [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStart"); }, + [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStop"); }, + chip::app::Clusters::Identify::IdentifyTypeEnum::kNone, + OnTriggerEffect, +}; + +Identify gIdentify1 = { + chip::EndpointId{ 1 }, + [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStart"); }, + [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStop"); }, + chip::app::Clusters::Identify::IdentifyTypeEnum::kNone, + OnTriggerEffect, +}; + +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::System; +using namespace ::chip::DeviceLayer; +using namespace chip::app::Clusters; + +void LaundryWasherApp::DeviceCallbacks::PostAttributeChangeCallback(EndpointId endpointId, ClusterId clusterId, + AttributeId attributeId, uint8_t type, uint16_t size, + uint8_t * value) +{ + ChipLogProgress(DeviceLayer, + "endpointId " ChipLogFormatMEI " clusterId " ChipLogFormatMEI " attribute ID: " ChipLogFormatMEI + " Type: %u Value: %u, length %u", + ChipLogValueMEI(endpointId), ChipLogValueMEI(clusterId), ChipLogValueMEI(attributeId), type, *value, size); + switch (clusterId) + { + case Clusters::OnOff::Id: + OnOnOffPostAttributeChangeCallback(endpointId, attributeId, value); + break; + } +} + +void LaundryWasherApp::DeviceCallbacks::OnOnOffPostAttributeChangeCallback(chip::EndpointId endpointId, + chip::AttributeId attributeId, uint8_t * value) +{ + switch (attributeId) + { + case Clusters::OnOff::Attributes::OnOff::Id: + if ((value != nullptr) && (*value == true)) + { + // Update the current mode to OnMode after device is on + ModeBase::Instance * modeInstance = LaundryWasherMode::Instance(); + + if (modeInstance != nullptr) + { + DataModel::Nullable mode = modeInstance->GetOnMode(); + if (mode.IsNull() == false) + { + modeInstance->UpdateCurrentMode(mode.Value()); + } + } + } + break; + + default:; + } +} + +// This returns an instance of this class. +LaundryWasherApp::DeviceCallbacks & LaundryWasherApp::DeviceCallbacks::GetDefaultInstance() +{ + static LaundryWasherApp::DeviceCallbacks sDeviceCallbacks; + return sDeviceCallbacks; +} + +chip::DeviceManager::CHIPDeviceManagerCallbacks & chip::NXP::App::GetDeviceCallbacks() +{ + return LaundryWasherApp::DeviceCallbacks::GetDefaultInstance(); +} diff --git a/examples/laundry-washer-app/nxp/common/main/ZclCallbacks.cpp b/examples/laundry-washer-app/nxp/common/main/ZclCallbacks.cpp new file mode 100644 index 00000000000000..0a26df6424b771 --- /dev/null +++ b/examples/laundry-washer-app/nxp/common/main/ZclCallbacks.cpp @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2021-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 + +#include "AppTask.h" +#include "CHIPDeviceManager.h" + +#include +#include +#include +#include +#include + +#include "laundry-washer-controls-delegate-impl.h" + +using namespace ::chip; + +void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & path, uint8_t type, uint16_t size, uint8_t * value) +{ + chip::DeviceManager::CHIPDeviceManagerCallbacks * cb = + chip::DeviceManager::CHIPDeviceManager::GetInstance().GetCHIPDeviceManagerCallbacks(); + if (cb != nullptr) + { + // propagate event to device manager + cb->PostAttributeChangeCallback(path.mEndpointId, path.mClusterId, path.mAttributeId, type, size, value); + } +} + +using namespace chip::app::Clusters::LaundryWasherControls; +void emberAfLaundryWasherControlsClusterInitCallback(EndpointId endpoint) +{ + LaundryWasherControlsServer::SetDefaultDelegate(endpoint, &LaundryWasherControlDelegate::getLaundryWasherControlDelegate()); +} diff --git a/examples/shell/nxp/k32w/k32w0/include/AppEvent.h b/examples/laundry-washer-app/nxp/common/main/include/AppEvent.h similarity index 80% rename from examples/shell/nxp/k32w/k32w0/include/AppEvent.h rename to examples/laundry-washer-app/nxp/common/main/include/AppEvent.h index 0e49456b8addd7..a0dad141a27055 100644 --- a/examples/shell/nxp/k32w/k32w0/include/AppEvent.h +++ b/examples/laundry-washer-app/nxp/common/main/include/AppEvent.h @@ -1,5 +1,5 @@ /* - * + * Copyright (c) 2020 Project CHIP Authors * Copyright (c) 2021 Nest Labs, Inc. * All rights reserved. * @@ -19,27 +19,21 @@ #pragma once struct AppEvent; -typedef void (*EventHandler)(AppEvent *); +using EventHandler = void (*)(const AppEvent &); struct AppEvent { enum AppEventTypes { - kEventType_Button = 0, - kEventType_Timer, + kEventType_Timer = 0, kEventType_TurnOn, kEventType_Install, }; - AppEventTypes Type; + uint16_t Type; union { - struct - { - uint8_t PinNo; - uint8_t Action; - } ButtonEvent; struct { void * Context; @@ -48,7 +42,7 @@ struct AppEvent { uint8_t Action; int32_t Actor; - } LightEvent; + } ClusterEvent; }; EventHandler Handler; diff --git a/examples/laundry-washer-app/nxp/common/main/include/AppTask.h b/examples/laundry-washer-app/nxp/common/main/include/AppTask.h new file mode 100644 index 00000000000000..239328f8a1c7ba --- /dev/null +++ b/examples/laundry-washer-app/nxp/common/main/include/AppTask.h @@ -0,0 +1,56 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2021-2023 Google LLC. + * Copyright 2024 NXP + * 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. + */ + +#pragma once + +#if CONFIG_APP_FREERTOS_OS +#include "AppTaskFreeRTOS.h" +#else +#include "AppTaskZephyr.h" +#endif + +namespace LaundryWasherApp { +#if CONFIG_APP_FREERTOS_OS +class AppTask : public chip::NXP::App::AppTaskFreeRTOS +#else +class AppTask : public chip::NXP::App::AppTaskZephyr +#endif +{ +public: + ~AppTask() override{}; + void PreInitMatterStack(void) override; + void PostInitMatterStack(void) override; + void AppMatter_RegisterCustomCliCommands(void) override; + // This returns an instance of this class. + static AppTask & GetDefaultInstance(); + +private: + static AppTask sAppTask; +}; + +} // namespace LaundryWasherApp + +/** + * Returns the application-specific implementation of the AppTaskBase object. + * + * Applications can use this to gain access to features of the AppTaskBase + * that are specific to the selected application. + */ +chip::NXP::App::AppTaskBase & GetAppTask(); diff --git a/examples/laundry-washer-app/nxp/common/main/include/DeviceCallbacks.h b/examples/laundry-washer-app/nxp/common/main/include/DeviceCallbacks.h new file mode 100644 index 00000000000000..0b16761b2f6f18 --- /dev/null +++ b/examples/laundry-washer-app/nxp/common/main/include/DeviceCallbacks.h @@ -0,0 +1,53 @@ +/* + * + * Copyright (c) 2020-2023 Project CHIP Authors + * Copyright 2024 NXP + * 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. + */ + +/** + * @file DeviceCallbacks.h + * + * Implementations for the DeviceManager callbacks for this application + * + **/ + +#pragma once + +#include "CHIPDeviceManager.h" +#include "CommonDeviceCallbacks.h" + +namespace LaundryWasherApp { +class DeviceCallbacks : public chip::NXP::App::CommonDeviceCallbacks +{ +public: + // This returns an instance of this class. + static DeviceCallbacks & GetDefaultInstance(); + void PostAttributeChangeCallback(chip::EndpointId endpointId, chip::ClusterId clusterId, chip::AttributeId attributeId, + uint8_t type, uint16_t size, uint8_t * value); + +private: + void OnOnOffPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, uint8_t * value); + void OnIdentifyPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, uint8_t * value); +}; +} // namespace LaundryWasherApp + +/** + * Returns the application-specific implementation of the CommonDeviceCallbacks object. + * + * Applications can use this to gain access to features of the CommonDeviceCallbacks + * that are specific to the selected application. + */ +chip::DeviceManager::CHIPDeviceManagerCallbacks & GetDeviceCallbacks(); diff --git a/examples/laundry-washer-app/nxp/common/main/include/operational-state-delegate-impl.h b/examples/laundry-washer-app/nxp/common/main/include/operational-state-delegate-impl.h new file mode 100644 index 00000000000000..79f2695eb0a628 --- /dev/null +++ b/examples/laundry-washer-app/nxp/common/main/include/operational-state-delegate-impl.h @@ -0,0 +1,120 @@ +/* + * + * 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. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { + +namespace OperationalState { + +// This is an application level delegate to handle operational state commands according to the specific business logic. +class GenericOperationalStateDelegateImpl : public Delegate +{ +public: + /** + * Get the countdown time. This attribute is not used in this application. + * @return The current countdown time. + */ + app::DataModel::Nullable GetCountdownTime() override { return {}; }; + + /** + * Fills in the provided GenericOperationalState with the state at index `index` if there is one, + * or returns CHIP_ERROR_NOT_FOUND if the index is out of range for the list of states. + * Note: This is used by the SDK to populate the operational state list attribute. If the contents of this list changes, + * the device SHALL call the Instance's ReportOperationalStateListChange method to report that this attribute has changed. + * @param index The index of the state, with 0 representing the first state. + * @param operationalState The GenericOperationalState is filled. + */ + CHIP_ERROR GetOperationalStateAtIndex(size_t index, GenericOperationalState & operationalState) override; + + /** + * Fills in the provided MutableCharSpan with the phase at index `index` if there is one, + * or returns CHIP_ERROR_NOT_FOUND if the index is out of range for the list of phases. + * + * If CHIP_ERROR_NOT_FOUND is returned for index 0, that indicates that the PhaseList attribute is null + * (there are no phases defined at all). + * + * Note: This is used by the SDK to populate the phase list attribute. If the contents of this list changes, the + * device SHALL call the Instance's ReportPhaseListChange method to report that this attribute has changed. + * @param index The index of the phase, with 0 representing the first phase. + * @param operationalPhase The MutableCharSpan is filled. + */ + CHIP_ERROR GetOperationalPhaseAtIndex(size_t index, MutableCharSpan & operationalPhase) override; + + // command callback + /** + * Handle Command Callback in application: Pause + * @param[out] get operational error after callback. + */ + void HandlePauseStateCallback(GenericOperationalError & err) override; + + /** + * Handle Command Callback in application: Resume + * @param[out] get operational error after callback. + */ + void HandleResumeStateCallback(GenericOperationalError & err) override; + + /** + * Handle Command Callback in application: Start + * @param[out] get operational error after callback. + */ + void HandleStartStateCallback(GenericOperationalError & err) override; + + /** + * Handle Command Callback in application: Stop + * @param[out] get operational error after callback. + */ + void HandleStopStateCallback(GenericOperationalError & err) override; + +protected: + Span mOperationalStateList; + Span mOperationalPhaseList; +}; + +// This is an application level delegate to handle operational state commands according to the specific business logic. +class OperationalStateDelegate : public GenericOperationalStateDelegateImpl +{ +private: + const GenericOperationalState opStateList[4] = { + GenericOperationalState(to_underlying(OperationalStateEnum::kStopped)), + GenericOperationalState(to_underlying(OperationalStateEnum::kRunning)), + GenericOperationalState(to_underlying(OperationalStateEnum::kPaused)), + GenericOperationalState(to_underlying(OperationalStateEnum::kError)), + }; + +public: + OperationalStateDelegate() + { + GenericOperationalStateDelegateImpl::mOperationalStateList = Span(opStateList); + } +}; + +Instance * GetOperationalStateInstance(); + +void Shutdown(); + +} // namespace OperationalState +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/laundry-washer-app/nxp/common/main/main.cpp b/examples/laundry-washer-app/nxp/common/main/main.cpp new file mode 100644 index 00000000000000..b2aadab98ceb8b --- /dev/null +++ b/examples/laundry-washer-app/nxp/common/main/main.cpp @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2021-2023 Google LLC. + * 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. + */ + +// ================================================================================ +// Main Code +// ================================================================================ + +#include "FreeRTOS.h" +#include +#include +#include + +#if configAPPLICATION_ALLOCATED_HEAP +uint8_t __attribute__((section(".heap"))) ucHeap[configTOTAL_HEAP_SIZE]; +#endif + +using namespace ::chip::DeviceLayer; + +extern "C" int main(int argc, char * argv[]) +{ + TaskHandle_t taskHandle; + + PlatformMgrImpl().HardwareInit(); + chip::NXP::App::GetAppTask().Start(); + vTaskStartScheduler(); +} + +#if (defined(configCHECK_FOR_STACK_OVERFLOW) && (configCHECK_FOR_STACK_OVERFLOW > 0)) +void vApplicationStackOverflowHook(TaskHandle_t xTask, char * pcTaskName) +{ + assert(0); +} +#endif diff --git a/examples/laundry-washer-app/nxp/common/main/operational-state-delegate-impl.cpp b/examples/laundry-washer-app/nxp/common/main/operational-state-delegate-impl.cpp new file mode 100644 index 00000000000000..4a6608842c174d --- /dev/null +++ b/examples/laundry-washer-app/nxp/common/main/operational-state-delegate-impl.cpp @@ -0,0 +1,143 @@ +/* + * + * 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 + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::OperationalState; +using namespace chip::app::Clusters::RvcOperationalState; + +CHIP_ERROR GenericOperationalStateDelegateImpl::GetOperationalStateAtIndex(size_t index, GenericOperationalState & operationalState) +{ + if (index >= mOperationalStateList.size()) + { + return CHIP_ERROR_NOT_FOUND; + } + operationalState = mOperationalStateList[index]; + return CHIP_NO_ERROR; +} + +CHIP_ERROR GenericOperationalStateDelegateImpl::GetOperationalPhaseAtIndex(size_t index, MutableCharSpan & operationalPhase) +{ + if (index >= mOperationalPhaseList.size()) + { + return CHIP_ERROR_NOT_FOUND; + } + return CopyCharSpanToMutableCharSpan(mOperationalPhaseList[index], operationalPhase); +} + +void GenericOperationalStateDelegateImpl::HandlePauseStateCallback(GenericOperationalError & err) +{ + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kPaused)); + if (error == CHIP_NO_ERROR) + { + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +void GenericOperationalStateDelegateImpl::HandleResumeStateCallback(GenericOperationalError & err) +{ + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kRunning)); + if (error == CHIP_NO_ERROR) + { + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +void GenericOperationalStateDelegateImpl::HandleStartStateCallback(GenericOperationalError & err) +{ + uint8_t opState = GetInstance()->GetCurrentOperationalState(); + if (opState == to_underlying(OperationalStateEnum::kError)) + { + err.Set(to_underlying(ErrorStateEnum::kUnableToStartOrResume)); + return; + } + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kRunning)); + if (error == CHIP_NO_ERROR) + { + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +void GenericOperationalStateDelegateImpl::HandleStopStateCallback(GenericOperationalError & err) +{ + // placeholder implementation + auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kStopped)); + if (error == CHIP_NO_ERROR) + { + err.Set(to_underlying(ErrorStateEnum::kNoError)); + } + else + { + err.Set(to_underlying(ErrorStateEnum::kUnableToCompleteOperation)); + } +} + +// Init Operational State cluster + +static OperationalState::Instance * gOperationalStateInstance = nullptr; +static OperationalStateDelegate * gOperationalStateDelegate = nullptr; + +OperationalState::Instance * OperationalState::GetOperationalStateInstance() +{ + return gOperationalStateInstance; +} + +void OperationalState::Shutdown() +{ + if (gOperationalStateInstance != nullptr) + { + delete gOperationalStateInstance; + gOperationalStateInstance = nullptr; + } + if (gOperationalStateDelegate != nullptr) + { + delete gOperationalStateDelegate; + gOperationalStateDelegate = nullptr; + } +} + +void emberAfOperationalStateClusterInitCallback(chip::EndpointId endpointId) +{ + VerifyOrDie(endpointId == 1); // this cluster is only enabled for endpoint 1. + VerifyOrDie(gOperationalStateInstance == nullptr && gOperationalStateDelegate == nullptr); + + gOperationalStateDelegate = new OperationalStateDelegate; + EndpointId operationalStateEndpoint = 0x01; + gOperationalStateInstance = new OperationalState::Instance(gOperationalStateDelegate, operationalStateEndpoint); + + gOperationalStateInstance->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kStopped)); + + gOperationalStateInstance->Init(); +} diff --git a/examples/lock-app/nxp/k32w/k32w0/.gn b/examples/laundry-washer-app/nxp/rt/rw61x/.gn similarity index 85% rename from examples/lock-app/nxp/k32w/k32w0/.gn rename to examples/laundry-washer-app/nxp/rt/rw61x/.gn index 3d48789e30ab3d..c0a26c2dc77832 100644 --- a/examples/lock-app/nxp/k32w/k32w0/.gn +++ b/examples/laundry-washer-app/nxp/rt/rw61x/.gn @@ -1,4 +1,5 @@ # Copyright (c) 2020 Project CHIP Authors +# Copyright 2023 NXP # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,6 +14,7 @@ # limitations under the License. import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") # The location of the build configuration file. buildconfig = "${build_root}/config/BUILDCONFIG.gn" @@ -25,4 +27,7 @@ default_args = { target_os = "freertos" import("//args.gni") + + # Import default platform configs + import("${chip_root}/src/platform/nxp/rt/rw61x/args.gni") } diff --git a/examples/laundry-washer-app/nxp/rt/rw61x/BUILD.gn b/examples/laundry-washer-app/nxp/rt/rw61x/BUILD.gn new file mode 100644 index 00000000000000..1a0e8f39d9c9a3 --- /dev/null +++ b/examples/laundry-washer-app/nxp/rt/rw61x/BUILD.gn @@ -0,0 +1,288 @@ +# Copyright (c) 2021 Project CHIP Authors +# Copyright 2023 NXP +# +# 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("//build_overrides/build.gni") +import("//build_overrides/chip.gni") +import("//build_overrides/nxp_sdk.gni") +import("//build_overrides/openthread.gni") +import("${chip_root}/src/platform/device.gni") + +#allows to get common NXP SDK gn options +import("${nxp_sdk_build_root}/nxp_sdk.gni") + +# Allows to get various RT gn options +import("${nxp_sdk_build_root}/${nxp_sdk_name}/rt_sdk.gni") + +import("${chip_root}/src/platform/nxp/${nxp_platform}/args.gni") +import( + "${nxp_sdk_build_root}/${nxp_sdk_name}/${rt_platform}/${rt_platform}.gni") +import("${nxp_sdk_build_root}/${nxp_sdk_name}/nxp_executable.gni") + +assert(current_os == "freertos") +assert(target_os == "freertos") +assert(nxp_platform == "rt/rw61x") + +declare_args() { + # Allows to start the tcp download test app + tcp_download = false + + # Allows to start the wifi connect test app + wifi_connect = false + + # The 2 params below are used only if tcp_download or wifi_connect are true, otherwise they're unused. + wifi_ssid = "" + wifi_password = "" + + # Setup discriminator as argument + setup_discriminator = 3840 +} + +example_platform_dir = "${chip_root}/examples/platform/nxp/${nxp_platform}" +common_example_dir = "${chip_root}/examples/platform/nxp/common" + +if (tcp_download == true && wifi_connect == true) { + assert("Cannot enable tcp_download and wifi_connect at the same time!") +} + +# Use NXP custom zap files for laundry-washer device-type +app_common_folder = "laundry-washer-app/nxp/zap" + +# Create here the SDK instance. +# Particular sources/defines/includes could be added/changed depending on the target application. +rt_sdk("sdk") { + defines = [] + + # To be moved, temporary mbedtls config fix to build app with factory data + if (chip_enable_secure_dac_private_key_storage == 1) { + defines += [ + "MBEDTLS_NIST_KW_C", + "MBEDTLS_PSA_CRYPTO_CLIENT", + ] + } + + cflags = [] + public_deps = [] + public_configs = [] + sources = [] + include_dirs = [] + + # Indicate paths to default board files + include_dirs += [ "${example_platform_dir}/board/" ] + sources += [ "${example_platform_dir}/board/pin_mux.c" ] + sources += [ "${example_platform_dir}/board/hardware_init.c" ] + sources += [ "${example_platform_dir}/board/clock_config.c" ] + sources += [ "${example_platform_dir}/board/board.c" ] + sources += [ "${example_platform_dir}/board/peripherals.c" ] + + # Indicate the path to CHIPProjectConfig.h + include_dirs += [ "include/config" ] + + # Indicate the default path to FreeRTOSConfig.h + include_dirs += [ "${example_platform_dir}/app/project_include/freeRTOS" ] + + # Indicate the default path to OpenThreadConfig.h + include_dirs += [ "${example_platform_dir}/app/project_include/openthread" ] + + # For matter with BR feature, increase FreeRTOS heap size + if (chip_enable_wifi && chip_enable_openthread) { + defines += [ "configTOTAL_HEAP_SIZE=(size_t)(160 * 1024)" ] + } + + defines += [ + "CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR=${setup_discriminator}", + ] +} + +# Create the SDK driver instance. +# Particular sources/defines/includes could be added to add other drivers not available in the default sdk driver template +rw61x_sdk_drivers("sdk_driver") { +} + +rt_executable("laundry-washer") { + output_name = "chip-rw61x-laundry-washer-example" + + defines = [ + "CONFIG_RENDEZVOUS_MODE=7", + "CONFIG_APP_FREERTOS_OS=1", + ] + + if (chip_enable_openthread) { + defines += [ "CONFIG_NET_L2_OPENTHREAD=1" ] + } + + include_dirs = [ + "../../common/main/include", + "../../common/main", + "${chip_root}/examples/all-clusters-app/all-clusters-common/include", + "${chip_root}/examples/providers/", + ] + + sources = [ + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp", + "${chip_root}/examples/providers/DeviceInfoProviderImpl.cpp", + "../../common/main/AppTask.cpp", + "../../common/main/DeviceCallbacks.cpp", + "../../common/main/ZclCallbacks.cpp", + "../../common/main/include/AppEvent.h", + "../../common/main/include/AppTask.h", + "../../common/main/include/DeviceCallbacks.h", + "../../common/main/main.cpp", + ] + + if (chip_enable_secure_dac_private_key_storage == 1) { + sources += [ + "${example_platform_dir}/factory_data/source/AppFactoryDataExample.cpp", + ] + } else { + sources += [ + "${common_example_dir}/factory_data/source/AppFactoryDataDefaultImpl.cpp", + ] + } + + # App common files + include_dirs += [ + "${common_example_dir}/icd/include", + "${common_example_dir}/matter_button/include", + "${common_example_dir}/matter_cli/include", + "${common_example_dir}/device_manager/include", + "${common_example_dir}/device_callbacks/include", + "${common_example_dir}/app_task/include", + "${common_example_dir}/factory_data/include", + ] + + sources += [ + "${common_example_dir}/app_task/source/AppTaskBase.cpp", + "${common_example_dir}/app_task/source/AppTaskFreeRTOS.cpp", + "${common_example_dir}/device_callbacks/source/CommonDeviceCallbacks.cpp", + "${common_example_dir}/device_manager/source/CHIPDeviceManager.cpp", + "${common_example_dir}/icd/source/ICDUtil.cpp", + "${common_example_dir}/matter_button/source/AppMatterButtonEmpty.cpp", + "${common_example_dir}/matter_cli/source/AppMatterCli.cpp", + ] + + deps = [ "${chip_root}/examples/${app_common_folder}" ] + + sources += [ + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/bridged-actions-stub.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/laundry-washer-controls-delegate-impl.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/laundry-washer-mode.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/smco-stub.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-modes-manager.cpp", + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/static-supported-temperature-levels.cpp", + "../../common/main/operational-state-delegate-impl.cpp", + ] + + if (chip_enable_matter_cli) { + defines += [ "ENABLE_CHIP_SHELL" ] + deps += [ + "${chip_root}/examples/shell/shell_common:shell_common", + "${chip_root}/src/lib/shell:shell", + ] + } + + if (chip_enable_ota_requestor) { + include_dirs += [ "${common_example_dir}/ota_requestor/include" ] + sources += [ + "${common_example_dir}/ota_requestor/source/OTARequestorInitiator.cpp", + "${common_example_dir}/ota_requestor/source/OTARequestorInitiatorCommon.cpp", + ] + } + + if (wifi_connect) { + defines += [ + "WIFI_CONNECT_TASK=1", + "WIFI_CONNECT=1", + ] + + if (!chip_enable_matter_cli) { + assert(wifi_ssid != "" && wifi_password != "", + "WiFi SSID and password must be specified at build time!") + } + + if (wifi_ssid != "") { + defines += [ "WIFI_SSID=\"${wifi_ssid}\"" ] + } + + if (wifi_password != "") { + defines += [ "WIFI_PASSWORD=\"${wifi_password}\"" ] + } + + include_dirs += [ "${common_example_dir}/wifi_connect/include" ] + sources += [ "${common_example_dir}/wifi_connect/source/WifiConnect.cpp" ] + } + + if (tcp_download) { + defines += [ "CONFIG_CHIP_TCP_DOWNLOAD=1" ] + defines += [ + "WIFI_CONNECT=1", + "WIFI_SSID=\"${wifi_ssid}\"", + "WIFI_PASSWORD=\"${wifi_password}\"", + ] + + include_dirs += [ "${common_example_dir}/tcp_download_test/include" ] + sources += + [ "${common_example_dir}/tcp_download_test/source/TcpDownload.cpp" ] + } + + # In case a dedicated assert function needs to be supported the flag sdk_fsl_assert_support should be set to false + # The would add to the build a dedicated application assert implementation. + if (!sdk_fsl_assert_support) { + sources += [ "${common_example_dir}/app_assert/source/AppAssert.cpp" ] + } + + cflags = [ "-Wconversion" ] + + ldscript = "${example_platform_dir}/app/ldscripts/RW610_flash.ld" + + inputs = [ ldscript ] + + ldflags = [ + "-T" + rebase_path(ldscript, root_build_dir), + "-fno-common", + "-Wl,--defsym=__stack_size__=2048", + "-ffreestanding", + "-fno-builtin", + "-mapcs", + "-u qspiflash_config", + "-u image_vector_table", + "-u boot_data", + "-u dcd_data", + "-Wl,-print-memory-usage", + "-Wl,--no-warn-rwx-segments", + ] + + if (chip_enable_ota_requestor) { + if (no_mcuboot) { + # If "no_mcuboot" is set to true, the application will be linked at the base of the flash. + print( + "Warning : The OTA Requestor is enabled without MCUBoot. This will prevent the application from applying software updates.") + } else { + # we need to reserve enough space for the bootloader (MCUBoot) + # MCUBoot requires 0x20000 Bytes to be reserved at the base of the flash + # Consequently, some sections will need to be shifted + ldflags += [ "-Wl,--defsym=__m_mcuboot_size__=0x20000" ] + } + } + + output_dir = root_out_dir +} + +group("rw61x") { + deps = [ ":laundry-washer" ] +} + +group("default") { + deps = [ ":rw61x" ] +} diff --git a/examples/laundry-washer-app/nxp/rt/rw61x/README.md b/examples/laundry-washer-app/nxp/rt/rw61x/README.md new file mode 100644 index 00000000000000..e6165a33292b1e --- /dev/null +++ b/examples/laundry-washer-app/nxp/rt/rw61x/README.md @@ -0,0 +1,5 @@ +# CHIP RW61x Laundry washer Application + +All instructions describing how to use a Matter application on NXP RW61x can be +found in [README.md](../../../../all-clusters-app/nxp/rt/rw61x/README.md) root +readme diff --git a/examples/build_overrides/k32w0_sdk.gni b/examples/laundry-washer-app/nxp/rt/rw61x/args.gni similarity index 69% rename from examples/build_overrides/k32w0_sdk.gni rename to examples/laundry-washer-app/nxp/rt/rw61x/args.gni index e77b63d1e02fa9..c2d91a5db7bae7 100644 --- a/examples/build_overrides/k32w0_sdk.gni +++ b/examples/laundry-washer-app/nxp/rt/rw61x/args.gni @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Project CHIP Authors +# Copyright (c) 2023 Project CHIP Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -declare_args() { - # Root directory for k32w SDK. - k32w0_sdk_build_root = - "//third_party/connectedhomeip/third_party/nxp/k32w0_sdk" -} +import("//build_overrides/chip.gni") + +# SDK target definitions +nxp_sdk_target = get_label_info(":sdk", "label_no_toolchain") +nxp_sdk_driver_target = get_label_info(":sdk_driver", "label_no_toolchain") diff --git a/examples/laundry-washer-app/nxp/rt/rw61x/build_overrides b/examples/laundry-washer-app/nxp/rt/rw61x/build_overrides new file mode 120000 index 00000000000000..f10867042f4d19 --- /dev/null +++ b/examples/laundry-washer-app/nxp/rt/rw61x/build_overrides @@ -0,0 +1 @@ +../../../../build_overrides \ No newline at end of file diff --git a/examples/laundry-washer-app/nxp/rt/rw61x/include/config/CHIPProjectConfig.h b/examples/laundry-washer-app/nxp/rt/rw61x/include/config/CHIPProjectConfig.h new file mode 100644 index 00000000000000..2653e97705fe39 --- /dev/null +++ b/examples/laundry-washer-app/nxp/rt/rw61x/include/config/CHIPProjectConfig.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * Copyright 2023 NXP + * 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. + */ + +/** + * @file + * Example project configuration file for CHIP. + * + * This is a place to put application or project-specific overrides + * to the default configuration values for general CHIP features. + * + */ + +#pragma once + +/* + * Tells to the platform Factory Data Provider whether to use the example configuration or real/provisioned data. + */ +#ifndef CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA +#define CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA 0 +#endif + +/** + * CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID + * + * 0xFFF1: Test vendor. + */ +#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID 0xFFF1 + +/** + * CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID + * + */ +#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0x8005 + +#if !CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA +// Use a default pairing code if one hasn't been provisioned in flash. +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#endif + +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 +#endif + +// Use a default pairing code if one hasn't been provisioned in flash. +#define CHIP_DEVICE_CONFIG_USE_TEST_PAIRING_CODE "CHIPUS" + +/** + * CHIP_DEVICE_CONFIG_USE_TEST_SERIAL_NUMBER + * + * Enables the use of a hard-coded default serial number if none + * is found in CHIP NV storage. + */ +#define CHIP_DEVICE_CONFIG_USE_TEST_SERIAL_NUMBER "DUMMY_SN" + +#endif /* !CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA */ + +/** + * CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION + * + * The hardware version number assigned to device or product by the device vendor. This + * number is scoped to the device product id, and typically corresponds to a revision of the + * physical device, a change to its packaging, and/or a change to its marketing presentation. + * This value is generally *not* incremented for device software versions. + */ +#define CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION 100 + +#ifndef CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING +#define CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING "v0.1.0" +#endif + +/** + * CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING + * + * A string identifying the software version running on the device. + * CHIP currently expects the software version to be in the format + * {MAJOR_VERSION}.0d{MINOR_VERSION} + */ +#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING +#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING NXP_CONFIG_DEVICE_SOFTWARE_VERSION_STRING +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION +#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION NXP_CONFIG_DEVICE_SOFTWARE_VERSION +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME +#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME "NXP Semiconductors" +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME +#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME "NXP Demo App" +#endif + +#ifndef CHIP_DEVICE_CONFIG_CERTIFICATION_DECLARATION +//-> format_version = 1 +//-> vendor_id = 0xFFF1 +//-> product_id_array = [ 0x8000, 0x8001, 0x8002, 0x8003, 0x8004, 0x8005, 0x8006, 0x8007, 0x8008, 0x8009, 0x800A, 0x800B, +// 0x800C, 0x800D, 0x800E, 0x800F, 0x8010, 0x8011, 0x8012, 0x8013, 0x8014, 0x8015, 0x8016, 0x8017, 0x8018, 0x8019, 0x801A, +// 0x801B, 0x801C, 0x801D, 0x801E, 0x801F, 0x8020, 0x8021, 0x8022, 0x8023, 0x8024, 0x8025, 0x8026, 0x8027, 0x8028, 0x8029, +// 0x802A, 0x802B, 0x802C, 0x802D, 0x802E, 0x802F, 0x8030, 0x8031, 0x8032, 0x8033, 0x8034, 0x8035, 0x8036, 0x8037, 0x8038, +// 0x8039, 0x803A, 0x803B, 0x803C, 0x803D, 0x803E, 0x803F, 0x8040, 0x8041, 0x8042, 0x8043, 0x8044, 0x8045, 0x8046, 0x8047, +// 0x8048, 0x8049, 0x804A, 0x804B, 0x804C, 0x804D, 0x804E, 0x804F, 0x8050, 0x8051, 0x8052, 0x8053, 0x8054, 0x8055, 0x8056, +// 0x8057, 0x8058, 0x8059, 0x805A, 0x805B, 0x805C, 0x805D, 0x805E, 0x805F, 0x8060, 0x8061, 0x8062, 0x8063 ] +//-> device_type_id = 0x0016 +//-> certificate_id = "ZIG20142ZB330003-24" +//-> security_level = 0 +//-> security_information = 0 +//-> version_number = 0x2694 +//-> certification_type = 0 +//-> dac_origin_vendor_id is not present +//-> dac_origin_product_id is not present +#define CHIP_DEVICE_CONFIG_CERTIFICATION_DECLARATION \ + { \ + 0x30, 0x82, 0x02, 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, 0xa0, 0x82, 0x02, 0x0a, 0x30, \ + 0x82, 0x02, 0x06, 0x02, 0x01, 0x03, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, \ + 0x02, 0x01, 0x30, 0x82, 0x01, 0x71, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x82, \ + 0x01, 0x62, 0x04, 0x82, 0x01, 0x5e, 0x15, 0x24, 0x00, 0x01, 0x25, 0x01, 0xf1, 0xff, 0x36, 0x02, 0x05, 0x00, 0x80, \ + 0x05, 0x01, 0x80, 0x05, 0x02, 0x80, 0x05, 0x03, 0x80, 0x05, 0x04, 0x80, 0x05, 0x05, 0x80, 0x05, 0x06, 0x80, 0x05, \ + 0x07, 0x80, 0x05, 0x08, 0x80, 0x05, 0x09, 0x80, 0x05, 0x0a, 0x80, 0x05, 0x0b, 0x80, 0x05, 0x0c, 0x80, 0x05, 0x0d, \ + 0x80, 0x05, 0x0e, 0x80, 0x05, 0x0f, 0x80, 0x05, 0x10, 0x80, 0x05, 0x11, 0x80, 0x05, 0x12, 0x80, 0x05, 0x13, 0x80, \ + 0x05, 0x14, 0x80, 0x05, 0x15, 0x80, 0x05, 0x16, 0x80, 0x05, 0x17, 0x80, 0x05, 0x18, 0x80, 0x05, 0x19, 0x80, 0x05, \ + 0x1a, 0x80, 0x05, 0x1b, 0x80, 0x05, 0x1c, 0x80, 0x05, 0x1d, 0x80, 0x05, 0x1e, 0x80, 0x05, 0x1f, 0x80, 0x05, 0x20, \ + 0x80, 0x05, 0x21, 0x80, 0x05, 0x22, 0x80, 0x05, 0x23, 0x80, 0x05, 0x24, 0x80, 0x05, 0x25, 0x80, 0x05, 0x26, 0x80, \ + 0x05, 0x27, 0x80, 0x05, 0x28, 0x80, 0x05, 0x29, 0x80, 0x05, 0x2a, 0x80, 0x05, 0x2b, 0x80, 0x05, 0x2c, 0x80, 0x05, \ + 0x2d, 0x80, 0x05, 0x2e, 0x80, 0x05, 0x2f, 0x80, 0x05, 0x30, 0x80, 0x05, 0x31, 0x80, 0x05, 0x32, 0x80, 0x05, 0x33, \ + 0x80, 0x05, 0x34, 0x80, 0x05, 0x35, 0x80, 0x05, 0x36, 0x80, 0x05, 0x37, 0x80, 0x05, 0x38, 0x80, 0x05, 0x39, 0x80, \ + 0x05, 0x3a, 0x80, 0x05, 0x3b, 0x80, 0x05, 0x3c, 0x80, 0x05, 0x3d, 0x80, 0x05, 0x3e, 0x80, 0x05, 0x3f, 0x80, 0x05, \ + 0x40, 0x80, 0x05, 0x41, 0x80, 0x05, 0x42, 0x80, 0x05, 0x43, 0x80, 0x05, 0x44, 0x80, 0x05, 0x45, 0x80, 0x05, 0x46, \ + 0x80, 0x05, 0x47, 0x80, 0x05, 0x48, 0x80, 0x05, 0x49, 0x80, 0x05, 0x4a, 0x80, 0x05, 0x4b, 0x80, 0x05, 0x4c, 0x80, \ + 0x05, 0x4d, 0x80, 0x05, 0x4e, 0x80, 0x05, 0x4f, 0x80, 0x05, 0x50, 0x80, 0x05, 0x51, 0x80, 0x05, 0x52, 0x80, 0x05, \ + 0x53, 0x80, 0x05, 0x54, 0x80, 0x05, 0x55, 0x80, 0x05, 0x56, 0x80, 0x05, 0x57, 0x80, 0x05, 0x58, 0x80, 0x05, 0x59, \ + 0x80, 0x05, 0x5a, 0x80, 0x05, 0x5b, 0x80, 0x05, 0x5c, 0x80, 0x05, 0x5d, 0x80, 0x05, 0x5e, 0x80, 0x05, 0x5f, 0x80, \ + 0x05, 0x60, 0x80, 0x05, 0x61, 0x80, 0x05, 0x62, 0x80, 0x05, 0x63, 0x80, 0x18, 0x24, 0x03, 0x16, 0x2c, 0x04, 0x13, \ + 0x5a, 0x49, 0x47, 0x32, 0x30, 0x31, 0x34, 0x32, 0x5a, 0x42, 0x33, 0x33, 0x30, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x34, \ + 0x24, 0x05, 0x00, 0x24, 0x06, 0x00, 0x25, 0x07, 0x94, 0x26, 0x24, 0x08, 0x00, 0x18, 0x31, 0x7d, 0x30, 0x7b, 0x02, \ + 0x01, 0x03, 0x80, 0x14, 0x62, 0xfa, 0x82, 0x33, 0x59, 0xac, 0xfa, 0xa9, 0x96, 0x3e, 0x1c, 0xfa, 0x14, 0x0a, 0xdd, \ + 0xf5, 0x04, 0xf3, 0x71, 0x60, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, \ + 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x04, 0x47, 0x30, 0x45, 0x02, 0x20, 0x24, 0xe5, \ + 0xd1, 0xf4, 0x7a, 0x7d, 0x7b, 0x0d, 0x20, 0x6a, 0x26, 0xef, 0x69, 0x9b, 0x7c, 0x97, 0x57, 0xb7, 0x2d, 0x46, 0x90, \ + 0x89, 0xde, 0x31, 0x92, 0xe6, 0x78, 0xc7, 0x45, 0xe7, 0xf6, 0x0c, 0x02, 0x21, 0x00, 0xf8, 0xaa, 0x2f, 0xa7, 0x11, \ + 0xfc, 0xb7, 0x9b, 0x97, 0xe3, 0x97, 0xce, 0xda, 0x66, 0x7b, 0xae, 0x46, 0x4e, 0x2b, 0xd3, 0xff, 0xdf, 0xc3, 0xcc, \ + 0xed, 0x7a, 0xa8, 0xca, 0x5f, 0x4c, 0x1a, 0x7c \ + } +#endif + +/** + * CHIP_DEVICE_CONFIG_ENABLE_CHIP_TIME_SERVICE_TIME_SYNC + * + * Enables synchronizing the device's real time clock with a remote CHIP Time service + * using the CHIP Time Sync protocol. + */ +// #define CHIP_DEVICE_CONFIG_ENABLE_CHIP_TIME_SERVICE_TIME_SYNC 1 + +/** + * CHIP_CONFIG_MAX_BINDINGS + * + * Maximum number of simultaneously active bindings per ChipExchangeManager + * 1 (Time Sync) + 2 (Two 1-way subscriptions) + 1 (Software Update) = 4 + * in the worst case. Keeping another 4 as buffer. + */ +#define CHIP_CONFIG_MAX_BINDINGS 6 + +/** + * CHIP_CONFIG_EVENT_LOGGING_WDM_OFFLOAD + * + * Select the ability to offload event logs to any interested subscribers using WDM. + */ +#define CHIP_CONFIG_EVENT_LOGGING_WDM_OFFLOAD 1 + +/** + * CHIP_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS + * + * Enable recording UTC timestamps. + */ +#define CHIP_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS 1 + +/** + * CHIP_DEVICE_CONFIG_EVENT_LOGGING_DEBUG_BUFFER_SIZE + * + * A size, in bytes, of the individual debug event logging buffer. + */ +#define CHIP_DEVICE_CONFIG_EVENT_LOGGING_DEBUG_BUFFER_SIZE (512) + +/** + * CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE + * + * For a development build, set the default importance of events to be logged as Debug. + * Since debug is the lowest importance level, this means all standard, critical, info and + * debug importance level vi events get logged. + */ +#if BUILD_RELEASE +#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Production +#else +#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Debug +#endif // BUILD_RELEASE + +/* Increasing the retransmission interval of the MRP messages after subsequent failures */ +#ifndef CHIP_CONFIG_MRP_LOCAL_ACTIVE_RETRY_INTERVAL +#define CHIP_CONFIG_MRP_LOCAL_ACTIVE_RETRY_INTERVAL (2000_ms32) +#endif diff --git a/examples/lock-app/nxp/k32w/k32w0/third_party/connectedhomeip b/examples/laundry-washer-app/nxp/rt/rw61x/third_party/connectedhomeip similarity index 100% rename from examples/lock-app/nxp/k32w/k32w0/third_party/connectedhomeip rename to examples/laundry-washer-app/nxp/rt/rw61x/third_party/connectedhomeip diff --git a/build_overrides/k32w0_sdk.gni b/examples/laundry-washer-app/nxp/zap/BUILD.gn similarity index 70% rename from build_overrides/k32w0_sdk.gni rename to examples/laundry-washer-app/nxp/zap/BUILD.gn index fa487e9e7fb254..d0a891b637617a 100644 --- a/build_overrides/k32w0_sdk.gni +++ b/examples/laundry-washer-app/nxp/zap/BUILD.gn @@ -1,4 +1,5 @@ # Copyright (c) 2020 Project CHIP Authors +# Copyright 2023 NXP # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,7 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -declare_args() { - # Root directory for K32W SDK build files. - k32w0_sdk_build_root = "//third_party/nxp/k32w0_sdk" +import("//build_overrides/chip.gni") + +import("${chip_root}/src/app/chip_data_model.gni") + +import("${chip_root}/src/platform/device.gni") + +chip_data_model("zap") { + zap_file = "laundry-washer-app.zap" + is_server = true } diff --git a/examples/laundry-washer-app/nxp/zap/laundry-washer-app.matter b/examples/laundry-washer-app/nxp/zap/laundry-washer-app.matter new file mode 100644 index 00000000000000..da2c45a674c038 --- /dev/null +++ b/examples/laundry-washer-app/nxp/zap/laundry-washer-app.matter @@ -0,0 +1,2342 @@ +// This IDL was generated automatically by ZAP. +// It is for view/code review purposes only. + +/** Attributes and commands for putting a device into Identification mode (e.g. flashing a light). */ +cluster Identify = 3 { + revision 4; + + enum EffectIdentifierEnum : enum8 { + kBlink = 0; + kBreathe = 1; + kOkay = 2; + kChannelChange = 11; + kFinishEffect = 254; + kStopEffect = 255; + } + + enum EffectVariantEnum : enum8 { + kDefault = 0; + } + + enum IdentifyTypeEnum : enum8 { + kNone = 0; + kLightOutput = 1; + kVisibleIndicator = 2; + kAudibleBeep = 3; + kDisplay = 4; + kActuator = 5; + } + + attribute int16u identifyTime = 0; + readonly attribute IdentifyTypeEnum identifyType = 1; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct IdentifyRequest { + int16u identifyTime = 0; + } + + request struct TriggerEffectRequest { + EffectIdentifierEnum effectIdentifier = 0; + EffectVariantEnum effectVariant = 1; + } + + /** Command description for Identify */ + command access(invoke: manage) Identify(IdentifyRequest): DefaultSuccess = 0; + /** Command description for TriggerEffect */ + command access(invoke: manage) TriggerEffect(TriggerEffectRequest): DefaultSuccess = 64; +} + +/** Attributes and commands for group configuration and manipulation. */ +cluster Groups = 4 { + revision 4; + + bitmap Feature : bitmap32 { + kGroupNames = 0x1; + } + + bitmap NameSupportBitmap : bitmap8 { + kGroupNames = 0x80; + } + + readonly attribute NameSupportBitmap nameSupport = 0; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct AddGroupRequest { + group_id groupID = 0; + char_string<16> groupName = 1; + } + + response struct AddGroupResponse = 0 { + enum8 status = 0; + group_id groupID = 1; + } + + request struct ViewGroupRequest { + group_id groupID = 0; + } + + response struct ViewGroupResponse = 1 { + enum8 status = 0; + group_id groupID = 1; + char_string<16> groupName = 2; + } + + request struct GetGroupMembershipRequest { + group_id groupList[] = 0; + } + + response struct GetGroupMembershipResponse = 2 { + nullable int8u capacity = 0; + group_id groupList[] = 1; + } + + request struct RemoveGroupRequest { + group_id groupID = 0; + } + + response struct RemoveGroupResponse = 3 { + enum8 status = 0; + group_id groupID = 1; + } + + request struct AddGroupIfIdentifyingRequest { + group_id groupID = 0; + char_string<16> groupName = 1; + } + + /** Command description for AddGroup */ + fabric command access(invoke: manage) AddGroup(AddGroupRequest): AddGroupResponse = 0; + /** Command description for ViewGroup */ + fabric command ViewGroup(ViewGroupRequest): ViewGroupResponse = 1; + /** Command description for GetGroupMembership */ + fabric command GetGroupMembership(GetGroupMembershipRequest): GetGroupMembershipResponse = 2; + /** Command description for RemoveGroup */ + fabric command access(invoke: manage) RemoveGroup(RemoveGroupRequest): RemoveGroupResponse = 3; + /** Command description for RemoveAllGroups */ + fabric command access(invoke: manage) RemoveAllGroups(): DefaultSuccess = 4; + /** Command description for AddGroupIfIdentifying */ + fabric command access(invoke: manage) AddGroupIfIdentifying(AddGroupIfIdentifyingRequest): DefaultSuccess = 5; +} + +/** Attributes and commands for switching devices between 'On' and 'Off' states. */ +cluster OnOff = 6 { + revision 6; + + enum DelayedAllOffEffectVariantEnum : enum8 { + kDelayedOffFastFade = 0; + kNoFade = 1; + kDelayedOffSlowFade = 2; + } + + enum DyingLightEffectVariantEnum : enum8 { + kDyingLightFadeOff = 0; + } + + enum EffectIdentifierEnum : enum8 { + kDelayedAllOff = 0; + kDyingLight = 1; + } + + enum StartUpOnOffEnum : enum8 { + kOff = 0; + kOn = 1; + kToggle = 2; + } + + bitmap Feature : bitmap32 { + kLighting = 0x1; + kDeadFrontBehavior = 0x2; + kOffOnly = 0x4; + } + + bitmap OnOffControlBitmap : bitmap8 { + kAcceptOnlyWhenOn = 0x1; + } + + readonly attribute boolean onOff = 0; + readonly attribute optional boolean globalSceneControl = 16384; + attribute optional int16u onTime = 16385; + attribute optional int16u offWaitTime = 16386; + attribute access(write: manage) optional nullable StartUpOnOffEnum startUpOnOff = 16387; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct OffWithEffectRequest { + EffectIdentifierEnum effectIdentifier = 0; + enum8 effectVariant = 1; + } + + request struct OnWithTimedOffRequest { + OnOffControlBitmap onOffControl = 0; + int16u onTime = 1; + int16u offWaitTime = 2; + } + + /** On receipt of this command, a device SHALL enter its ‘Off’ state. This state is device dependent, but it is recommended that it is used for power off or similar functions. On receipt of the Off command, the OnTime attribute SHALL be set to 0. */ + command Off(): DefaultSuccess = 0; + /** On receipt of this command, a device SHALL enter its ‘On’ state. This state is device dependent, but it is recommended that it is used for power on or similar functions. On receipt of the On command, if the value of the OnTime attribute is equal to 0, the device SHALL set the OffWaitTime attribute to 0. */ + command On(): DefaultSuccess = 1; + /** On receipt of this command, if a device is in its ‘Off’ state it SHALL enter its ‘On’ state. Otherwise, if it is in its ‘On’ state it SHALL enter its ‘Off’ state. On receipt of the Toggle command, if the value of the OnOff attribute is equal to FALSE and if the value of the OnTime attribute is equal to 0, the device SHALL set the OffWaitTime attribute to 0. If the value of the OnOff attribute is equal to TRUE, the OnTime attribute SHALL be set to 0. */ + command Toggle(): DefaultSuccess = 2; + /** The OffWithEffect command allows devices to be turned off using enhanced ways of fading. */ + command OffWithEffect(OffWithEffectRequest): DefaultSuccess = 64; + /** The OnWithRecallGlobalScene command allows the recall of the settings when the device was turned off. */ + command OnWithRecallGlobalScene(): DefaultSuccess = 65; + /** The OnWithTimedOff command allows devices to be turned on for a specific duration with a guarded off duration so that SHOULD the device be subsequently switched off, further OnWithTimedOff commands, received during this time, are prevented from turning the devices back on. */ + command OnWithTimedOff(OnWithTimedOffRequest): DefaultSuccess = 66; +} + +/** The Descriptor Cluster is meant to replace the support from the Zigbee Device Object (ZDO) for describing a node, its endpoints and clusters. */ +cluster Descriptor = 29 { + revision 2; + + bitmap Feature : bitmap32 { + kTagList = 0x1; + } + + struct DeviceTypeStruct { + devtype_id deviceType = 0; + int16u revision = 1; + } + + struct SemanticTagStruct { + nullable vendor_id mfgCode = 0; + enum8 namespaceID = 1; + enum8 tag = 2; + optional nullable char_string label = 3; + } + + readonly attribute DeviceTypeStruct deviceTypeList[] = 0; + readonly attribute cluster_id serverList[] = 1; + readonly attribute cluster_id clientList[] = 2; + readonly attribute endpoint_no partsList[] = 3; + readonly attribute optional SemanticTagStruct tagList[] = 4; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + +/** The Binding Cluster is meant to replace the support from the Zigbee Device Object (ZDO) for supporting the binding table. */ +cluster Binding = 30 { + revision 1; // NOTE: Default/not specifically set + + fabric_scoped struct TargetStruct { + optional node_id node = 1; + optional group_id group = 2; + optional endpoint_no endpoint = 3; + optional cluster_id cluster = 4; + fabric_idx fabricIndex = 254; + } + + attribute access(write: manage) TargetStruct binding[] = 0; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + +/** The Access Control Cluster exposes a data model view of a + Node's Access Control List (ACL), which codifies the rules used to manage + and enforce Access Control for the Node's endpoints and their associated + cluster instances. */ +cluster AccessControl = 31 { + revision 1; // NOTE: Default/not specifically set + + enum AccessControlEntryAuthModeEnum : enum8 { + kPASE = 1; + kCASE = 2; + kGroup = 3; + } + + enum AccessControlEntryPrivilegeEnum : enum8 { + kView = 1; + kProxyView = 2; + kOperate = 3; + kManage = 4; + kAdminister = 5; + } + + enum ChangeTypeEnum : enum8 { + kChanged = 0; + kAdded = 1; + kRemoved = 2; + } + + struct AccessControlTargetStruct { + nullable cluster_id cluster = 0; + nullable endpoint_no endpoint = 1; + nullable devtype_id deviceType = 2; + } + + fabric_scoped struct AccessControlEntryStruct { + fabric_sensitive AccessControlEntryPrivilegeEnum privilege = 1; + fabric_sensitive AccessControlEntryAuthModeEnum authMode = 2; + nullable fabric_sensitive int64u subjects[] = 3; + nullable fabric_sensitive AccessControlTargetStruct targets[] = 4; + fabric_idx fabricIndex = 254; + } + + fabric_scoped struct AccessControlExtensionStruct { + fabric_sensitive octet_string<128> data = 1; + fabric_idx fabricIndex = 254; + } + + fabric_sensitive info event access(read: administer) AccessControlEntryChanged = 0 { + nullable node_id adminNodeID = 1; + nullable int16u adminPasscodeID = 2; + ChangeTypeEnum changeType = 3; + nullable AccessControlEntryStruct latestValue = 4; + fabric_idx fabricIndex = 254; + } + + fabric_sensitive info event access(read: administer) AccessControlExtensionChanged = 1 { + nullable node_id adminNodeID = 1; + nullable int16u adminPasscodeID = 2; + ChangeTypeEnum changeType = 3; + nullable AccessControlExtensionStruct latestValue = 4; + fabric_idx fabricIndex = 254; + } + + attribute access(read: administer, write: administer) AccessControlEntryStruct acl[] = 0; + attribute access(read: administer, write: administer) optional AccessControlExtensionStruct extension[] = 1; + readonly attribute int16u subjectsPerAccessControlEntry = 2; + readonly attribute int16u targetsPerAccessControlEntry = 3; + readonly attribute int16u accessControlEntriesPerFabric = 4; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + +/** This cluster provides attributes and events for determining basic information about Nodes, which supports both + Commissioning and operational determination of Node characteristics, such as Vendor ID, Product ID and serial number, + which apply to the whole Node. Also allows setting user device information such as location. */ +cluster BasicInformation = 40 { + revision 3; + + enum ColorEnum : enum8 { + kBlack = 0; + kNavy = 1; + kGreen = 2; + kTeal = 3; + kMaroon = 4; + kPurple = 5; + kOlive = 6; + kGray = 7; + kBlue = 8; + kLime = 9; + kAqua = 10; + kRed = 11; + kFuchsia = 12; + kYellow = 13; + kWhite = 14; + kNickel = 15; + kChrome = 16; + kBrass = 17; + kCopper = 18; + kSilver = 19; + kGold = 20; + } + + enum ProductFinishEnum : enum8 { + kOther = 0; + kMatte = 1; + kSatin = 2; + kPolished = 3; + kRugged = 4; + kFabric = 5; + } + + struct CapabilityMinimaStruct { + int16u caseSessionsPerFabric = 0; + int16u subscriptionsPerFabric = 1; + } + + struct ProductAppearanceStruct { + ProductFinishEnum finish = 0; + nullable ColorEnum primaryColor = 1; + } + + critical event StartUp = 0 { + int32u softwareVersion = 0; + } + + critical event ShutDown = 1 { + } + + info event Leave = 2 { + fabric_idx fabricIndex = 0; + } + + info event ReachableChanged = 3 { + boolean reachableNewValue = 0; + } + + readonly attribute int16u dataModelRevision = 0; + readonly attribute char_string<32> vendorName = 1; + readonly attribute vendor_id vendorID = 2; + readonly attribute char_string<32> productName = 3; + readonly attribute int16u productID = 4; + attribute access(write: manage) char_string<32> nodeLabel = 5; + attribute access(write: administer) char_string<2> location = 6; + readonly attribute int16u hardwareVersion = 7; + readonly attribute char_string<64> hardwareVersionString = 8; + readonly attribute int32u softwareVersion = 9; + readonly attribute char_string<64> softwareVersionString = 10; + readonly attribute optional char_string<16> manufacturingDate = 11; + readonly attribute optional char_string<32> partNumber = 12; + readonly attribute optional long_char_string<256> productURL = 13; + readonly attribute optional char_string<64> productLabel = 14; + readonly attribute optional char_string<32> serialNumber = 15; + attribute access(write: manage) optional boolean localConfigDisabled = 16; + readonly attribute optional boolean reachable = 17; + readonly attribute optional char_string<32> uniqueID = 18; + readonly attribute CapabilityMinimaStruct capabilityMinima = 19; + readonly attribute optional ProductAppearanceStruct productAppearance = 20; + readonly attribute int32u specificationVersion = 21; + readonly attribute int16u maxPathsPerInvoke = 22; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + command MfgSpecificPing(): DefaultSuccess = 0; +} + +/** Provides an interface for providing OTA software updates */ +cluster OtaSoftwareUpdateProvider = 41 { + revision 1; // NOTE: Default/not specifically set + + enum ApplyUpdateActionEnum : enum8 { + kProceed = 0; + kAwaitNextAction = 1; + kDiscontinue = 2; + } + + enum DownloadProtocolEnum : enum8 { + kBDXSynchronous = 0; + kBDXAsynchronous = 1; + kHTTPS = 2; + kVendorSpecific = 3; + } + + enum StatusEnum : enum8 { + kUpdateAvailable = 0; + kBusy = 1; + kNotAvailable = 2; + kDownloadProtocolNotSupported = 3; + } + + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct QueryImageRequest { + vendor_id vendorID = 0; + int16u productID = 1; + int32u softwareVersion = 2; + DownloadProtocolEnum protocolsSupported[] = 3; + optional int16u hardwareVersion = 4; + optional char_string<2> location = 5; + optional boolean requestorCanConsent = 6; + optional octet_string<512> metadataForProvider = 7; + } + + response struct QueryImageResponse = 1 { + StatusEnum status = 0; + optional int32u delayedActionTime = 1; + optional char_string<256> imageURI = 2; + optional int32u softwareVersion = 3; + optional char_string<64> softwareVersionString = 4; + optional octet_string<32> updateToken = 5; + optional boolean userConsentNeeded = 6; + optional octet_string<512> metadataForRequestor = 7; + } + + request struct ApplyUpdateRequestRequest { + octet_string<32> updateToken = 0; + int32u newVersion = 1; + } + + response struct ApplyUpdateResponse = 3 { + ApplyUpdateActionEnum action = 0; + int32u delayedActionTime = 1; + } + + request struct NotifyUpdateAppliedRequest { + octet_string<32> updateToken = 0; + int32u softwareVersion = 1; + } + + /** Determine availability of a new Software Image */ + command QueryImage(QueryImageRequest): QueryImageResponse = 0; + /** Determine next action to take for a downloaded Software Image */ + command ApplyUpdateRequest(ApplyUpdateRequestRequest): ApplyUpdateResponse = 2; + /** Notify OTA Provider that an update was applied */ + command NotifyUpdateApplied(NotifyUpdateAppliedRequest): DefaultSuccess = 4; +} + +/** Provides an interface for downloading and applying OTA software updates */ +cluster OtaSoftwareUpdateRequestor = 42 { + revision 1; // NOTE: Default/not specifically set + + enum AnnouncementReasonEnum : enum8 { + kSimpleAnnouncement = 0; + kUpdateAvailable = 1; + kUrgentUpdateAvailable = 2; + } + + enum ChangeReasonEnum : enum8 { + kUnknown = 0; + kSuccess = 1; + kFailure = 2; + kTimeOut = 3; + kDelayByProvider = 4; + } + + enum UpdateStateEnum : enum8 { + kUnknown = 0; + kIdle = 1; + kQuerying = 2; + kDelayedOnQuery = 3; + kDownloading = 4; + kApplying = 5; + kDelayedOnApply = 6; + kRollingBack = 7; + kDelayedOnUserConsent = 8; + } + + fabric_scoped struct ProviderLocation { + node_id providerNodeID = 1; + endpoint_no endpoint = 2; + fabric_idx fabricIndex = 254; + } + + info event StateTransition = 0 { + UpdateStateEnum previousState = 0; + UpdateStateEnum newState = 1; + ChangeReasonEnum reason = 2; + nullable int32u targetSoftwareVersion = 3; + } + + critical event VersionApplied = 1 { + int32u softwareVersion = 0; + int16u productID = 1; + } + + info event DownloadError = 2 { + int32u softwareVersion = 0; + int64u bytesDownloaded = 1; + nullable int8u progressPercent = 2; + nullable int64s platformCode = 3; + } + + attribute access(write: administer) ProviderLocation defaultOTAProviders[] = 0; + readonly attribute boolean updatePossible = 1; + readonly attribute UpdateStateEnum updateState = 2; + readonly attribute nullable int8u updateStateProgress = 3; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct AnnounceOTAProviderRequest { + node_id providerNodeID = 0; + vendor_id vendorID = 1; + AnnouncementReasonEnum announcementReason = 2; + optional octet_string<512> metadataForNode = 3; + endpoint_no endpoint = 4; + } + + /** Announce the presence of an OTA Provider */ + command AnnounceOTAProvider(AnnounceOTAProviderRequest): DefaultSuccess = 0; +} + +/** Nodes should be expected to be deployed to any and all regions of the world. These global regions + may have differing common languages, units of measurements, and numerical formatting + standards. As such, Nodes that visually or audibly convey information need a mechanism by which + they can be configured to use a user’s preferred language, units, etc */ +cluster LocalizationConfiguration = 43 { + revision 1; // NOTE: Default/not specifically set + + attribute access(write: manage) char_string<35> activeLocale = 0; + readonly attribute char_string supportedLocales[] = 1; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + +/** Nodes should be expected to be deployed to any and all regions of the world. These global regions + may have differing preferences for how dates and times are conveyed. As such, Nodes that visually + or audibly convey time information need a mechanism by which they can be configured to use a + user’s preferred format. */ +cluster TimeFormatLocalization = 44 { + revision 1; // NOTE: Default/not specifically set + + enum CalendarTypeEnum : enum8 { + kBuddhist = 0; + kChinese = 1; + kCoptic = 2; + kEthiopian = 3; + kGregorian = 4; + kHebrew = 5; + kIndian = 6; + kIslamic = 7; + kJapanese = 8; + kKorean = 9; + kPersian = 10; + kTaiwanese = 11; + kUseActiveLocale = 255; + } + + enum HourFormatEnum : enum8 { + k12hr = 0; + k24hr = 1; + kUseActiveLocale = 255; + } + + bitmap Feature : bitmap32 { + kCalendarFormat = 0x1; + } + + attribute access(write: manage) HourFormatEnum hourFormat = 0; + attribute access(write: manage) optional CalendarTypeEnum activeCalendarType = 1; + readonly attribute optional CalendarTypeEnum supportedCalendarTypes[] = 2; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + +/** Nodes should be expected to be deployed to any and all regions of the world. These global regions + may have differing preferences for the units in which values are conveyed in communication to a + user. As such, Nodes that visually or audibly convey measurable values to the user need a + mechanism by which they can be configured to use a user’s preferred unit. */ +cluster UnitLocalization = 45 { + revision 1; + + enum TempUnitEnum : enum8 { + kFahrenheit = 0; + kCelsius = 1; + kKelvin = 2; + } + + bitmap Feature : bitmap32 { + kTemperatureUnit = 0x1; + } + + attribute access(write: manage) optional TempUnitEnum temperatureUnit = 0; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + +/** This cluster is used to describe the configuration and capabilities of a physical power source that provides power to the Node. */ +cluster PowerSource = 47 { + revision 1; // NOTE: Default/not specifically set + + enum BatApprovedChemistryEnum : enum16 { + kUnspecified = 0; + kAlkaline = 1; + kLithiumCarbonFluoride = 2; + kLithiumChromiumOxide = 3; + kLithiumCopperOxide = 4; + kLithiumIronDisulfide = 5; + kLithiumManganeseDioxide = 6; + kLithiumThionylChloride = 7; + kMagnesium = 8; + kMercuryOxide = 9; + kNickelOxyhydride = 10; + kSilverOxide = 11; + kZincAir = 12; + kZincCarbon = 13; + kZincChloride = 14; + kZincManganeseDioxide = 15; + kLeadAcid = 16; + kLithiumCobaltOxide = 17; + kLithiumIon = 18; + kLithiumIonPolymer = 19; + kLithiumIronPhosphate = 20; + kLithiumSulfur = 21; + kLithiumTitanate = 22; + kNickelCadmium = 23; + kNickelHydrogen = 24; + kNickelIron = 25; + kNickelMetalHydride = 26; + kNickelZinc = 27; + kSilverZinc = 28; + kSodiumIon = 29; + kSodiumSulfur = 30; + kZincBromide = 31; + kZincCerium = 32; + } + + enum BatChargeFaultEnum : enum8 { + kUnspecified = 0; + kAmbientTooHot = 1; + kAmbientTooCold = 2; + kBatteryTooHot = 3; + kBatteryTooCold = 4; + kBatteryAbsent = 5; + kBatteryOverVoltage = 6; + kBatteryUnderVoltage = 7; + kChargerOverVoltage = 8; + kChargerUnderVoltage = 9; + kSafetyTimeout = 10; + } + + enum BatChargeLevelEnum : enum8 { + kOK = 0; + kWarning = 1; + kCritical = 2; + } + + enum BatChargeStateEnum : enum8 { + kUnknown = 0; + kIsCharging = 1; + kIsAtFullCharge = 2; + kIsNotCharging = 3; + } + + enum BatCommonDesignationEnum : enum16 { + kUnspecified = 0; + kAAA = 1; + kAA = 2; + kC = 3; + kD = 4; + k4v5 = 5; + k6v0 = 6; + k9v0 = 7; + k12AA = 8; + kAAAA = 9; + kA = 10; + kB = 11; + kF = 12; + kN = 13; + kNo6 = 14; + kSubC = 15; + kA23 = 16; + kA27 = 17; + kBA5800 = 18; + kDuplex = 19; + k4SR44 = 20; + k523 = 21; + k531 = 22; + k15v0 = 23; + k22v5 = 24; + k30v0 = 25; + k45v0 = 26; + k67v5 = 27; + kJ = 28; + kCR123A = 29; + kCR2 = 30; + k2CR5 = 31; + kCRP2 = 32; + kCRV3 = 33; + kSR41 = 34; + kSR43 = 35; + kSR44 = 36; + kSR45 = 37; + kSR48 = 38; + kSR54 = 39; + kSR55 = 40; + kSR57 = 41; + kSR58 = 42; + kSR59 = 43; + kSR60 = 44; + kSR63 = 45; + kSR64 = 46; + kSR65 = 47; + kSR66 = 48; + kSR67 = 49; + kSR68 = 50; + kSR69 = 51; + kSR516 = 52; + kSR731 = 53; + kSR712 = 54; + kLR932 = 55; + kA5 = 56; + kA10 = 57; + kA13 = 58; + kA312 = 59; + kA675 = 60; + kAC41E = 61; + k10180 = 62; + k10280 = 63; + k10440 = 64; + k14250 = 65; + k14430 = 66; + k14500 = 67; + k14650 = 68; + k15270 = 69; + k16340 = 70; + kRCR123A = 71; + k17500 = 72; + k17670 = 73; + k18350 = 74; + k18500 = 75; + k18650 = 76; + k19670 = 77; + k25500 = 78; + k26650 = 79; + k32600 = 80; + } + + enum BatFaultEnum : enum8 { + kUnspecified = 0; + kOverTemp = 1; + kUnderTemp = 2; + } + + enum BatReplaceabilityEnum : enum8 { + kUnspecified = 0; + kNotReplaceable = 1; + kUserReplaceable = 2; + kFactoryReplaceable = 3; + } + + enum PowerSourceStatusEnum : enum8 { + kUnspecified = 0; + kActive = 1; + kStandby = 2; + kUnavailable = 3; + } + + enum WiredCurrentTypeEnum : enum8 { + kAC = 0; + kDC = 1; + } + + enum WiredFaultEnum : enum8 { + kUnspecified = 0; + kOverVoltage = 1; + kUnderVoltage = 2; + } + + bitmap Feature : bitmap32 { + kWired = 0x1; + kBattery = 0x2; + kRechargeable = 0x4; + kReplaceable = 0x8; + } + + struct BatChargeFaultChangeType { + BatChargeFaultEnum current[] = 0; + BatChargeFaultEnum previous[] = 1; + } + + struct BatFaultChangeType { + BatFaultEnum current[] = 0; + BatFaultEnum previous[] = 1; + } + + struct WiredFaultChangeType { + WiredFaultEnum current[] = 0; + WiredFaultEnum previous[] = 1; + } + + info event WiredFaultChange = 0 { + WiredFaultEnum current[] = 0; + WiredFaultEnum previous[] = 1; + } + + info event BatFaultChange = 1 { + BatFaultEnum current[] = 0; + BatFaultEnum previous[] = 1; + } + + info event BatChargeFaultChange = 2 { + BatChargeFaultEnum current[] = 0; + BatChargeFaultEnum previous[] = 1; + } + + readonly attribute PowerSourceStatusEnum status = 0; + readonly attribute int8u order = 1; + readonly attribute char_string<60> description = 2; + readonly attribute optional nullable int32u wiredAssessedInputVoltage = 3; + readonly attribute optional nullable int16u wiredAssessedInputFrequency = 4; + readonly attribute optional WiredCurrentTypeEnum wiredCurrentType = 5; + readonly attribute optional nullable int32u wiredAssessedCurrent = 6; + readonly attribute optional int32u wiredNominalVoltage = 7; + readonly attribute optional int32u wiredMaximumCurrent = 8; + readonly attribute optional boolean wiredPresent = 9; + readonly attribute optional WiredFaultEnum activeWiredFaults[] = 10; + readonly attribute optional nullable int32u batVoltage = 11; + readonly attribute optional nullable int8u batPercentRemaining = 12; + readonly attribute optional nullable int32u batTimeRemaining = 13; + readonly attribute optional BatChargeLevelEnum batChargeLevel = 14; + readonly attribute optional boolean batReplacementNeeded = 15; + readonly attribute optional BatReplaceabilityEnum batReplaceability = 16; + readonly attribute optional boolean batPresent = 17; + readonly attribute optional BatFaultEnum activeBatFaults[] = 18; + readonly attribute optional char_string<60> batReplacementDescription = 19; + readonly attribute optional BatCommonDesignationEnum batCommonDesignation = 20; + readonly attribute optional char_string<20> batANSIDesignation = 21; + readonly attribute optional char_string<20> batIECDesignation = 22; + readonly attribute optional BatApprovedChemistryEnum batApprovedChemistry = 23; + readonly attribute optional int32u batCapacity = 24; + readonly attribute optional int8u batQuantity = 25; + readonly attribute optional BatChargeStateEnum batChargeState = 26; + readonly attribute optional nullable int32u batTimeToFullCharge = 27; + readonly attribute optional boolean batFunctionalWhileCharging = 28; + readonly attribute optional nullable int32u batChargingCurrent = 29; + readonly attribute optional BatChargeFaultEnum activeBatChargeFaults[] = 30; + readonly attribute endpoint_no endpointList[] = 31; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + +/** This cluster is used to manage global aspects of the Commissioning flow. */ +cluster GeneralCommissioning = 48 { + revision 1; // NOTE: Default/not specifically set + + enum CommissioningErrorEnum : enum8 { + kOK = 0; + kValueOutsideRange = 1; + kInvalidAuthentication = 2; + kNoFailSafe = 3; + kBusyWithOtherAdmin = 4; + } + + enum RegulatoryLocationTypeEnum : enum8 { + kIndoor = 0; + kOutdoor = 1; + kIndoorOutdoor = 2; + } + + struct BasicCommissioningInfo { + int16u failSafeExpiryLengthSeconds = 0; + int16u maxCumulativeFailsafeSeconds = 1; + } + + attribute access(write: administer) int64u breadcrumb = 0; + readonly attribute BasicCommissioningInfo basicCommissioningInfo = 1; + readonly attribute RegulatoryLocationTypeEnum regulatoryConfig = 2; + readonly attribute RegulatoryLocationTypeEnum locationCapability = 3; + readonly attribute boolean supportsConcurrentConnection = 4; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct ArmFailSafeRequest { + int16u expiryLengthSeconds = 0; + int64u breadcrumb = 1; + } + + response struct ArmFailSafeResponse = 1 { + CommissioningErrorEnum errorCode = 0; + char_string<128> debugText = 1; + } + + request struct SetRegulatoryConfigRequest { + RegulatoryLocationTypeEnum newRegulatoryConfig = 0; + char_string<2> countryCode = 1; + int64u breadcrumb = 2; + } + + response struct SetRegulatoryConfigResponse = 3 { + CommissioningErrorEnum errorCode = 0; + char_string debugText = 1; + } + + response struct CommissioningCompleteResponse = 5 { + CommissioningErrorEnum errorCode = 0; + char_string debugText = 1; + } + + /** Arm the persistent fail-safe timer with an expiry time of now + ExpiryLengthSeconds using device clock */ + command access(invoke: administer) ArmFailSafe(ArmFailSafeRequest): ArmFailSafeResponse = 0; + /** Set the regulatory configuration to be used during commissioning */ + command access(invoke: administer) SetRegulatoryConfig(SetRegulatoryConfigRequest): SetRegulatoryConfigResponse = 2; + /** Signals the Server that the Client has successfully completed all steps of Commissioning/Recofiguration needed during fail-safe period. */ + fabric command access(invoke: administer) CommissioningComplete(): CommissioningCompleteResponse = 4; +} + +/** Functionality to configure, enable, disable network credentials and access on a Matter device. */ +cluster NetworkCommissioning = 49 { + revision 1; // NOTE: Default/not specifically set + + enum NetworkCommissioningStatusEnum : enum8 { + kSuccess = 0; + kOutOfRange = 1; + kBoundsExceeded = 2; + kNetworkIDNotFound = 3; + kDuplicateNetworkID = 4; + kNetworkNotFound = 5; + kRegulatoryError = 6; + kAuthFailure = 7; + kUnsupportedSecurity = 8; + kOtherConnectionFailure = 9; + kIPV6Failed = 10; + kIPBindFailed = 11; + kUnknownError = 12; + } + + enum WiFiBandEnum : enum8 { + k2G4 = 0; + k3G65 = 1; + k5G = 2; + k6G = 3; + k60G = 4; + k1G = 5; + } + + bitmap Feature : bitmap32 { + kWiFiNetworkInterface = 0x1; + kThreadNetworkInterface = 0x2; + kEthernetNetworkInterface = 0x4; + kPerDeviceCredentials = 0x8; + } + + bitmap ThreadCapabilitiesBitmap : bitmap16 { + kIsBorderRouterCapable = 0x1; + kIsRouterCapable = 0x2; + kIsSleepyEndDeviceCapable = 0x4; + kIsFullThreadDevice = 0x8; + kIsSynchronizedSleepyEndDeviceCapable = 0x10; + } + + bitmap WiFiSecurityBitmap : bitmap8 { + kUnencrypted = 0x1; + kWEP = 0x2; + kWPAPersonal = 0x4; + kWPA2Personal = 0x8; + kWPA3Personal = 0x10; + kWPA3MatterPDC = 0x20; + } + + struct NetworkInfoStruct { + octet_string<32> networkID = 0; + boolean connected = 1; + optional nullable octet_string<20> networkIdentifier = 2; + optional nullable octet_string<20> clientIdentifier = 3; + } + + struct ThreadInterfaceScanResultStruct { + int16u panId = 0; + int64u extendedPanId = 1; + char_string<16> networkName = 2; + int16u channel = 3; + int8u version = 4; + octet_string<8> extendedAddress = 5; + int8s rssi = 6; + int8u lqi = 7; + } + + struct WiFiInterfaceScanResultStruct { + WiFiSecurityBitmap security = 0; + octet_string<32> ssid = 1; + octet_string<6> bssid = 2; + int16u channel = 3; + WiFiBandEnum wiFiBand = 4; + int8s rssi = 5; + } + + readonly attribute access(read: administer) int8u maxNetworks = 0; + readonly attribute access(read: administer) NetworkInfoStruct networks[] = 1; + readonly attribute optional int8u scanMaxTimeSeconds = 2; + readonly attribute optional int8u connectMaxTimeSeconds = 3; + attribute access(write: administer) boolean interfaceEnabled = 4; + readonly attribute access(read: administer) nullable NetworkCommissioningStatusEnum lastNetworkingStatus = 5; + readonly attribute access(read: administer) nullable octet_string<32> lastNetworkID = 6; + readonly attribute access(read: administer) nullable int32s lastConnectErrorValue = 7; + readonly attribute optional WiFiBandEnum supportedWiFiBands[] = 8; + readonly attribute optional ThreadCapabilitiesBitmap supportedThreadFeatures = 9; + readonly attribute optional int16u threadVersion = 10; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct ScanNetworksRequest { + optional nullable octet_string<32> ssid = 0; + optional int64u breadcrumb = 1; + } + + response struct ScanNetworksResponse = 1 { + NetworkCommissioningStatusEnum networkingStatus = 0; + optional char_string debugText = 1; + optional WiFiInterfaceScanResultStruct wiFiScanResults[] = 2; + optional ThreadInterfaceScanResultStruct threadScanResults[] = 3; + } + + request struct AddOrUpdateWiFiNetworkRequest { + octet_string<32> ssid = 0; + octet_string<64> credentials = 1; + optional int64u breadcrumb = 2; + optional octet_string<140> networkIdentity = 3; + optional octet_string<20> clientIdentifier = 4; + optional octet_string<32> possessionNonce = 5; + } + + request struct AddOrUpdateThreadNetworkRequest { + octet_string<254> operationalDataset = 0; + optional int64u breadcrumb = 1; + } + + request struct RemoveNetworkRequest { + octet_string<32> networkID = 0; + optional int64u breadcrumb = 1; + } + + response struct NetworkConfigResponse = 5 { + NetworkCommissioningStatusEnum networkingStatus = 0; + optional char_string<512> debugText = 1; + optional int8u networkIndex = 2; + optional octet_string<140> clientIdentity = 3; + optional octet_string<64> possessionSignature = 4; + } + + request struct ConnectNetworkRequest { + octet_string<32> networkID = 0; + optional int64u breadcrumb = 1; + } + + response struct ConnectNetworkResponse = 7 { + NetworkCommissioningStatusEnum networkingStatus = 0; + optional char_string debugText = 1; + nullable int32s errorValue = 2; + } + + request struct ReorderNetworkRequest { + octet_string<32> networkID = 0; + int8u networkIndex = 1; + optional int64u breadcrumb = 2; + } + + request struct QueryIdentityRequest { + octet_string<20> keyIdentifier = 0; + optional octet_string<32> possessionNonce = 1; + } + + response struct QueryIdentityResponse = 10 { + octet_string<140> identity = 0; + optional octet_string<64> possessionSignature = 1; + } + + /** Detemine the set of networks the device sees as available. */ + command access(invoke: administer) ScanNetworks(ScanNetworksRequest): ScanNetworksResponse = 0; + /** Add or update the credentials for a given Wi-Fi network. */ + command access(invoke: administer) AddOrUpdateWiFiNetwork(AddOrUpdateWiFiNetworkRequest): NetworkConfigResponse = 2; + /** Add or update the credentials for a given Thread network. */ + command access(invoke: administer) AddOrUpdateThreadNetwork(AddOrUpdateThreadNetworkRequest): NetworkConfigResponse = 3; + /** Remove the definition of a given network (including its credentials). */ + command access(invoke: administer) RemoveNetwork(RemoveNetworkRequest): NetworkConfigResponse = 4; + /** Connect to the specified network, using previously-defined credentials. */ + command access(invoke: administer) ConnectNetwork(ConnectNetworkRequest): ConnectNetworkResponse = 6; + /** Modify the order in which networks will be presented in the Networks attribute. */ + command access(invoke: administer) ReorderNetwork(ReorderNetworkRequest): NetworkConfigResponse = 8; + /** Retrieve details about and optionally proof of possession of a network client identity. */ + command access(invoke: administer) QueryIdentity(QueryIdentityRequest): QueryIdentityResponse = 9; +} + +/** The General Diagnostics Cluster, along with other diagnostics clusters, provide a means to acquire standardized diagnostics metrics that MAY be used by a Node to assist a user or Administrative Node in diagnosing potential problems. */ +cluster GeneralDiagnostics = 51 { + revision 2; + + enum BootReasonEnum : enum8 { + kUnspecified = 0; + kPowerOnReboot = 1; + kBrownOutReset = 2; + kSoftwareWatchdogReset = 3; + kHardwareWatchdogReset = 4; + kSoftwareUpdateCompleted = 5; + kSoftwareReset = 6; + } + + enum HardwareFaultEnum : enum8 { + kUnspecified = 0; + kRadio = 1; + kSensor = 2; + kResettableOverTemp = 3; + kNonResettableOverTemp = 4; + kPowerSource = 5; + kVisualDisplayFault = 6; + kAudioOutputFault = 7; + kUserInterfaceFault = 8; + kNonVolatileMemoryError = 9; + kTamperDetected = 10; + } + + enum InterfaceTypeEnum : enum8 { + kUnspecified = 0; + kWiFi = 1; + kEthernet = 2; + kCellular = 3; + kThread = 4; + } + + enum NetworkFaultEnum : enum8 { + kUnspecified = 0; + kHardwareFailure = 1; + kNetworkJammed = 2; + kConnectionFailed = 3; + } + + enum RadioFaultEnum : enum8 { + kUnspecified = 0; + kWiFiFault = 1; + kCellularFault = 2; + kThreadFault = 3; + kNFCFault = 4; + kBLEFault = 5; + kEthernetFault = 6; + } + + bitmap Feature : bitmap32 { + kDataModelTest = 0x1; + } + + struct NetworkInterface { + char_string<32> name = 0; + boolean isOperational = 1; + nullable boolean offPremiseServicesReachableIPv4 = 2; + nullable boolean offPremiseServicesReachableIPv6 = 3; + octet_string<8> hardwareAddress = 4; + octet_string IPv4Addresses[] = 5; + octet_string IPv6Addresses[] = 6; + InterfaceTypeEnum type = 7; + } + + critical event HardwareFaultChange = 0 { + HardwareFaultEnum current[] = 0; + HardwareFaultEnum previous[] = 1; + } + + critical event RadioFaultChange = 1 { + RadioFaultEnum current[] = 0; + RadioFaultEnum previous[] = 1; + } + + critical event NetworkFaultChange = 2 { + NetworkFaultEnum current[] = 0; + NetworkFaultEnum previous[] = 1; + } + + critical event BootReason = 3 { + BootReasonEnum bootReason = 0; + } + + readonly attribute NetworkInterface networkInterfaces[] = 0; + readonly attribute int16u rebootCount = 1; + readonly attribute optional int64u upTime = 2; + readonly attribute optional int32u totalOperationalHours = 3; + readonly attribute optional BootReasonEnum bootReason = 4; + readonly attribute optional HardwareFaultEnum activeHardwareFaults[] = 5; + readonly attribute optional RadioFaultEnum activeRadioFaults[] = 6; + readonly attribute optional NetworkFaultEnum activeNetworkFaults[] = 7; + readonly attribute boolean testEventTriggersEnabled = 8; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct TestEventTriggerRequest { + octet_string<16> enableKey = 0; + int64u eventTrigger = 1; + } + + response struct TimeSnapshotResponse = 2 { + systime_ms systemTimeMs = 0; + nullable posix_ms posixTimeMs = 1; + } + + request struct PayloadTestRequestRequest { + octet_string<16> enableKey = 0; + int8u value = 1; + int16u count = 2; + } + + response struct PayloadTestResponse = 4 { + octet_string payload = 0; + } + + /** Provide a means for certification tests to trigger some test-plan-specific events */ + command access(invoke: manage) TestEventTrigger(TestEventTriggerRequest): DefaultSuccess = 0; + /** Take a snapshot of system time and epoch time. */ + command TimeSnapshot(): TimeSnapshotResponse = 1; + /** Request a variable length payload response. */ + command PayloadTestRequest(PayloadTestRequestRequest): PayloadTestResponse = 3; +} + +/** The Wi-Fi Network Diagnostics Cluster provides a means to acquire standardized diagnostics metrics that MAY be used by a Node to assist a user or Administrative Node in diagnosing potential problems. */ +cluster WiFiNetworkDiagnostics = 54 { + revision 1; // NOTE: Default/not specifically set + + enum AssociationFailureCauseEnum : enum8 { + kUnknown = 0; + kAssociationFailed = 1; + kAuthenticationFailed = 2; + kSsidNotFound = 3; + } + + enum ConnectionStatusEnum : enum8 { + kConnected = 0; + kNotConnected = 1; + } + + enum SecurityTypeEnum : enum8 { + kUnspecified = 0; + kNone = 1; + kWEP = 2; + kWPA = 3; + kWPA2 = 4; + kWPA3 = 5; + } + + enum WiFiVersionEnum : enum8 { + kA = 0; + kB = 1; + kG = 2; + kN = 3; + kAc = 4; + kAx = 5; + kAh = 6; + } + + bitmap Feature : bitmap32 { + kPacketCounts = 0x1; + kErrorCounts = 0x2; + } + + info event Disconnection = 0 { + int16u reasonCode = 0; + } + + info event AssociationFailure = 1 { + AssociationFailureCauseEnum associationFailureCause = 0; + int16u status = 1; + } + + info event ConnectionStatus = 2 { + ConnectionStatusEnum connectionStatus = 0; + } + + readonly attribute nullable octet_string<6> bssid = 0; + readonly attribute nullable SecurityTypeEnum securityType = 1; + readonly attribute nullable WiFiVersionEnum wiFiVersion = 2; + readonly attribute nullable int16u channelNumber = 3; + readonly attribute nullable int8s rssi = 4; + readonly attribute optional nullable int32u beaconLostCount = 5; + readonly attribute optional nullable int32u beaconRxCount = 6; + readonly attribute optional nullable int32u packetMulticastRxCount = 7; + readonly attribute optional nullable int32u packetMulticastTxCount = 8; + readonly attribute optional nullable int32u packetUnicastRxCount = 9; + readonly attribute optional nullable int32u packetUnicastTxCount = 10; + readonly attribute optional nullable int64u currentMaxRate = 11; + readonly attribute optional nullable int64u overrunCount = 12; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + /** Reception of this command SHALL reset the Breacon and Packet related count attributes to 0 */ + command ResetCounts(): DefaultSuccess = 0; +} + +/** Commands to trigger a Node to allow a new Administrator to commission it. */ +cluster AdministratorCommissioning = 60 { + revision 1; // NOTE: Default/not specifically set + + enum CommissioningWindowStatusEnum : enum8 { + kWindowNotOpen = 0; + kEnhancedWindowOpen = 1; + kBasicWindowOpen = 2; + } + + enum StatusCode : enum8 { + kBusy = 2; + kPAKEParameterError = 3; + kWindowNotOpen = 4; + } + + bitmap Feature : bitmap32 { + kBasic = 0x1; + } + + readonly attribute CommissioningWindowStatusEnum windowStatus = 0; + readonly attribute nullable fabric_idx adminFabricIndex = 1; + readonly attribute nullable vendor_id adminVendorId = 2; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct OpenCommissioningWindowRequest { + int16u commissioningTimeout = 0; + octet_string PAKEPasscodeVerifier = 1; + int16u discriminator = 2; + int32u iterations = 3; + octet_string<32> salt = 4; + } + + request struct OpenBasicCommissioningWindowRequest { + int16u commissioningTimeout = 0; + } + + /** This command is used by a current Administrator to instruct a Node to go into commissioning mode using enhanced commissioning method. */ + timed command access(invoke: administer) OpenCommissioningWindow(OpenCommissioningWindowRequest): DefaultSuccess = 0; + /** This command is used by a current Administrator to instruct a Node to go into commissioning mode using basic commissioning method, if the node supports it. */ + timed command access(invoke: administer) OpenBasicCommissioningWindow(OpenBasicCommissioningWindowRequest): DefaultSuccess = 1; + /** This command is used by a current Administrator to instruct a Node to revoke any active Open Commissioning Window or Open Basic Commissioning Window command. */ + timed command access(invoke: administer) RevokeCommissioning(): DefaultSuccess = 2; +} + +/** This cluster is used to add or remove Operational Credentials on a Commissionee or Node, as well as manage the associated Fabrics. */ +cluster OperationalCredentials = 62 { + revision 1; // NOTE: Default/not specifically set + + enum CertificateChainTypeEnum : enum8 { + kDACCertificate = 1; + kPAICertificate = 2; + } + + enum NodeOperationalCertStatusEnum : enum8 { + kOK = 0; + kInvalidPublicKey = 1; + kInvalidNodeOpId = 2; + kInvalidNOC = 3; + kMissingCsr = 4; + kTableFull = 5; + kInvalidAdminSubject = 6; + kFabricConflict = 9; + kLabelConflict = 10; + kInvalidFabricIndex = 11; + } + + fabric_scoped struct FabricDescriptorStruct { + octet_string<65> rootPublicKey = 1; + vendor_id vendorID = 2; + fabric_id fabricID = 3; + node_id nodeID = 4; + char_string<32> label = 5; + fabric_idx fabricIndex = 254; + } + + fabric_scoped struct NOCStruct { + fabric_sensitive octet_string noc = 1; + nullable fabric_sensitive octet_string icac = 2; + fabric_idx fabricIndex = 254; + } + + readonly attribute access(read: administer) NOCStruct NOCs[] = 0; + readonly attribute FabricDescriptorStruct fabrics[] = 1; + readonly attribute int8u supportedFabrics = 2; + readonly attribute int8u commissionedFabrics = 3; + readonly attribute octet_string trustedRootCertificates[] = 4; + readonly attribute int8u currentFabricIndex = 5; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct AttestationRequestRequest { + octet_string<32> attestationNonce = 0; + } + + response struct AttestationResponse = 1 { + octet_string<900> attestationElements = 0; + octet_string<64> attestationSignature = 1; + } + + request struct CertificateChainRequestRequest { + CertificateChainTypeEnum certificateType = 0; + } + + response struct CertificateChainResponse = 3 { + octet_string<600> certificate = 0; + } + + request struct CSRRequestRequest { + octet_string<32> CSRNonce = 0; + optional boolean isForUpdateNOC = 1; + } + + response struct CSRResponse = 5 { + octet_string NOCSRElements = 0; + octet_string attestationSignature = 1; + } + + request struct AddNOCRequest { + octet_string<400> NOCValue = 0; + optional octet_string<400> ICACValue = 1; + octet_string<16> IPKValue = 2; + int64u caseAdminSubject = 3; + vendor_id adminVendorId = 4; + } + + request struct UpdateNOCRequest { + octet_string NOCValue = 0; + optional octet_string ICACValue = 1; + } + + response struct NOCResponse = 8 { + NodeOperationalCertStatusEnum statusCode = 0; + optional fabric_idx fabricIndex = 1; + optional char_string<128> debugText = 2; + } + + request struct UpdateFabricLabelRequest { + char_string<32> label = 0; + } + + request struct RemoveFabricRequest { + fabric_idx fabricIndex = 0; + } + + request struct AddTrustedRootCertificateRequest { + octet_string rootCACertificate = 0; + } + + /** Sender is requesting attestation information from the receiver. */ + command access(invoke: administer) AttestationRequest(AttestationRequestRequest): AttestationResponse = 0; + /** Sender is requesting a device attestation certificate from the receiver. */ + command access(invoke: administer) CertificateChainRequest(CertificateChainRequestRequest): CertificateChainResponse = 2; + /** Sender is requesting a certificate signing request (CSR) from the receiver. */ + command access(invoke: administer) CSRRequest(CSRRequestRequest): CSRResponse = 4; + /** Sender is requesting to add the new node operational certificates. */ + command access(invoke: administer) AddNOC(AddNOCRequest): NOCResponse = 6; + /** Sender is requesting to update the node operational certificates. */ + fabric command access(invoke: administer) UpdateNOC(UpdateNOCRequest): NOCResponse = 7; + /** This command SHALL be used by an Administrative Node to set the user-visible Label field for a given Fabric, as reflected by entries in the Fabrics attribute. */ + fabric command access(invoke: administer) UpdateFabricLabel(UpdateFabricLabelRequest): NOCResponse = 9; + /** This command is used by Administrative Nodes to remove a given fabric index and delete all associated fabric-scoped data. */ + command access(invoke: administer) RemoveFabric(RemoveFabricRequest): NOCResponse = 10; + /** This command SHALL add a Trusted Root CA Certificate, provided as its CHIP Certificate representation. */ + command access(invoke: administer) AddTrustedRootCertificate(AddTrustedRootCertificateRequest): DefaultSuccess = 11; +} + +/** The Group Key Management Cluster is the mechanism by which group keys are managed. */ +cluster GroupKeyManagement = 63 { + revision 1; // NOTE: Default/not specifically set + + enum GroupKeySecurityPolicyEnum : enum8 { + kTrustFirst = 0; + kCacheAndSync = 1; + } + + bitmap Feature : bitmap32 { + kCacheAndSync = 0x1; + } + + fabric_scoped struct GroupInfoMapStruct { + group_id groupId = 1; + endpoint_no endpoints[] = 2; + optional char_string<16> groupName = 3; + fabric_idx fabricIndex = 254; + } + + fabric_scoped struct GroupKeyMapStruct { + group_id groupId = 1; + int16u groupKeySetID = 2; + fabric_idx fabricIndex = 254; + } + + struct GroupKeySetStruct { + int16u groupKeySetID = 0; + GroupKeySecurityPolicyEnum groupKeySecurityPolicy = 1; + nullable octet_string<16> epochKey0 = 2; + nullable epoch_us epochStartTime0 = 3; + nullable octet_string<16> epochKey1 = 4; + nullable epoch_us epochStartTime1 = 5; + nullable octet_string<16> epochKey2 = 6; + nullable epoch_us epochStartTime2 = 7; + } + + attribute access(write: manage) GroupKeyMapStruct groupKeyMap[] = 0; + readonly attribute GroupInfoMapStruct groupTable[] = 1; + readonly attribute int16u maxGroupsPerFabric = 2; + readonly attribute int16u maxGroupKeysPerFabric = 3; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct KeySetWriteRequest { + GroupKeySetStruct groupKeySet = 0; + } + + request struct KeySetReadRequest { + int16u groupKeySetID = 0; + } + + response struct KeySetReadResponse = 2 { + GroupKeySetStruct groupKeySet = 0; + } + + request struct KeySetRemoveRequest { + int16u groupKeySetID = 0; + } + + response struct KeySetReadAllIndicesResponse = 5 { + int16u groupKeySetIDs[] = 0; + } + + /** Write a new set of keys for the given key set id. */ + fabric command access(invoke: administer) KeySetWrite(KeySetWriteRequest): DefaultSuccess = 0; + /** Read the keys for a given key set id. */ + fabric command access(invoke: administer) KeySetRead(KeySetReadRequest): KeySetReadResponse = 1; + /** Revoke a Root Key from a Group */ + fabric command access(invoke: administer) KeySetRemove(KeySetRemoveRequest): DefaultSuccess = 3; + /** Return the list of Group Key Sets associated with the accessing fabric */ + fabric command access(invoke: administer) KeySetReadAllIndices(): KeySetReadAllIndicesResponse = 4; +} + +/** The Fixed Label Cluster provides a feature for the device to tag an endpoint with zero or more read only +labels. */ +cluster FixedLabel = 64 { + revision 1; // NOTE: Default/not specifically set + + struct LabelStruct { + char_string<16> label = 0; + char_string<16> value = 1; + } + + readonly attribute LabelStruct labelList[] = 0; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + +/** The User Label Cluster provides a feature to tag an endpoint with zero or more labels. */ +cluster UserLabel = 65 { + revision 1; // NOTE: Default/not specifically set + + struct LabelStruct { + char_string<16> label = 0; + char_string<16> value = 1; + } + + attribute access(write: manage) LabelStruct labelList[] = 0; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + +/** Attributes and commands for selecting a mode from a list of supported options. */ +cluster ModeSelect = 80 { + revision 2; + + bitmap Feature : bitmap32 { + kOnOff = 0x1; + } + + struct SemanticTagStruct { + vendor_id mfgCode = 0; + enum16 value = 1; + } + + struct ModeOptionStruct { + char_string<64> label = 0; + int8u mode = 1; + SemanticTagStruct semanticTags[] = 2; + } + + readonly attribute char_string<64> description = 0; + readonly attribute nullable enum16 standardNamespace = 1; + readonly attribute ModeOptionStruct supportedModes[] = 2; + readonly attribute int8u currentMode = 3; + attribute optional nullable int8u startUpMode = 4; + attribute optional nullable int8u onMode = 5; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct ChangeToModeRequest { + int8u newMode = 0; + } + + /** On receipt of this command, if the NewMode field matches the Mode field in an entry of the SupportedModes list, the server SHALL set the CurrentMode attribute to the NewMode value, otherwise, the server SHALL respond with an INVALID_COMMAND status response. */ + command ChangeToMode(ChangeToModeRequest): DefaultSuccess = 0; +} + +/** Attributes and commands for selecting a mode from a list of supported options. */ +cluster LaundryWasherMode = 81 { + revision 2; + + enum ModeTag : enum16 { + kNormal = 16384; + kDelicate = 16385; + kHeavy = 16386; + kWhites = 16387; + } + + bitmap Feature : bitmap32 { + kOnOff = 0x1; + } + + struct ModeTagStruct { + optional vendor_id mfgCode = 0; + enum16 value = 1; + } + + struct ModeOptionStruct { + char_string<64> label = 0; + int8u mode = 1; + ModeTagStruct modeTags[] = 2; + } + + readonly attribute ModeOptionStruct supportedModes[] = 0; + readonly attribute int8u currentMode = 1; + attribute optional nullable int8u startUpMode = 2; + attribute optional nullable int8u onMode = 3; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct ChangeToModeRequest { + int8u newMode = 0; + } + + response struct ChangeToModeResponse = 1 { + enum8 status = 0; + optional char_string statusText = 1; + } + + /** This command is used to change device modes. + On receipt of this command the device SHALL respond with a ChangeToModeResponse command. */ + command ChangeToMode(ChangeToModeRequest): ChangeToModeResponse = 0; +} + +/** This cluster supports remotely monitoring and controlling the different types of functionality available to a washing device, such as a washing machine. */ +cluster LaundryWasherControls = 83 { + revision 1; + + enum NumberOfRinsesEnum : enum8 { + kNone = 0; + kNormal = 1; + kExtra = 2; + kMax = 3; + } + + bitmap Feature : bitmap32 { + kSpin = 0x1; + kRinse = 0x2; + } + + readonly attribute optional char_string spinSpeeds[] = 0; + attribute optional nullable int8u spinSpeedCurrent = 1; + attribute optional NumberOfRinsesEnum numberOfRinses = 2; + readonly attribute optional NumberOfRinsesEnum supportedRinses[] = 3; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; +} + +/** Attributes and commands for configuring the temperature control, and reporting temperature. */ +cluster TemperatureControl = 86 { + revision 1; // NOTE: Default/not specifically set + + bitmap Feature : bitmap32 { + kTemperatureNumber = 0x1; + kTemperatureLevel = 0x2; + kTemperatureStep = 0x4; + } + + readonly attribute optional temperature temperatureSetpoint = 0; + readonly attribute optional temperature minTemperature = 1; + readonly attribute optional temperature maxTemperature = 2; + readonly attribute optional temperature step = 3; + readonly attribute optional int8u selectedTemperatureLevel = 4; + readonly attribute optional char_string supportedTemperatureLevels[] = 5; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + request struct SetTemperatureRequest { + optional temperature targetTemperature = 0; + optional int8u targetTemperatureLevel = 1; + } + + /** Set Temperature */ + command SetTemperature(SetTemperatureRequest): DefaultSuccess = 0; +} + +/** This cluster supports remotely monitoring and, where supported, changing the operational state of any device where a state machine is a part of the operation. */ +cluster OperationalState = 96 { + revision 1; + + enum ErrorStateEnum : enum8 { + kNoError = 0; + kUnableToStartOrResume = 1; + kUnableToCompleteOperation = 2; + kCommandInvalidInState = 3; + } + + enum OperationalStateEnum : enum8 { + kStopped = 0; + kRunning = 1; + kPaused = 2; + kError = 3; + } + + struct ErrorStateStruct { + enum8 errorStateID = 0; + optional char_string<64> errorStateLabel = 1; + optional char_string<64> errorStateDetails = 2; + } + + struct OperationalStateStruct { + enum8 operationalStateID = 0; + optional char_string<64> operationalStateLabel = 1; + } + + critical event OperationalError = 0 { + ErrorStateStruct errorState = 0; + } + + info event OperationCompletion = 1 { + enum8 completionErrorCode = 0; + optional nullable elapsed_s totalOperationalTime = 1; + optional nullable elapsed_s pausedTime = 2; + } + + readonly attribute nullable char_string phaseList[] = 0; + readonly attribute nullable int8u currentPhase = 1; + readonly attribute optional nullable elapsed_s countdownTime = 2; + readonly attribute OperationalStateStruct operationalStateList[] = 3; + readonly attribute OperationalStateEnum operationalState = 4; + readonly attribute ErrorStateStruct operationalError = 5; + readonly attribute command_id generatedCommandList[] = 65528; + readonly attribute command_id acceptedCommandList[] = 65529; + readonly attribute event_id eventList[] = 65530; + readonly attribute attrib_id attributeList[] = 65531; + readonly attribute bitmap32 featureMap = 65532; + readonly attribute int16u clusterRevision = 65533; + + response struct OperationalCommandResponse = 4 { + ErrorStateStruct commandResponseState = 0; + } + + /** Upon receipt, the device SHALL pause its operation if it is possible based on the current function of the server. */ + command Pause(): OperationalCommandResponse = 0; + /** Upon receipt, the device SHALL stop its operation if it is at a position where it is safe to do so and/or permitted. */ + command Stop(): OperationalCommandResponse = 1; + /** Upon receipt, the device SHALL start its operation if it is safe to do so and the device is in an operational state from which it can be started. */ + command Start(): OperationalCommandResponse = 2; + /** Upon receipt, the device SHALL resume its operation from the point it was at when it received the Pause command, or from the point when it was paused by means outside of this cluster (for example by manual button press). */ + command Resume(): OperationalCommandResponse = 3; +} + +endpoint 0 { + device type ma_rootdevice = 22, version 1; + + binding cluster OtaSoftwareUpdateProvider; + + server cluster Identify { + ram attribute identifyTime default = 0x0000; + ram attribute identifyType default = 0x00; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 4; + + handle command Identify; + } + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute featureMap; + callback attribute clusterRevision; + } + + server cluster AccessControl { + emits event AccessControlEntryChanged; + emits event AccessControlExtensionChanged; + callback attribute acl; + callback attribute extension; + callback attribute subjectsPerAccessControlEntry; + callback attribute targetsPerAccessControlEntry; + callback attribute accessControlEntriesPerFabric; + callback attribute attributeList; + ram attribute featureMap default = 0; + callback attribute clusterRevision; + } + + server cluster BasicInformation { + emits event StartUp; + emits event ShutDown; + emits event Leave; + callback attribute dataModelRevision; + callback attribute vendorName; + callback attribute vendorID; + callback attribute productName; + callback attribute productID; + persist attribute nodeLabel; + callback attribute location; + callback attribute hardwareVersion; + callback attribute hardwareVersionString; + callback attribute softwareVersion; + callback attribute softwareVersionString; + callback attribute manufacturingDate; + callback attribute partNumber; + callback attribute productURL; + callback attribute productLabel; + callback attribute serialNumber; + persist attribute localConfigDisabled default = 0; + callback attribute uniqueID; + callback attribute capabilityMinima; + callback attribute productAppearance; + callback attribute specificationVersion; + callback attribute maxPathsPerInvoke; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 2; + } + + server cluster OtaSoftwareUpdateRequestor { + emits event StateTransition; + emits event VersionApplied; + emits event DownloadError; + callback attribute defaultOTAProviders; + ram attribute updatePossible default = 1; + ram attribute updateState default = 0; + ram attribute updateStateProgress default = 0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command AnnounceOTAProvider; + } + + server cluster LocalizationConfiguration { + persist attribute activeLocale default = "en-US"; + callback attribute supportedLocales; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + } + + server cluster TimeFormatLocalization { + ram attribute hourFormat; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + } + + server cluster UnitLocalization { + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + } + + server cluster GeneralCommissioning { + ram attribute breadcrumb default = 0x0000000000000000; + callback attribute basicCommissioningInfo; + callback attribute regulatoryConfig; + callback attribute locationCapability; + callback attribute supportsConcurrentConnection; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command ArmFailSafe; + handle command ArmFailSafeResponse; + handle command SetRegulatoryConfig; + handle command SetRegulatoryConfigResponse; + handle command CommissioningComplete; + handle command CommissioningCompleteResponse; + } + + server cluster NetworkCommissioning { + ram attribute maxNetworks; + callback attribute networks; + ram attribute scanMaxTimeSeconds; + ram attribute connectMaxTimeSeconds; + ram attribute interfaceEnabled; + ram attribute lastNetworkingStatus; + ram attribute lastNetworkID; + ram attribute lastConnectErrorValue; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 2; + ram attribute clusterRevision default = 1; + + handle command ScanNetworks; + handle command ScanNetworksResponse; + handle command AddOrUpdateWiFiNetwork; + handle command AddOrUpdateThreadNetwork; + handle command RemoveNetwork; + handle command NetworkConfigResponse; + handle command ConnectNetwork; + handle command ConnectNetworkResponse; + handle command ReorderNetwork; + } + + server cluster GeneralDiagnostics { + emits event BootReason; + callback attribute networkInterfaces; + callback attribute rebootCount; + callback attribute upTime; + callback attribute totalOperationalHours; + callback attribute bootReason; + callback attribute testEventTriggersEnabled default = false; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + + handle command TestEventTrigger; + handle command TimeSnapshot; + handle command TimeSnapshotResponse; + } + + server cluster WiFiNetworkDiagnostics { + callback attribute bssid; + callback attribute securityType; + callback attribute wiFiVersion; + callback attribute channelNumber; + callback attribute rssi; + callback attribute currentMaxRate; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + } + + server cluster AdministratorCommissioning { + callback attribute windowStatus; + callback attribute adminFabricIndex; + callback attribute adminVendorId; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command OpenCommissioningWindow; + handle command RevokeCommissioning; + } + + server cluster OperationalCredentials { + callback attribute NOCs; + callback attribute fabrics; + callback attribute supportedFabrics; + callback attribute commissionedFabrics; + callback attribute trustedRootCertificates; + callback attribute currentFabricIndex; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command AttestationRequest; + handle command AttestationResponse; + handle command CertificateChainRequest; + handle command CertificateChainResponse; + handle command CSRRequest; + handle command CSRResponse; + handle command AddNOC; + handle command UpdateNOC; + handle command NOCResponse; + handle command UpdateFabricLabel; + handle command RemoveFabric; + handle command AddTrustedRootCertificate; + } + + server cluster GroupKeyManagement { + callback attribute groupKeyMap; + callback attribute groupTable; + callback attribute maxGroupsPerFabric; + callback attribute maxGroupKeysPerFabric; + callback attribute featureMap; + callback attribute clusterRevision; + + handle command KeySetWrite; + handle command KeySetRead; + handle command KeySetReadResponse; + handle command KeySetRemove; + handle command KeySetReadAllIndices; + handle command KeySetReadAllIndicesResponse; + } +} +endpoint 1 { + device type ma_laundry_washer = 115, version 1; + + + server cluster Identify { + ram attribute identifyTime default = 0x0; + ram attribute identifyType default = 0x00; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 4; + + handle command Identify; + handle command TriggerEffect; + } + + server cluster Groups { + ram attribute nameSupport; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 4; + + handle command AddGroup; + handle command AddGroupResponse; + handle command ViewGroup; + handle command ViewGroupResponse; + handle command GetGroupMembership; + handle command GetGroupMembershipResponse; + handle command RemoveGroup; + handle command RemoveGroupResponse; + handle command RemoveAllGroups; + handle command AddGroupIfIdentifying; + } + + server cluster OnOff { + persist attribute onOff default = 0; + ram attribute globalSceneControl default = 1; + ram attribute onTime default = 0; + ram attribute offWaitTime default = 0; + persist attribute startUpOnOff default = 0xFF; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 1; + ram attribute clusterRevision default = 5; + + handle command Off; + handle command On; + handle command Toggle; + handle command OffWithEffect; + handle command OnWithRecallGlobalScene; + handle command OnWithTimedOff; + } + + server cluster Descriptor { + callback attribute deviceTypeList; + callback attribute serverList; + callback attribute clientList; + callback attribute partsList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + callback attribute clusterRevision; + } + + server cluster Binding { + callback attribute binding; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + } + + server cluster PowerSource { + ram attribute status; + ram attribute order; + ram attribute description; + ram attribute wiredCurrentType; + callback attribute endpointList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 1; + ram attribute clusterRevision default = 2; + } + + server cluster FixedLabel { + callback attribute labelList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + } + + server cluster UserLabel { + callback attribute labelList; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + } + + server cluster ModeSelect { + ram attribute description; + ram attribute standardNamespace; + callback attribute supportedModes; + ram attribute currentMode; + ram attribute startUpMode; + ram attribute onMode; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command ChangeToMode; + } + + server cluster LaundryWasherMode { + callback attribute supportedModes; + callback attribute currentMode; + callback attribute startUpMode; + callback attribute onMode; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + callback attribute featureMap; + ram attribute clusterRevision default = 1; + + handle command ChangeToMode; + handle command ChangeToModeResponse; + } + + server cluster LaundryWasherControls { + callback attribute spinSpeeds; + ram attribute spinSpeedCurrent; + ram attribute numberOfRinses; + callback attribute supportedRinses; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 3; + ram attribute clusterRevision default = 1; + } + + server cluster TemperatureControl { + ram attribute selectedTemperatureLevel; + callback attribute supportedTemperatureLevels; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 2; + ram attribute clusterRevision default = 1; + + handle command SetTemperature; + } + + server cluster OperationalState { + emits event OperationalError; + emits event OperationCompletion; + callback attribute phaseList; + callback attribute currentPhase; + callback attribute countdownTime; + callback attribute operationalStateList; + callback attribute operationalState; + callback attribute operationalError; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 1; + + handle command Pause; + handle command Stop; + handle command Start; + handle command Resume; + handle command OperationalCommandResponse; + } +} + + diff --git a/examples/laundry-washer-app/nxp/zap/laundry-washer-app.zap b/examples/laundry-washer-app/nxp/zap/laundry-washer-app.zap new file mode 100644 index 00000000000000..d5c7ee215e1976 --- /dev/null +++ b/examples/laundry-washer-app/nxp/zap/laundry-washer-app.zap @@ -0,0 +1,5288 @@ +{ + "fileFormat": 2, + "featureLevel": 99, + "creator": "zap", + "keyValuePairs": [ + { + "key": "commandDiscovery", + "value": "1" + }, + { + "key": "defaultResponsePolicy", + "value": "always" + }, + { + "key": "manufacturerCodes", + "value": "0x1002" + } + ], + "package": [ + { + "pathRelativity": "relativeToZap", + "path": "../../../../src/app/zap-templates/zcl/zcl.json", + "type": "zcl-properties", + "category": "matter", + "version": 1, + "description": "Matter SDK ZCL data" + }, + { + "pathRelativity": "relativeToZap", + "path": "../../../../src/app/zap-templates/app-templates.json", + "type": "gen-templates-json", + "version": "chip-v1" + } + ], + "endpointTypes": [ + { + "id": 1, + "name": "MA-rootdevice", + "deviceTypeRef": { + "code": 22, + "profileId": 259, + "label": "MA-rootdevice", + "name": "MA-rootdevice" + }, + "deviceTypes": [ + { + "code": 22, + "profileId": 259, + "label": "MA-rootdevice", + "name": "MA-rootdevice" + } + ], + "deviceVersions": [ + 1 + ], + "deviceIdentifiers": [ + 22 + ], + "deviceTypeName": "MA-rootdevice", + "deviceTypeCode": 22, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Identify", + "code": 3, + "mfgCode": null, + "define": "IDENTIFY_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Identify", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "IdentifyTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0000", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "IdentifyType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "IdentifyTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x00", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "4", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Access Control", + "code": 31, + "mfgCode": null, + "define": "ACCESS_CONTROL_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "ACL", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Extension", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SubjectsPerAccessControlEntry", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TargetsPerAccessControlEntry", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AccessControlEntriesPerFabric", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "AccessControlEntryChanged", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "AccessControlExtensionChanged", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, + { + "name": "Basic Information", + "code": 40, + "mfgCode": null, + "define": "BASIC_INFORMATION_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DataModelRevision", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "VendorName", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "VendorID", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "vendor_id", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ProductName", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ProductID", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "NodeLabel", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "NVM", + "singleton": 1, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "Location", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "HardwareVersion", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "HardwareVersionString", + "code": 8, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SoftwareVersion", + "code": 9, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SoftwareVersionString", + "code": 10, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ManufacturingDate", + "code": 11, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "PartNumber", + "code": 12, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ProductURL", + "code": 13, + "mfgCode": null, + "side": "server", + "type": "long_char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ProductLabel", + "code": 14, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SerialNumber", + "code": 15, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "LocalConfigDisabled", + "code": 16, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "NVM", + "singleton": 1, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "UniqueID", + "code": 18, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "CapabilityMinima", + "code": 19, + "mfgCode": null, + "side": "server", + "type": "CapabilityMinimaStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ProductAppearance", + "code": 20, + "mfgCode": null, + "side": "server", + "type": "ProductAppearanceStruct", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SpecificationVersion", + "code": 21, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MaxPathsPerInvoke", + "code": 22, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 1, + "bounded": 0, + "defaultValue": "2", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "StartUp", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "ShutDown", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "Leave", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, + { + "name": "OTA Software Update Provider", + "code": 41, + "mfgCode": null, + "define": "OTA_SOFTWARE_UPDATE_PROVIDER_CLUSTER", + "side": "client", + "enabled": 1, + "commands": [ + { + "name": "QueryImage", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "QueryImageResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ApplyUpdateRequest", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ApplyUpdateResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "NotifyUpdateApplied", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "client", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "OTA Software Update Requestor", + "code": 42, + "mfgCode": null, + "define": "OTA_SOFTWARE_UPDATE_REQUESTOR_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "AnnounceOTAProvider", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "DefaultOTAProviders", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "UpdatePossible", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "UpdateState", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "UpdateStateEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "UpdateStateProgress", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "StateTransition", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "VersionApplied", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "DownloadError", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, + { + "name": "Localization Configuration", + "code": 43, + "mfgCode": null, + "define": "LOCALIZATION_CONFIGURATION_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "ActiveLocale", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "NVM", + "singleton": 0, + "bounded": 0, + "defaultValue": "en-US", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SupportedLocales", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Time Format Localization", + "code": 44, + "mfgCode": null, + "define": "TIME_FORMAT_LOCALIZATION_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "HourFormat", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "HourFormatEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Unit Localization", + "code": 45, + "mfgCode": null, + "define": "UNIT_LOCALIZATION_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "General Commissioning", + "code": 48, + "mfgCode": null, + "define": "GENERAL_COMMISSIONING_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "ArmFailSafe", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ArmFailSafeResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "SetRegulatoryConfig", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetRegulatoryConfigResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "CommissioningComplete", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CommissioningCompleteResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "Breadcrumb", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int64u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0000000000000000", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "BasicCommissioningInfo", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "BasicCommissioningInfo", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "RegulatoryConfig", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "RegulatoryLocationTypeEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LocationCapability", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "RegulatoryLocationTypeEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SupportsConcurrentConnection", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Network Commissioning", + "code": 49, + "mfgCode": null, + "define": "NETWORK_COMMISSIONING_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "ScanNetworks", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ScanNetworksResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "AddOrUpdateWiFiNetwork", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AddOrUpdateThreadNetwork", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RemoveNetwork", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "NetworkConfigResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ConnectNetwork", + "code": 6, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ConnectNetworkResponse", + "code": 7, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ReorderNetwork", + "code": 8, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "MaxNetworks", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Networks", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ScanMaxTimeSeconds", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ConnectMaxTimeSeconds", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "InterfaceEnabled", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LastNetworkingStatus", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "NetworkCommissioningStatusEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LastNetworkID", + "code": 6, + "mfgCode": null, + "side": "server", + "type": "octet_string", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "LastConnectErrorValue", + "code": 7, + "mfgCode": null, + "side": "server", + "type": "int32s", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "2", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "General Diagnostics", + "code": 51, + "mfgCode": null, + "define": "GENERAL_DIAGNOSTICS_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "TestEventTrigger", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "TimeSnapshot", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "TimeSnapshotResponse", + "code": 2, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "NetworkInterfaces", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "RebootCount", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "UpTime", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int64u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TotalOperationalHours", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BootReason", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "BootReasonEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "TestEventTriggersEnabled", + "code": 8, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "false", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "BootReason", + "code": 3, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, + { + "name": "WiFi Network Diagnostics", + "code": 54, + "mfgCode": null, + "define": "WIFI_NETWORK_DIAGNOSTICS_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "BSSID", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "octet_string", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SecurityType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "SecurityTypeEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "WiFiVersion", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "WiFiVersionEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "ChannelNumber", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "RSSI", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int8s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "CurrentMaxRate", + "code": 11, + "mfgCode": null, + "side": "server", + "type": "int64u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Administrator Commissioning", + "code": 60, + "mfgCode": null, + "define": "ADMINISTRATOR_COMMISSIONING_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "OpenCommissioningWindow", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RevokeCommissioning", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "WindowStatus", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "CommissioningWindowStatusEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AdminFabricIndex", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "fabric_idx", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AdminVendorId", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "vendor_id", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Operational Credentials", + "code": 62, + "mfgCode": null, + "define": "OPERATIONAL_CREDENTIALS_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "AttestationRequest", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AttestationResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "CertificateChainRequest", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CertificateChainResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "CSRRequest", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "CSRResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "AddNOC", + "code": 6, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "UpdateNOC", + "code": 7, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "NOCResponse", + "code": 8, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "UpdateFabricLabel", + "code": 9, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RemoveFabric", + "code": 10, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AddTrustedRootCertificate", + "code": 11, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "NOCs", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Fabrics", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "SupportedFabrics", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "CommissionedFabrics", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "TrustedRootCertificates", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "CurrentFabricIndex", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + }, + { + "name": "Group Key Management", + "code": 63, + "mfgCode": null, + "define": "GROUP_KEY_MANAGEMENT_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "KeySetWrite", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetRead", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetReadResponse", + "code": 2, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "KeySetRemove", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetReadAllIndices", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "KeySetReadAllIndicesResponse", + "code": 5, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "GroupKeyMap", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "GroupTable", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, + { + "name": "MaxGroupsPerFabric", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "MaxGroupKeysPerFabric", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + } + ] + } + ] + }, + { + "id": 2, + "name": "Anonymous Endpoint Type", + "deviceTypeRef": { + "code": 115, + "profileId": 259, + "label": "MA-laundry-washer", + "name": "MA-laundry-washer" + }, + "deviceTypes": [ + { + "code": 115, + "profileId": 259, + "label": "MA-laundry-washer", + "name": "MA-laundry-washer" + } + ], + "deviceVersions": [ + 1 + ], + "deviceIdentifiers": [ + 115 + ], + "deviceTypeName": "MA-laundry-washer", + "deviceTypeCode": 115, + "deviceTypeProfileId": 259, + "clusters": [ + { + "name": "Identify", + "code": 3, + "mfgCode": null, + "define": "IDENTIFY_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Identify", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "TriggerEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "IdentifyTime", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "IdentifyType", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "IdentifyTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x00", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "4", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Groups", + "code": 4, + "mfgCode": null, + "define": "GROUPS_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "AddGroup", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AddGroupResponse", + "code": 0, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ViewGroup", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ViewGroupResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "GetGroupMembership", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetGroupMembershipResponse", + "code": 2, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "RemoveGroup", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "RemoveGroupResponse", + "code": 3, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "RemoveAllGroups", + "code": 4, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "AddGroupIfIdentifying", + "code": 5, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "NameSupport", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "NameSupportBitmap", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "4", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "On/Off", + "code": 6, + "mfgCode": null, + "define": "ON_OFF_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Off", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "On", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "Toggle", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "OffWithEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "OnWithRecallGlobalScene", + "code": 65, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "OnWithTimedOff", + "code": 66, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "OnOff", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "NVM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GlobalSceneControl", + "code": 16384, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OnTime", + "code": 16385, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OffWaitTime", + "code": 16386, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "StartUpOnOff", + "code": 16387, + "mfgCode": null, + "side": "server", + "type": "StartUpOnOffEnum", + "included": 1, + "storageOption": "NVM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0xFF", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Descriptor", + "code": 29, + "mfgCode": null, + "define": "DESCRIPTOR_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "DeviceTypeList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ServerList", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClientList", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PartsList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Binding", + "code": 30, + "mfgCode": null, + "define": "BINDING_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "Binding", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Power Source", + "code": 47, + "mfgCode": null, + "define": "POWER_SOURCE_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "Status", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "PowerSourceStatusEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Order", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "Description", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "WiredCurrentType", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "WiredCurrentTypeEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EndpointList", + "code": 31, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "2", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Fixed Label", + "code": 64, + "mfgCode": null, + "define": "FIXED_LABEL_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "LabelList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "User Label", + "code": 65, + "mfgCode": null, + "define": "USER_LABEL_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "LabelList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Mode Select", + "code": 80, + "mfgCode": null, + "define": "MODE_SELECT_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "ChangeToMode", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "Description", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "char_string", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "StandardNamespace", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "enum16", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SupportedModes", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CurrentMode", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "StartUpMode", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OnMode", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Laundry Washer Mode", + "code": 81, + "mfgCode": null, + "define": "LAUNDRY_WASHER_MODE_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "ChangeToMode", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "ChangeToModeResponse", + "code": 1, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "SupportedModes", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CurrentMode", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "StartUpMode", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OnMode", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Laundry Washer Controls", + "code": 83, + "mfgCode": null, + "define": "LAUNDRY_WASHER_CONTROLS_CLUSTER", + "side": "server", + "enabled": 1, + "attributes": [ + { + "name": "SpinSpeeds", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SpinSpeedCurrent", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NumberOfRinses", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "NumberOfRinsesEnum", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SupportedRinses", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "3", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Temperature Control", + "code": 86, + "mfgCode": null, + "define": "TEMPERATURE_CONTROL_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "SetTemperature", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "SelectedTemperatureLevel", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "SupportedTemperatureLevels", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "2", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ] + }, + { + "name": "Operational State", + "code": 96, + "mfgCode": null, + "define": "OPERATIONAL_STATE_CLUSTER", + "side": "server", + "enabled": 1, + "commands": [ + { + "name": "Pause", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "Stop", + "code": 1, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "Start", + "code": 2, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "Resume", + "code": 3, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "OperationalCommandResponse", + "code": 4, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + } + ], + "attributes": [ + { + "name": "PhaseList", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CurrentPhase", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CountdownTime", + "code": 2, + "mfgCode": null, + "side": "server", + "type": "elapsed_s", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OperationalStateList", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OperationalState", + "code": 4, + "mfgCode": null, + "side": "server", + "type": "OperationalStateEnum", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "OperationalError", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "ErrorStateStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "server", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "ClusterRevision", + "code": 65533, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "1", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + } + ], + "events": [ + { + "name": "OperationalError", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "OperationCompletion", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + } + ] + } + ], + "endpoints": [ + { + "endpointTypeName": "MA-rootdevice", + "endpointTypeIndex": 0, + "profileId": 259, + "endpointId": 0, + "networkId": 0 + }, + { + "endpointTypeName": "Anonymous Endpoint Type", + "endpointTypeIndex": 1, + "profileId": 259, + "endpointId": 1, + "networkId": 0 + } + ] +} \ No newline at end of file diff --git a/examples/lighting-app/nxp/k32w/k32w0/.gn b/examples/lighting-app/nxp/k32w/k32w0/.gn index 3d48789e30ab3d..363727423ce903 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/.gn +++ b/examples/lighting-app/nxp/k32w/k32w0/.gn @@ -25,4 +25,7 @@ default_args = { target_os = "freertos" import("//args.gni") + + # Import default platform configs + import("${chip_root}/src/platform/nxp/k32w/k32w0/args.gni") } diff --git a/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn b/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn index 93bf1dc363f9dd..43a9860b5da48a 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn +++ b/examples/lighting-app/nxp/k32w/k32w0/BUILD.gn @@ -13,11 +13,13 @@ # limitations under the License. import("//build_overrides/chip.gni") -import("//build_overrides/k32w0_sdk.gni") +import("//build_overrides/nxp_sdk.gni") import("//build_overrides/openthread.gni") -import("${k32w0_sdk_build_root}/k32w0_executable.gni") -import("${k32w0_sdk_build_root}/k32w0_sdk.gni") +import("${nxp_sdk_build_root}/nxp_sdk.gni") + +import("${nxp_sdk_build_root}/${nxp_sdk_name}/${nxp_sdk_name}.gni") +import("${nxp_sdk_build_root}/${nxp_sdk_name}/nxp_executable.gni") import("${chip_root}/src/crypto/crypto.gni") import("${chip_root}/src/lib/core/core.gni") @@ -26,6 +28,9 @@ import("${chip_root}/src/platform/device.gni") declare_args() { chip_software_version = 0 chip_simple_hash_verification = 0 + + # Setup discriminator as argument + setup_discriminator = 3840 } if (chip_pw_tokenizer_logging) { @@ -70,6 +75,10 @@ k32w0_sdk("sdk") { "CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION=${chip_software_version}", ] } + + defines += [ + "CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR=${setup_discriminator}", + ] } k32w0_executable("light_app") { diff --git a/examples/lighting-app/nxp/k32w/k32w0/README.md b/examples/lighting-app/nxp/k32w/k32w0/README.md index 3194d71b39b889..af43002f21c65a 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/README.md +++ b/examples/lighting-app/nxp/k32w/k32w0/README.md @@ -231,7 +231,7 @@ Start building the application: ```bash user@ubuntu:~/Desktop/git/connectedhomeip$ cd examples/lighting-app/nxp/k32w/k32w0 -user@ubuntu:~/Desktop/git/connectedhomeip/examples/lighting-app/nxp/k32w/k32w0$ gn gen out/debug --args="chip_with_OM15082=1 chip_with_ot_cli=0 is_debug=false chip_crypto=\"platform\" chip_with_se05x=0 chip_pw_tokenizer_logging=true" +user@ubuntu:~/Desktop/git/connectedhomeip/examples/lighting-app/nxp/k32w/k32w0$ gn gen out/debug user@ubuntu:~/Desktop/git/connectedhomeip/examples/lighting-app/nxp/k32w/k32w0$ ninja -C out/debug ``` @@ -257,6 +257,16 @@ In case the board doesn't have 32KHz crystal fitted, one can use the 32KHz free running oscillator as a clock source. In this case one must set the use_fro_32k argument to 1. +K32W0x1 supports antenna diversity feature, which is a technique that maximizes +the performance of an antenna system, allowing the radio signal to be switched +between two antennas that have very low correlation between their received +signals. Typically, this is achieved by spacing two antennas around 0.25 +wavelengths apart or by using 2 orthogonal types of polarization. This is +controlled by software. K32W0x1 provides an output (`ADO`) on one of `DIO7`, +`DIO9` or `DIO19` and optionally its complement (`ADE`) on `DIO6` that can be +used to control an antenna switch. In order to use this feature, user must set +`use_antenna_diversity` to 1. + In case signing errors are encountered when running the "sign_images.sh" script (run automatically) install the recommanded packages (python version > 3, pip3, pycrypto, pycryptodome): @@ -624,14 +634,14 @@ The concept for OTA is the next one: informed of the node id of the OTA Provider Application. _Computer #1_ can be any system running an Ubuntu distribution. We recommand -using TE 7.5 instructions from -[here](https://groups.csa-iot.org/wg/matter-csg/document/24839), where RPi 4 are -proposed. Also, TE 7.5 instructions document point to the OS/Docker images that -should be used on the RPis. For compatibility reasons, we recommand compiling -chip-tool and OTA Provider applications with the same commit id that was used -for compiling the Lighting Application. Also, please note that there is a single -controller (chip-tool) running on Computer #1 which is used for commissioning -both the device and the OTA Provider Application. If needed, +using CSA official instructions from +[here](https://groups.csa-iot.org/wg/matter-csg/document/28566), where RPi 4 are +proposed. Also, CSA official instructions document point to the OS/Docker images +that should be used on the RPis. For compatibility reasons, we recommand +compiling chip-tool and OTA Provider applications with the same commit id that +was used for compiling the Lighting Application. Also, please note that there is +a single controller (chip-tool) running on Computer #1 which is used for +commissioning both the device and the OTA Provider Application. If needed, [these instructions](https://itsfoss.com/connect-wifi-terminal-ubuntu/) could be used for connecting the RPis to WiFi. diff --git a/examples/lighting-app/nxp/k32w/k32w0/args.gni b/examples/lighting-app/nxp/k32w/k32w0/args.gni index 4f4bba4b47cf07..f5fd7a83cd9005 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/args.gni +++ b/examples/lighting-app/nxp/k32w/k32w0/args.gni @@ -13,8 +13,6 @@ # limitations under the License. import("//build_overrides/chip.gni") -import("${chip_root}/config/standalone/args.gni") -import("${chip_root}/examples/platform/nxp/k32w/k32w0/args.gni") # SDK target. This is overridden to add our SDK app_config.h & defines. k32w0_sdk_target = get_label_info(":sdk", "label_no_toolchain") @@ -22,3 +20,11 @@ k32w0_sdk_target = get_label_info(":sdk", "label_no_toolchain") chip_enable_ota_requestor = true chip_stack_lock_tracking = "fatal" chip_enable_ble = true + +is_debug = false + +chip_crypto = "platform" +chip_crypto_flavor = "NXP-Ultrafast-P256" +chip_with_ot_cli = 0 +chip_with_OM15082 = 1 +chip_pw_tokenizer_logging = true diff --git a/examples/lighting-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h b/examples/lighting-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h index 946d457622e467..e32575e55ff5c4 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h +++ b/examples/lighting-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h @@ -106,8 +106,13 @@ #define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0x8005 // Use a default setup PIN code if one hasn't been provisioned in flash. +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#endif + +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 +#endif /** * CHIP_DEVICE_CONFIG_TEST_SERIAL_NUMBER diff --git a/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp b/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp index 39c9584c883480..7b9ecccdec3068 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp +++ b/examples/lighting-app/nxp/k32w/k32w0/main/AppTask.cpp @@ -52,6 +52,9 @@ #include "LEDWidget.h" #include "app_config.h" +#if CHIP_CRYPTO_HSM +#include +#endif #ifdef ENABLE_HSM_DEVICE_ATTESTATION #include "DeviceAttestationSe05xCredsExample.h" #endif @@ -643,7 +646,6 @@ void AppTask::BleStartAdvertising(intptr_t arg) else { ConnectivityMgr().SetBLEAdvertisingEnabled(true); - if (chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow() == CHIP_NO_ERROR) { K32W_LOG("Started BLE Advertising!"); diff --git a/examples/lighting-app/nxp/k32w/k32w0/main/include/AppTask.h b/examples/lighting-app/nxp/k32w/k32w0/main/include/AppTask.h index 9590734521d5e1..0c455f431a8cf4 100644 --- a/examples/lighting-app/nxp/k32w/k32w0/main/include/AppTask.h +++ b/examples/lighting-app/nxp/k32w/k32w0/main/include/AppTask.h @@ -112,7 +112,6 @@ class AppTask kFunctionTurnOnTurnOff, kFunction_Identify, kFunction_TriggerEffect, - kFunction_Invalid } Function; diff --git a/examples/lighting-app/nxp/k32w/k32w1/BUILD.gn b/examples/lighting-app/nxp/k32w/k32w1/BUILD.gn index 2f327213533e94..7785720c28c3ec 100644 --- a/examples/lighting-app/nxp/k32w/k32w1/BUILD.gn +++ b/examples/lighting-app/nxp/k32w/k32w1/BUILD.gn @@ -36,6 +36,9 @@ if (chip_enable_pw_rpc) { declare_args() { chip_software_version = 0 + + # Setup discriminator as argument + setup_discriminator = 3840 } assert(current_os == "freertos") @@ -85,6 +88,10 @@ k32w1_sdk("sdk") { "BOARD_APP_UART_CLK_FREQ=96000000", ] } + + defines += [ + "CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR=${setup_discriminator}", + ] } k32w1_executable("light_app") { @@ -92,6 +99,13 @@ k32w1_executable("light_app") { sources = [] deps = [] + defines = [] + + if (chip_config_dimmable_led) { + defines += [ "CHIP_CONFIG_ENABLE_DIMMABLE_LED = 1" ] + } else { + defines += [ "CHIP_CONFIG_ENABLE_DIMMABLE_LED = 0" ] + } if (chip_enable_pw_rpc) { forward_variables_from(pw_rpc_server, "*") @@ -111,12 +125,19 @@ k32w1_executable("light_app") { "main/main.cpp", ] - public = [ "${chip_root}/src/platform/nxp/k32w/k32w1/DefaultTestEventTriggerDelegate.h" ] + if (chip_config_dimmable_led) { + sources += [ + "${k32w1_platform_dir}/util/LED_Dimmer.cpp", + "${k32w1_platform_dir}/util/include/LED_Dimmer.h", + ] + deps += [ "${chip_root}/examples/lighting-app/lighting-common/" ] + } else { + deps += [ "${chip_root}/examples/lighting-app/nxp/zap/" ] + } deps += [ ":sdk", "${chip_root}/examples/common/QRCode", - "${chip_root}/examples/lighting-app/nxp/zap/", "${chip_root}/examples/providers:device_info_provider", "${chip_root}/src/lib", "${chip_root}/src/platform:syscalls_stub", @@ -126,13 +147,13 @@ k32w1_executable("light_app") { if (chip_openthread_ftd) { deps += [ - "${chip_root}/third_party/openthread/repo:libopenthread-cli-ftd", - "${chip_root}/third_party/openthread/repo:libopenthread-ftd", + "${openthread_root}:libopenthread-cli-ftd", + "${openthread_root}:libopenthread-ftd", ] } else { deps += [ - "${chip_root}/third_party/openthread/repo:libopenthread-cli-mtd", - "${chip_root}/third_party/openthread/repo:libopenthread-mtd", + "${openthread_root}:libopenthread-cli-mtd", + "${openthread_root}:libopenthread-mtd", ] } diff --git a/examples/lighting-app/nxp/k32w/k32w1/README.md b/examples/lighting-app/nxp/k32w/k32w1/README.md index 253c21e04eafbb..c44c73a1ecc946 100644 --- a/examples/lighting-app/nxp/k32w/k32w1/README.md +++ b/examples/lighting-app/nxp/k32w/k32w1/README.md @@ -22,18 +22,17 @@ into an existing Matter network and can be controlled by this network. - [Device UI](#device-ui) - [Building](#building) - [SMU2](#smu2-memory) + - [LED PWM](#led-pwm) - [Manufacturing data](#manufacturing-data) - [Flashing](#flashing) - [Flashing the NBU image](#flashing-the-nbu-image) - [Flashing the host image](#flashing-the-host-image) - [Debugging](#debugging) - [OTA](#ota) - - [Convert srec into sb3 file](#convert-srec-into-sb3-file) - [Convert sb3 into ota file](#convert-sb3-into-ota-file) - [Running OTA](#running-ota) - [Known issues](#known-issues) - - [Running RPC console](#running-rpc-console) @@ -136,8 +135,9 @@ In order to build the Matter example, we recommend using a Linux distribution ``` user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_K32W1_SDK_ROOT=/home/user/Desktop/SDK_K32W1/ user@ubuntu:~/Desktop/git/connectedhomeip$ source ./scripts/activate.sh +user@ubuntu:~/Desktop/git/connectedhomeip$ scripts/checkout_submodules.py --shallow --platform nxp --recursive user@ubuntu:~/Desktop/git/connectedhomeip$ cd examples/lighting-app/nxp/k32w/k32w1 -user@ubuntu:~/Desktop/git/connectedhomeip/examples/lighting-app/nxp/k32w/k32w1$ gn gen out/debug --args="chip_with_ot_cli=0 is_debug=false chip_openthread_ftd=true chip_crypto=\"platform\"" +user@ubuntu:~/Desktop/git/connectedhomeip/examples/lighting-app/nxp/k32w/k32w1$ gn gen out/debug user@ubuntu:~/Desktop/git/connectedhomeip/examples/lighting-app/nxp/k32w/k32w1$ ninja -C out/debug ``` @@ -154,10 +154,10 @@ memory. When compiling with OpenThread FTD support (`chip_openthread_ftd=true`) and with `use_smu2_static=true`, the following components are placed in `SMU2` memory: -- `gImageProcessor` from `OTAImageProcessorImpl.cpp`. -- `gApplicationProcessor` from `OTAHooks.cpp`. -- `Server::sServer` from `Server.cpp`. -- `ThreadStackManagerImpl::sInstance` from `ThreadStackManagerImpl.cpp`. +- `gImageProcessor` from OTAImageProcessorImpl.cpp. +- `gApplicationProcessor` from OTAHooks.cpp. +- `Server::sServer` from Server.cpp. +- `ThreadStackManagerImpl::sInstance` from ThreadStackManagerImpl.cpp. These instances and global variables are placed in `SMU2` memory through name matching in the application linker script. They should not be changed or, if @@ -171,6 +171,17 @@ the OpenThread buffers will be dynamically allocated instead of statically, freeing some `SRAM`. To enable this feature compile with OpenThread FTD support (`chip_openthread_ftd=true`) and with `use_smu2_dynamic=true`. +### LED PWM + +In the default configuration, the onboard RGB LED pins are configured as GPIO +pins. In order to enable the dimming feature, the pins need to be configured in +PWM mode and synced with channels of the `TPM` (Timer PWM Module). To enable +this feature, compile the application with: `chip_config_dimmable_led=true` + +If the feature is enabled, the LED brightness can be controlled using **Level +control** cluster +[commands](../../../../../docs/guides/chip_tool_guide.md#step-7-control-application-data-model-clusters). + ## Manufacturing data Use `chip_with_factory_data=1` in the gn build command to enable factory data. @@ -195,7 +206,7 @@ path - [K32W148 board quick start guide](https://www.nxp.com/document/guide/getting-started-with-the-k32w148-development-platform:GS-K32W148EVK) can be used for updating the `NBU/radio` core: -- Section 2.4 – Get Software – install `SPSDK` (Secure Provisioning Command +- Section 2.5 – Get Software – install `SPSDK` (Secure Provisioning Command Line Tool) - Section 3.3 – Updating `NBU` for Wireless examples - use the corresponding .sb3 file found in the SDK package at path @@ -298,7 +309,7 @@ In `OTAP` application the image only for the CM33 core - keep other settings at default values -### Convert sb3 into ota file +### Convert `sb3` into `ota` file In order to build an OTA image, use NXP wrapper over the standard tool `src/app/ota_image_tool.py`: diff --git a/examples/lighting-app/nxp/k32w/k32w1/args.gni b/examples/lighting-app/nxp/k32w/k32w1/args.gni index a06e5828acdc45..d0c28a1e048545 100644 --- a/examples/lighting-app/nxp/k32w/k32w1/args.gni +++ b/examples/lighting-app/nxp/k32w/k32w1/args.gni @@ -17,6 +17,13 @@ import("//build_overrides/chip.gni") # SDK target. This is overridden to add our SDK app_config.h & defines. k32w1_sdk_target = get_label_info(":sdk", "label_no_toolchain") +chip_config_dimmable_led = false chip_enable_ota_requestor = true chip_stack_lock_tracking = "fatal" chip_enable_ble = true + +is_debug = false + +chip_crypto = "platform" +chip_openthread_ftd = true +chip_with_ot_cli = 0 diff --git a/examples/lighting-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h b/examples/lighting-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h index 05f8042ed28138..ad5d876dc650ab 100644 --- a/examples/lighting-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h +++ b/examples/lighting-app/nxp/k32w/k32w1/include/CHIPProjectConfig.h @@ -101,8 +101,13 @@ #define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0x8005 // Use a default setup PIN code if one hasn't been provisioned in flash. +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#endif + +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 +#endif // Use a default pairing code if one hasn't been provisioned in flash. #define CHIP_DEVICE_CONFIG_USE_TEST_PAIRING_CODE "CHIPUS" diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/AppTask.cpp b/examples/lighting-app/nxp/k32w/k32w1/main/AppTask.cpp index 2eb2f8e095b8b1..96f5a5b96b3248 100644 --- a/examples/lighting-app/nxp/k32w/k32w1/main/AppTask.cpp +++ b/examples/lighting-app/nxp/k32w/k32w1/main/AppTask.cpp @@ -57,6 +57,10 @@ #include "fsl_component_button.h" #include "fwk_platform.h" +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED +#include "LED_Dimmer.h" +#endif + #define FACTORY_RESET_TRIGGER_TIMEOUT 6000 #define FACTORY_RESET_CANCEL_WINDOW_TIMEOUT 3000 #define APP_TASK_PRIORITY 2 @@ -165,7 +169,13 @@ CHIP_ERROR AppTask::Init() #ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR sStatusLED.Init(SYSTEM_STATE_LED, false); #endif + +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED + init_dimmable(); +#else sLightLED.Init(LIGHT_STATE_LED, false); +#endif + UpdateDeviceState(); /* intialize the Keyboard and button press callback */ @@ -487,7 +497,12 @@ void AppTask::ResetActionEventHandler(AppEvent * aEvent) #ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR sStatusLED.Set(false); #endif + +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED + sLightLED.SetLevel(0); +#else sLightLED.Set(false); +#endif #ifndef CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR sStatusLED.Blink(500); @@ -537,7 +552,7 @@ void AppTask::LightActionEventHandler(AppEvent * aEvent) if (err == CHIP_NO_ERROR) { - initiated = LightingMgr().InitiateAction(actor, action); + initiated = LightingMgr().InitiateAction(actor, action, LightingMgr().IsTurnedOff() ? 0 : 1); if (!initiated) { @@ -644,6 +659,10 @@ void AppTask::ActionInitiated(LightingManager::Action_t aAction, int32_t aActor) { K32W_LOG("Turn off Action has been initiated") } + else if (aAction == LightingManager::DIM_ACTION) + { + K32W_LOG("Dim Action has been initiated"); + } if (aActor == AppEvent::kEventType_Button) { @@ -653,20 +672,33 @@ void AppTask::ActionInitiated(LightingManager::Action_t aAction, int32_t aActor) sAppTask.mFunction = kFunctionTurnOnTurnOff; } -void AppTask::ActionCompleted(LightingManager::Action_t aAction) +void AppTask::ActionCompleted(LightingManager::Action_t aAction, uint8_t level) { // Turn on the light LED if in a TURNON state OR // Turn off the light LED if in a TURNOFF state. if (aAction == LightingManager::TURNON_ACTION) { K32W_LOG("Turn on action has been completed") +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED +#else sLightLED.Set(true); +#endif } else if (aAction == LightingManager::TURNOFF_ACTION) { K32W_LOG("Turn off action has been completed") +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED +#else sLightLED.Set(false); +#endif + } + else if (aAction == LightingManager::DIM_ACTION) + { + K32W_LOG("Move to level %d completed", level); } +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED + sLightLED.SetLevel(LightingMgr().IsTurnedOff() ? 1 : LightingMgr().GetDimLevel()); +#endif if (sAppTask.mSyncClusterToButtonAction) { @@ -679,6 +711,9 @@ void AppTask::ActionCompleted(LightingManager::Action_t aAction) void AppTask::RestoreLightingState(void) { +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED + LightingMgr().SetState(!LightingMgr().IsTurnedOff()); +#else /* restore initial state for the LED indicating Lighting state */ if (LightingMgr().IsTurnedOff()) { @@ -688,6 +723,7 @@ void AppTask::RestoreLightingState(void) { sLightLED.Set(true); } +#endif } void AppTask::OnIdentifyStart(Identify * identify) @@ -706,7 +742,11 @@ void AppTask::OnIdentifyStart(Identify * identify) ChipLogProgress(Zcl, "Identify process has started. Status LED should blink with a period of 0.5 seconds."); sAppTask.mFunction = kFunction_Identify; +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED + sLightLED.SetLevel(0); +#else sLightLED.Set(false); +#endif sLightLED.Blink(250); } @@ -790,7 +830,11 @@ void AppTask::OnTriggerEffect(Identify * identify) if (timerDelay) { +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED + sLightLED.SetLevel(0); +#else sLightLED.Set(false); +#endif sLightLED.Blink(500); chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(timerDelay), OnTriggerEffectComplete, identify); @@ -873,7 +917,10 @@ void AppTask::UpdateDeviceStateInternal(intptr_t arg) (void) app::Clusters::OnOff::Attributes::OnOff::Get(1, &onoffAttrValue); /* set the device state */ +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED +#else sLightLED.Set(onoffAttrValue); +#endif LightingMgr().SetState(onoffAttrValue); } diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/LightingManager.cpp b/examples/lighting-app/nxp/k32w/k32w1/main/LightingManager.cpp index 7a5cefc9b668fc..2627138043bbe4 100644 --- a/examples/lighting-app/nxp/k32w/k32w1/main/LightingManager.cpp +++ b/examples/lighting-app/nxp/k32w/k32w1/main/LightingManager.cpp @@ -19,6 +19,10 @@ #include "LightingManager.h" +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED +#include "LED_Dimmer.h" +#endif + #include "AppTask.h" #include "FreeRTOS.h" @@ -30,6 +34,8 @@ int LightingManager::Init() { mState = kState_On; + mLevel = kLevel_Max; + return 0; } @@ -44,24 +50,48 @@ void LightingManager::SetState(bool state) mState = state ? kState_On : kState_Off; } +void LightingManager::SetDimLevel(uint8_t level) +{ + mLevel = level; +} + bool LightingManager::IsTurnedOff() { return (mState == kState_Off) ? true : false; } -bool LightingManager::InitiateAction(int32_t aActor, Action_t aAction) +uint8_t LightingManager::GetDimLevel() +{ + return mLevel; +} + +bool LightingManager::InitiateAction(int32_t aActor, Action_t aAction, uint8_t kValue) { bool action_initiated = false; + State_t current_state; if (mState == kState_On && aAction == TURNOFF_ACTION) { action_initiated = true; - mState = kState_Off; + current_state = kState_Off; } else if (mState == kState_Off && aAction == TURNON_ACTION) { action_initiated = true; - mState = kState_On; + current_state = kState_On; + } + + else if (aAction == DIM_ACTION && kValue != mLevel) + { + action_initiated = true; + if (kValue == 1) + { + current_state = kState_Off; + } + else + { + current_state = kState_On; + } } if (action_initiated) @@ -70,9 +100,20 @@ bool LightingManager::InitiateAction(int32_t aActor, Action_t aAction) { mActionInitiated_CB(aAction, aActor); } + + if (aAction == TURNON_ACTION || aAction == TURNOFF_ACTION) + { + SetState(current_state == kState_On); + } + else if (aAction == DIM_ACTION) + { + mState = current_state; + SetDimLevel(kValue); + } + if (mActionCompleted_CB) { - mActionCompleted_CB(aAction); + mActionCompleted_CB(aAction, kValue); } } diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp b/examples/lighting-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp index 5a4eee6e2c09e4..a9c6aee8798c49 100644 --- a/examples/lighting-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp +++ b/examples/lighting-app/nxp/k32w/k32w1/main/ZclCallbacks.cpp @@ -40,14 +40,21 @@ void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & return; } - LightingMgr().InitiateAction(0, *value ? LightingManager::TURNON_ACTION : LightingManager::TURNOFF_ACTION); + LightingMgr().InitiateAction(0, *value ? LightingManager::TURNON_ACTION : LightingManager::TURNOFF_ACTION, *value); } else if (path.mClusterId == LevelControl::Id) { - ChipLogProgress(Zcl, "Level Control attribute ID: " ChipLogFormatMEI " Type: %u Value: %u, length %u", - ChipLogValueMEI(path.mAttributeId), type, *value, size); + if (path.mAttributeId != LevelControl::Attributes::CurrentLevel::Id) + { + ChipLogProgress(Zcl, "Unknown attribute ID: " ChipLogFormatMEI, ChipLogValueMEI(path.mAttributeId)); + return; + } - // WIP Apply attribute change to Light + if (*value > 1 && *value < 254) + { + ChipLogProgress(Zcl, "Setting value: %d", *value); + LightingMgr().InitiateAction(0, LightingManager::DIM_ACTION, *value); + } } else if (path.mClusterId == ColorControl::Id) { diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/include/AppTask.h b/examples/lighting-app/nxp/k32w/k32w1/main/include/AppTask.h index b5201da2477862..096690c680b1d2 100644 --- a/examples/lighting-app/nxp/k32w/k32w1/main/include/AppTask.h +++ b/examples/lighting-app/nxp/k32w/k32w1/main/include/AppTask.h @@ -71,7 +71,7 @@ class AppTask CHIP_ERROR Init(); static void ActionInitiated(LightingManager::Action_t aAction, int32_t aActor); - static void ActionCompleted(LightingManager::Action_t aAction); + static void ActionCompleted(LightingManager::Action_t aAction, uint8_t level); void CancelTimer(void); diff --git a/examples/lighting-app/nxp/k32w/k32w1/main/include/LightingManager.h b/examples/lighting-app/nxp/k32w/k32w1/main/include/LightingManager.h index 327bf3bdf02763..f96d6c7ecae40c 100644 --- a/examples/lighting-app/nxp/k32w/k32w1/main/include/LightingManager.h +++ b/examples/lighting-app/nxp/k32w/k32w1/main/include/LightingManager.h @@ -33,7 +33,7 @@ class LightingManager { TURNON_ACTION = 0, TURNOFF_ACTION, - + DIM_ACTION, INVALID_ACTION } Action; @@ -43,18 +43,24 @@ class LightingManager kState_Off, } State; + static const uint8_t kLevel_Max = 254; + static const uint8_t kLevel_Min = 0; + int Init(); bool IsTurnedOff(); - bool InitiateAction(int32_t aActor, Action_t aAction); + uint8_t GetDimLevel(); + bool InitiateAction(int32_t aActor, Action_t aAction, uint8_t kValue); typedef void (*Callback_fn_initiated)(Action_t, int32_t aActor); - typedef void (*Callback_fn_completed)(Action_t); + typedef void (*Callback_fn_completed)(Action_t, uint8_t level); void SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB); void SetState(bool state); + void SetDimLevel(uint8_t level); private: friend LightingManager & LightingMgr(void); State_t mState; + uint8_t mLevel; Callback_fn_initiated mActionInitiated_CB; Callback_fn_completed mActionCompleted_CB; diff --git a/examples/lock-app/nxp/k32w/k32w0/BUILD.gn b/examples/lock-app/nxp/k32w/k32w0/BUILD.gn deleted file mode 100644 index c755514d66a175..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/BUILD.gn +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright (c) 2020 Project CHIP Authors -# -# 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("//build_overrides/chip.gni") -import("//build_overrides/k32w0_sdk.gni") -import("//build_overrides/openthread.gni") -import("${chip_root}/src/platform/device.gni") -import("${chip_root}/third_party/simw-top-mini/simw_config.gni") - -import("${k32w0_sdk_build_root}/k32w0_executable.gni") -import("${k32w0_sdk_build_root}/k32w0_sdk.gni") - -import("${chip_root}/src/crypto/crypto.gni") -import("${chip_root}/src/lib/core/core.gni") - -declare_args() { - chip_software_version = 0 -} - -if (chip_pw_tokenizer_logging) { - import("//build_overrides/pigweed.gni") - import("$dir_pw_tokenizer/database.gni") -} - -assert(current_os == "freertos") - -k32w0_platform_dir = "${chip_root}/examples/platform/nxp/k32w/k32w0" - -k32w0_sdk("sdk") { - sources = [ - "${k32w0_platform_dir}/app/project_include/OpenThreadConfig.h", - "include/CHIPProjectConfig.h", - "include/FreeRTOSConfig.h", - "main/include/app_config.h", - ] - - public_deps = - [ "${chip_root}/third_party/openthread/platforms:libopenthread-platform" ] - - include_dirs = [ - "main/include", - "main", - "include", - "${k32w0_platform_dir}/app/support", - "${k32w0_platform_dir}/util/include", - "${k32w0_platform_dir}/common", - ] - - defines = [] - if (is_debug) { - defines += [ "BUILD_RELEASE=0" ] - } else { - defines += [ "BUILD_RELEASE=1" ] - } - - if (chip_software_version != 0) { - defines += [ - "CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION=${chip_software_version}", - ] - } -} - -k32w0_executable("lock_app") { - output_name = "chip-k32w0x-lock-example" - - sources = [ - "${k32w0_platform_dir}/util/LEDWidget.cpp", - "${k32w0_platform_dir}/util/include/LEDWidget.h", - "main/AppTask.cpp", - "main/BoltLockManager.cpp", - "main/ZclCallbacks.cpp", - "main/include/AppEvent.h", - "main/include/AppTask.h", - "main/include/BoltLockManager.h", - "main/main.cpp", - ] - - if (chip_with_factory_data == 1 && use_custom_factory_provider == 1) { - sources += [ - "${k32w0_platform_dir}/common/CustomFactoryDataProvider.cpp", - "${k32w0_platform_dir}/common/CustomFactoryDataProvider.h", - ] - - defines = [ "CHIP_DEVICE_CONFIG_USE_CUSTOM_PROVIDER=1" ] - } - - deps = [ - ":sdk", - "${chip_root}/examples/common/QRCode", - "${chip_root}/examples/lock-app/nxp/zap/", - "${chip_root}/examples/providers:device_info_provider", - "${chip_root}/src/crypto", - "${chip_root}/src/lib", - "${chip_root}/src/platform:syscalls_stub", - "${chip_root}/third_party/mbedtls:mbedtls", - "${k32w0_platform_dir}/app/support:freertos_mbedtls_utils", - ] - - if (chip_openthread_ftd) { - deps += [ - "${chip_root}/third_party/openthread/repo:libopenthread-cli-ftd", - "${chip_root}/third_party/openthread/repo:libopenthread-ftd", - ] - } else { - deps += [ - "${chip_root}/third_party/openthread/repo:libopenthread-cli-mtd", - "${chip_root}/third_party/openthread/repo:libopenthread-mtd", - ] - } - - cflags = [ "-Wconversion" ] - - output_dir = root_out_dir - - ldscript = "${k32w0_platform_dir}/app/ldscripts/chip-k32w0x-linker.ld" - - inputs = [ ldscript ] - - ldflags = [ - "-T" + rebase_path(ldscript, root_build_dir), - "-Wl,-print-memory-usage", - ] -} - -if (chip_pw_tokenizer_logging) { - pw_tokenizer_database("lock_app.database") { - database = "$root_build_dir/chip-k32w0x-lock-example-database.bin" - create = "binary" - deps = [ ":lock_app" ] - optional_paths = [ "$root_build_dir/chip-k32w0x-lock-example" ] - } -} -group("k32w0") { - deps = [ - ":binsign", - ":lock_app", - ] - if (chip_pw_tokenizer_logging) { - deps += [ ":lock_app.database" ] - } -} - -action("binsign") { - deps = [ ":lock_app" ] - script = "${k32w0_platform_dir}/scripts/sign-outdir.py" - output_name = "bignsign.log" - outputs = [ "${root_build_dir}/${output_name}" ] -} - -group("default") { - deps = [ ":k32w0" ] -} diff --git a/examples/lock-app/nxp/k32w/k32w0/README.md b/examples/lock-app/nxp/k32w/k32w0/README.md deleted file mode 100644 index 49920906819b0f..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/README.md +++ /dev/null @@ -1,452 +0,0 @@ -# CHIP K32W061 Lock Example Application - -The Project CHIP K32W061 Lock Example demonstrates how to remotely control a -door lock device with one basic bolt. It uses buttons to test changing the lock -and device states and LEDs to show the state of these changes. You can use this -example as a reference for creating your own application. - -The example is based on -[Project CHIP](https://github.com/project-chip/connectedhomeip) and the NXP K32W -SDK, and supports remote access and control of a simulated door lock over a -low-power, 802.15.4 Thread network. - -The example behaves as a Project CHIP accessory, that is a device that can be -paired into an existing Project CHIP network and can be controlled by this -network. - -
- -- [CHIP K32W0 Lock Example Application](#chip-k32w061-lock-example-application) -- [Introduction](#introduction) - - [Bluetooth LE Advertising](#bluetooth-le-advertising) - - [Bluetooth LE Rendezvous](#bluetooth-le-rendezvous) -- [Device UI](#device-ui) -- [Building](#building) - - [Overwrite board config files](#overwrite-board-config-files) - - [Known issues building](#known-issues-building) -- [Manufacturing data](#manufacturing-data) -- [Flashing and debugging](#flashing-and-debugging) -- [Pigweed Tokenizer](#pigweed-tokenizer) - - [Detokenizer script](#detokenizer-script) - - [Notes](#notes) - - [Known issues](#known-issues-tokenizer) -- [NXP Ultrafast P256 ECC Library](#nxp-ultrafast-p256-ecc-library) - - [Building steps](#building-steps) -- [Tinycrypt ECC library](#tinycrypt-ecc-library) - - [Building steps](#building-steps-1) -- [Low power](#low-power) - - - [Known issues low power](#known-issues-low-power) - - - -## Introduction - -![K32W061 DK6](../../../../platform/nxp/k32w/k32w0/doc/images/k32w-dk6.jpg) - -The K32W061 lock example application provides a working demonstration of a -connected door lock device, built using the Project CHIP codebase and the NXP -K32W061 SDK. The example supports remote access (e.g.: using CHIP Tool from a -mobile phone) and control of a simulated door lock over a low-power, 802.15.4 -Thread network. It is capable of being paired into an existing Project CHIP -network along with other Project CHIP-enabled devices. - -The example targets the -[NXP K32W061 DK6](https://www.nxp.com/products/wireless/thread/k32w061-41-high-performance-secure-and-ultra-low-power-mcu-for-zigbeethread-and-bluetooth-le-5-0-with-built-in-nfc-option:K32W061_41) -development kit, but is readily adaptable to other K32W-based hardware. - -The CHIP device that runs the lock application is controlled by the CHIP -controller device over the Thread protocol. By default, the CHIP device has -Thread disabled, and it should be paired over Bluetooth LE with the CHIP -controller and obtain configuration from it. The actions required before -establishing full communication are described below. - -The example also comes with a test mode, which allows to start Thread with the -default settings by pressing a button. However, this mode does not guarantee -that the device will be able to communicate with the CHIP controller and other -devices. - -### SE051H Secure Element - -Deployment of this firmware configuration requires the K32W061 board setups -using the K32W061 module board, SE051 Expansion board and Generic Expansion -board as shown below: - -![SE051H + K32W061 DK6](../../../../platform/nxp/k32w/k32w0/doc/images/k32w-se.jpg) - -The SE051H Secure Element extension may be used for best in class security and -offloading some of the Project CHIP cryptographic operations. Depending on your -hardware configuration, choose one of the options below (building with or -without Secure Element). NOTE: the SE051H is a derivative of the SE051 product -family (see http://www.nxp.com/SE051) including dedicated CHIP support in -addition to the SE051 feature set. See the material provided separately by NXP -for more details on SE051H. - -### Bluetooth LE Advertising - -In this example, to commission the device onto a Project CHIP network, it must -be discoverable over Bluetooth LE. For security reasons, you must start -Bluetooth LE advertising manually after powering up the device by pressing -Button USERINTERFACE. - -### Bluetooth LE Rendezvous - -In this example, the commissioning procedure (called rendezvous) is done over -Bluetooth LE between a CHIP device and the CHIP controller, where the controller -has the commissioner role. - -To start the rendezvous, the controller must get the commissioning information -from the CHIP device. The data payload is encoded within a QR code, printed to -the UART console and shared using an NFC tag. For security reasons, you must -start NFC tag emulation manually after powering up the device by pressing -Button 4. - -### Thread Provisioning - -Last part of the rendezvous procedure, the provisioning operation involves -sending the Thread network credentials from the CHIP controller to the CHIP -device. As a result, device is able to join the Thread network and communicate -with other Thread devices in the network. - -## Device UI - -The example application provides a simple UI that depicts the state of the -device and offers basic user control. This UI is implemented via the -general-purpose LEDs and buttons built in to the OM15082 Expansion board -attached to the DK6 board. - -**LED D2** shows the overall state of the device and its connectivity. Four -states are depicted: - -- _Short Flash On (50ms on/950ms off)_ — The device is in an - unprovisioned (unpaired) state and is waiting for a commissioning - application to connect. - -* _Rapid Even Flashing (100ms on/100ms off)_ — The device is in an - unprovisioned state and a commissioning application is connected via BLE. - -- _Short Flash Off (950ms on/50ms off)_ — The device is full - provisioned, but does not yet have full network (Thread) or service - connectivity. - -* _Solid On_ — The device is fully provisioned and has full network and - service connectivity. - -**LED D3** shows the state of the simulated lock bolt. When the LED is lit the -bolt is extended (i.e. door locked); when not lit, the bolt is retracted (door -unlocked). The LED will flash whenever the simulated bolt is in motion from one -position to another. - -**Button SW2** can be used to reset the device to a default state. A short Press -Button SW2 initiates a factory reset. After an initial period of 3 seconds, LED2 -D2 and D3 will flash in unison to signal the pending reset. After 6 seconds will -cause the device to reset its persistent configuration and initiate a reboot. -The reset action can be cancelled by press SW2 button at any point before the 6 -second limit. - -**Button SW3** can be used to change the state of the simulated bolt. This can -be used to mimic a user manually operating the lock. The button behaves as a -toggle, swapping the state every time it is pressed. - -**Button SW4** can be used for joining a predefined Thread network advertised by -a Border Router. Default parameters for a Thread network are hard-coded and are -being used if this button is pressed. - -The remaining two LEDs (D1/D4) and button (SW1) are unused. - -Directly on the development board, **Button USERINTERFACE** can be used for -enabling Bluetooth LE advertising for a predefined period of time. Also, pushing -this button starts the NFC emulation by writing the onboarding information in -the NTAG. - -### No expansion board - -In case the **OM15082** Expansion board is not attached to the DK6 board, the -functionality of LED D2 and LED D3 is taken over by LED DS2, respectively LED -DS3, which can be found on the DK6 board. - -Also, by long pressing the **USERINTERFACE** button, the factory reset action -will be initiated. - -## Building - -In order to build the Project CHIP example, we recommend using a Linux -distribution (the demo-application was compiled on Ubuntu 20.04). - -Activate the Matter environment: - -```bash -user@ubuntu:~/Desktop/git/connectedhomeip$ source ./scripts/activate.sh -``` - -To bring the SDK in the environment, the user can: - -- download it with west tool, in which case it will be handled automatically - by gn: - - ```bash - user@ubuntu:~/Desktop/git/connectedhomeip$ cd third_party/nxp/k32w0_sdk/repo - user@ubuntu:~/Desktop/git/connectedhomeip/third_party/nxp/k32w0_sdk/repo$ west init -l manifest --mf west.yml - user@ubuntu:~/Desktop/git/connectedhomeip/third_party/nxp/k32w0_sdk/repo$ west update - ``` - - In case there are local modification to the already installed github NXP - SDK, use the below `west forall` command instead of the `west init` command - to reset the west workspace. Warning: all local changes will be lost after - running this command. - - ```bash - user@ubuntu:~/Desktop/git/connectedhomeip$ cd third_party/nxp/k32w0_sdk/repo - user@ubuntu:~/Desktop/git/connectedhomeip/third_party/nxp/k32w0_sdk/repo$ west forall -c "git reset --hard && git clean -xdf" -a - ``` - -- set up a custom path to the SDK, in which case - `k32w0_sdk_root=\"${NXP_K32W0_SDK_ROOT}\"` must be added to the `gn gen` - command: - - ``` - user@ubuntu:~/Desktop/git/connectedhomeip$ export NXP_K32W0_SDK_ROOT=/custom/path/to/SDK - ``` - -Start building the application: - -```bash -user@ubuntu:~/Desktop/git/connectedhomeip$ cd examples/lock-app/nxp/k32w/k32w0 -user@ubuntu:~/Desktop/git/connectedhomeip/examples/lock-app/nxp/k32w/k32w0$ gn gen out/debug --args="chip_with_OM15082=1 chip_with_ot_cli=0 is_debug=false chip_crypto=\"platform\" chip_with_se05x=0 chip_pw_tokenizer_logging=true" -user@ubuntu:~/Desktop/git/connectedhomeip/examples/lock-app/nxp/k32w/k32w0$ ninja -C out/debug -``` - -To build with Secure Element, follow the same steps as above but set -`chip_with_se05x=1` in the `gn gen` command. - -- K32W041AM flavor - - Exactly the same steps as above but set argument `build_for_k32w041am=1` in - the gn command. - -Also, in case the OM15082 Expansion Board is not attached to the DK6 board, the -build argument (`chip_with_OM15082`) inside the gn build instruction should be -set to zero. The argument `chip_with_OM15082` is set to zero by default. - -In case that Openthread CLI is needed, `chip_with_ot_cli` build argument must be -set to 1. - -In case the board doesn't have 32KHz crystal fitted, one can use the 32KHz free -running oscillator as a clock source. In this case one must set the -`use_fro_32k` argument to 1. - -In case signing errors are encountered when running the "sign_images.sh" script -(run automatically) install the recommanded packages (python version > 3, pip3, -pycrypto, pycryptodome): - -``` -user@ubuntu:~$ python3 --version -Python 3.8.2 -user@ubuntu:~$ pip3 --version -pip 20.0.2 from /usr/lib/python3/dist-packages/pip (python 3.8) -user@ubuntu:~$ pip3 list | grep -i pycrypto -pycrypto 2.6.1 -pycryptodome 3.9.8 -``` - -The resulting output file can be found in out/debug/chip-k32w0x-lock-example. - -### Overwrite board config files - -The example uses template/reference board configuration files. - -To overwrite the board configuration files, set `override_is_DK6=false` in the -`k32w0_sdk` target from the app `BUILD.gn`: - -``` -k32w0_sdk("sdk") { - override_is_DK6 = false - ... -} -``` - -This variable will be used by `k32w0_sdk.gni` to overwrite `chip_with_DK6` -option, thus the reference board configuration files will no longer be used. - -## Known issues building - -- When using Secure element and cross-compiling on Linux, log messages from - the Plug&Trust middleware stack may not echo to the console. - -## Rotating device id - -This is an optional feature and can be used in multiple ways (please see section -5.4.2.4.5 from Matter specification). One use case is Amazon Frustration Free -Setup, which leverages the C3 Characteristic (Additional commissioning-related -data) to offer an easier way to set up the device. The rotating device id will -be encoded in this additional data and is programmed to rotate at pre-defined -moments. The algorithm uses a unique per-device identifier that must be -programmed during factory provisioning. - -Please use the following build args: - -- `chip_enable_rotating_device_id=1` - to enable rotating device id. -- `chip_enable_additional_data_advertising=1` - to enable C3 characteristic. - -## Manufacturing data - -See -[Guide for writing manufacturing data on NXP devices](../../../../../docs/guides/nxp_manufacturing_flow.md). - -There are factory data generated binaries available in -examples/platform/nxp/k32w/k32w0/scripts/demo_generated_factory_data folder. -These are based on the DAC, PAI and PAA certificates found in -scripts/tools/nxp/demo_generated_certs folder. The demo_factory_data_dut1.bin -uses the DAC certificate and private key found in -examples/platform/nxp/k32w/k32w0/scripts/demo_generated_factory_data/dac/dut1 -folder. The demo_factory_data_dut2.bin uses the DAC certificate and private key -found in -examples/platform/nxp/k32w/k32w0/scripts/demo_generated_factory_data/dac/dut2 -folder. These two factory data binaries can be used for testing topologies with -2 DUTS. They contain the corresponding DACs/PAIs generated using -generate_nxp_chip_factory_bin.py script. The discriminator is 14014 and the -passcode is 1000. These demo certificates are working with the CDs installed in -CHIPProjectConfig.h. - -Regarding factory data provider, there are two options: - -- use the default factory data provider: `FactoryDataProviderImpl` by setting - `chip_with_factory_data=1` in the gn build command. -- use a custom factory data provider: please see - [Guide for implementing a custom factory data provider](../../../../platform/nxp/k32w/k32w0/common/README.md). - This can be enabled when `chip_with_factory_data=1` by setting - `use_custom_factory_provider=1` in the gn build command. - -## Flashing and debugging - -Program the firmware using the official -[OpenThread Flash Instructions](https://github.com/openthread/ot-nxp/tree/main/src/k32w0/k32w061#flash-binaries). - -All you have to do is to replace the Openthread binaries from the above -documentation with _out/debug/chip-k32w0x-lock-example.bin_ if DK6Programmer is -used or with _out/debug/chip-k32w0x-lock-example_ if MCUXpresso is used. - -## Pigweed tokenizer - -The tokenizer is a pigweed module that allows hashing the strings. This greatly -reduces the flash needed for logs. The module can be enabled by building with -the gn argument _chip_pw_tokenizer_logging=true_. The detokenizer script is -needed for parsing the hashed scripts. - -### Detokenizer script - -The python3 script detokenizer.py is a script that decodes the tokenized logs -either from a file or from a serial port. It is located in the following path -`examples/platform/nxp/k32w/k32w0/scripts/detokenizer.py`. - -The script can be used in the following ways: - -``` -usage: detokenizer.py serial [-h] -i INPUT -d DATABASE [-o OUTPUT] -usage: detokenizer.py file [-h] -i INPUT -d DATABASE -o OUTPUT -``` - -The first parameter is either _serial_ or _file_ and it selects between decoding -from a file or from a serial port. - -The second parameter is _-i INPUT_ and it must se set to the path of the file or -the serial to decode from. - -The third parameter is _-d DATABASE_ and represents the path to the token -database to be used for decoding. The default path is -_out/debug/chip-k32w0x-lock-example-database.bin_ after a successful build. - -The forth parameter is _-o OUTPUT_ and it represents the path to the output file -where the decoded logs will be stored. This parameter is required for file usage -and optional for serial usage. If not provided when used with serial port, it -will show the decoded log only at the stdout and not save it to file. - -### Notes - -The token database is created automatically after building the binary if the -argument _chip_pw_tokenizer_logging=true_ was used. - -The detokenizer script must be run inside the example's folder after a -successful run of the _scripts/activate.sh_ script. The pw_tokenizer module used -by the script is loaded by the environment. An example of running the -detokenizer script to see logs of a lock app: - -``` -python3 ../../../../../examples/platform/nxp/k32w/k32w0/scripts/detokenizer.py serial -i /dev/ttyACM0 -d out/debug/chip-k32w0x-lock-example-database.bin -o device.txt -``` - -### Known issues tokenizer - -The building process will not update the token database if it already exists. In -case that new strings are added and the database already exists in the output -folder, it must be deleted so that it will be recreated at the next build. - -Not all tokens will be decoded. This is due to a gcc/pw_tokenizer issue. The -pw_tokenizer creates special elf sections using attributes where the tokens and -strings will be stored. This sections will be used by the database creation -script. For template C++ functions, gcc ignores these attributes and places all -the strings by default in the .rodata section. As a result the database creation -script won't find them in the special-created sections. - -If run, closed and rerun with the serial option on the same serial port, the -detokenization script will get stuck and not show any logs. The solution is to -unplug and plug the board and then rerun the script. - -## NXP Ultrafast P256 ECC Library - -### Building steps - -By default, the application builds with NXP Ultrafast P256 ECC Library. To build -with this library, use the following arguments: - -- Build without Secure element (_chip_with_se05x=0_) and with crypto platform - (_chip_crypto=\"platform\"_). - -To stop using Ultrafast P256 ECC Library, simply build with -_chip_crypto=\"mbedtls\"_ or with Tinycrypt. - -## Tinycrypt ECC library - -### Building steps - -In order to use the Tinycrypt ECC library, use the following build arguments: - -- Build without Secure element (_chip_with_se05x=0_), with crypto platform - (_chip_crypto=\"platform\"_) and with tinycrypt selected - (_chip_crypto_flavor=\"tinycrypt\"_). - -## Low power - -The example also offers the possibility to run in low power mode. This means -that the board will go in a deep power down mode most of the time and the power -consumption will be very low. - -In order to build with low power support, the _chip_with_low_power=1_ must be -provided to the build system. In this case, please note that the GN build -arguments _chip_with_OM15082_ and _chip_with_ot_cli_ must be set to 0 and -_chip_logging_ must be set to false to disable logging. - -In order to maintain a low power consumption, the LEDs showing the state of the -elock and the internal state are disabled. Console logs can be used instead. -Also, please note that once the board is flashed with MCUXpresso the debugger -disconnects because the board enters low power. - -Power Measurement Tool can be used inside MCUXpresso for checking the power -consumption pattern: Window -> Show View -> Other -> Power Measurement Tool. The -configuration for this tool is the next one: - -![POWER_CONF](../../../../platform/nxp/k32w/k32w0/doc/images/power_conf.JPG) - -Also, please make sure that the J14 jumper is set to the _ENABLED_ position and -no expansion board is attached to the DK6. A view from this tool is illustrated -below: - -![POWER_VIEW](../../../../platform/nxp/k32w/k32w0/doc/images/power_view.JPG) - -Please note that that the Power Measurement Tool is not very accurate and -professional tools must be used if exact power consumption needs to be known. - -## Known issues low power - -- Power Measurement Tool may not work correctly in MCUXpresso versions greater - that 11.0.1. diff --git a/examples/lock-app/nxp/k32w/k32w0/args.gni b/examples/lock-app/nxp/k32w/k32w0/args.gni deleted file mode 100644 index a733921b547e38..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/args.gni +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (c) 2020 Project CHIP Authors -# -# 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("//build_overrides/chip.gni") -import("${chip_root}/config/standalone/args.gni") -import("${chip_root}/examples/platform/nxp/k32w/k32w0/args.gni") - -# SDK target. This is overridden to add our SDK app_config.h & defines. -k32w0_sdk_target = get_label_info(":sdk", "label_no_toolchain") - -chip_stack_lock_tracking = "fatal" -chip_enable_ble = true - -chip_enable_icd_server = true -chip_persist_subscriptions = true -chip_subscription_timeout_resumption = true diff --git a/examples/lock-app/nxp/k32w/k32w0/build_overrides b/examples/lock-app/nxp/k32w/k32w0/build_overrides deleted file mode 120000 index ad07557834803a..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/build_overrides +++ /dev/null @@ -1 +0,0 @@ -../../../../build_overrides/ \ No newline at end of file diff --git a/examples/lock-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h b/examples/lock-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h deleted file mode 100644 index 43598870fb7533..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/include/CHIPProjectConfig.h +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (c) 2020 Project CHIP Authors - * Copyright (c) 2020 Google LLC. - * 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. - */ - -/** - * @file - * Example project configuration file for CHIP. - * - * This is a place to put application or project-specific overrides - * to the default configuration values for general CHIP features. - * - */ - -#pragma once - -// Security and Authentication disabled for development build. -// For convenience, enable CHIP Security Test Mode and disable the requirement for -// authentication in various protocols. -// WARNING: These options make it possible to circumvent basic CHIP security functionality, -// including message encryption. Because of this they MUST NEVER BE ENABLED IN PRODUCTION BUILDS. -#define CHIP_CONFIG_SECURITY_TEST_MODE 0 - -// Use hard-coded test certificates already embedded in generic chip code => set it to 0 -// Use real/development certificates => set it to 1 + file the provisioning section from -// the internal flash -#ifndef CONFIG_CHIP_LOAD_REAL_FACTORY_DATA -#define CONFIG_CHIP_LOAD_REAL_FACTORY_DATA 0 -#endif - -#if CONFIG_CHIP_LOAD_REAL_FACTORY_DATA - -// Enable usage of custom factory data provider -#ifndef CHIP_DEVICE_CONFIG_USE_CUSTOM_PROVIDER -#define CHIP_DEVICE_CONFIG_USE_CUSTOM_PROVIDER 0 -#endif - -// VID/PID for product => will be used by Basic Information Cluster -#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID 0x1037 -#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0xA220 - -// Set the following define to use the Certification Declaration from below and not use it stored in factory data section -#ifndef CHIP_USE_DEVICE_CONFIG_CERTIFICATION_DECLARATION -#define CHIP_USE_DEVICE_CONFIG_CERTIFICATION_DECLARATION 0 -#endif - -#ifndef CHIP_DEVICE_CONFIG_CERTIFICATION_DECLARATION -//-> format_version = 1 -//-> vendor_id = 0x1037 -//-> product_id_array = [ 0xA220 ] -//-> device_type_id = 0x0015 -//-> certificate_id = "ZIG20142ZB330003-24" -//-> security_level = 0 -//-> security_information = 0 -//-> version_number = 0x2694 -//-> certification_type = 0 -//-> dac_origin_vendor_id is not present -//-> dac_origin_product_id is not present -#define CHIP_DEVICE_CONFIG_CERTIFICATION_DECLARATION \ - { \ - 0x30, 0x81, 0xe8, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, 0xa0, 0x81, 0xda, 0x30, 0x81, 0xd7, \ - 0x02, 0x01, 0x03, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, \ - 0x44, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x37, 0x04, 0x35, 0x15, 0x24, 0x00, \ - 0x01, 0x25, 0x01, 0x37, 0x10, 0x36, 0x02, 0x05, 0x20, 0xa2, 0x18, 0x24, 0x03, 0x15, 0x2c, 0x04, 0x13, 0x5a, 0x49, \ - 0x47, 0x32, 0x30, 0x31, 0x34, 0x31, 0x5a, 0x42, 0x33, 0x33, 0x30, 0x30, 0x30, 0x31, 0x2d, 0x32, 0x34, 0x24, 0x05, \ - 0x00, 0x24, 0x06, 0x00, 0x25, 0x07, 0x76, 0x98, 0x24, 0x08, 0x00, 0x18, 0x31, 0x7d, 0x30, 0x7b, 0x02, 0x01, 0x03, \ - 0x80, 0x14, 0x62, 0xfa, 0x82, 0x33, 0x59, 0xac, 0xfa, 0xa9, 0x96, 0x3e, 0x1c, 0xfa, 0x14, 0x0a, 0xdd, 0xf5, 0x04, \ - 0xf3, 0x71, 0x60, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0a, 0x06, \ - 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x04, 0x47, 0x30, 0x45, 0x02, 0x20, 0x29, 0x7f, 0xf6, 0x0e, \ - 0x4c, 0x72, 0x38, 0x77, 0x19, 0xfc, 0xb9, 0x7c, 0x08, 0x71, 0x8a, 0x88, 0x61, 0xf7, 0x0a, 0xf7, 0xf0, 0x63, 0x43, \ - 0x09, 0x69, 0xb0, 0x40, 0xf2, 0xce, 0xfd, 0x69, 0x15, 0x02, 0x21, 0x00, 0xa8, 0xd1, 0x6e, 0xae, 0xd5, 0x7f, 0x28, \ - 0x34, 0x0d, 0x21, 0x65, 0xa3, 0x3f, 0xc4, 0x4c, 0x8b, 0x26, 0x26, 0x3f, 0xb2, 0xac, 0x8f, 0x22, 0xe4, 0xff, 0xbd, \ - 0x9b, 0x48, 0x2d, 0x7e, 0xc9, 0x17, \ - } - -// All remaining data will be pulled from the provisioning region of flash. -#endif - -#else - -// Use a default setup PIN code if one hasn't been provisioned in flash. -#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 -#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 - -/** - * CHIP_DEVICE_CONFIG_TEST_SERIAL_NUMBER - * - * Enables the use of a hard-coded default serial number if none - * is found in CHIP NV storage. - */ -#define CHIP_DEVICE_CONFIG_TEST_SERIAL_NUMBER "TEST_SN" - -/** - * CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID - * - * 0xFFF1: Test vendor. - */ -#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID 0xFFF1 - -/** - * CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID - * - * 0x8006: example lock-app - */ -#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0x8006 - -#endif - -/** - * CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION - * - * The hardware version number assigned to device or product by the device vendor. This - * number is scoped to the device product id, and typically corresponds to a revision of the - * physical device, a change to its packaging, and/or a change to its marketing presentation. - * This value is generally *not* incremented for device software versions. - */ -#define CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION 100 - -#ifndef CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING -#define CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING "v0.1.0" -#endif - -/** - * CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING - * - * A string identifying the software version running on the device. - * CHIP currently expects the software version to be in the format - * {MAJOR_VERSION}.0d{MINOR_VERSION} - */ -#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING -#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING "03-2022-te8" -#endif - -#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION -#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION 42020 -#endif - -#ifndef CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME -#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME "NXP Semiconductors" -#endif - -#ifndef CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME -#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME "NXP Demo App" -#endif - -/** - * CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_TIMEOUT - * - * The amount of time in miliseconds after which BLE should change his advertisements - * from fast interval to slow interval. - * - * 30000 (30 secondes). - */ -#define CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_TIMEOUT (30 * 1000) - -/** - * @def CHIP_CONFIG_MAX_FABRICS - * - * @brief - * Maximum number of fabrics the device can participate in. Each fabric can - * provision the device with its unique operational credentials and manage - * its own access control lists. - */ -#define CHIP_CONFIG_MAX_FABRICS 5 // 5 is the minimum number of supported fabrics - -#define CHIP_DEVICE_CONFIG_ENABLE_SED 1 - -/** - * CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE - * - * For a development build, set the default importance of events to be logged as Debug. - * Since debug is the lowest importance level, this means all standard, critical, info and - * debug importance level vi events get logged. - */ -#if BUILD_RELEASE -#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Production -#else -#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Debug -#endif // BUILD_RELEASE - -#define CHIP_DEVICE_CONFIG_ENABLE_EXTENDED_DISCOVERY 1 - -/** - * CHIP_DEVICE_CONFIG_BLE_SET_PHY_2M_REQ - * - * This define enables/disables the Gap_LeSetPhy request to switch to 2M. - * It is disabled here for interoperability reasons just to be extra cautious. - * Both devices may send a Link Layer control procedure in parallel resulting in a - * LPM Error Transaction Collision. - * If the peer device doesn't accept our reject command, this can result in a BLE - * connection timeout. - */ -#define CHIP_DEVICE_CONFIG_BLE_SET_PHY_2M_REQ 0 - -/** - * @def CHIP_IM_MAX_NUM_COMMAND_HANDLER - * - * @brief Defines the maximum number of CommandHandler, limits the number of active commands transactions on server. - */ -#define CHIP_IM_MAX_NUM_COMMAND_HANDLER 1 - -/** - * @def CHIP_IM_MAX_NUM_WRITE_HANDLER - * - * @brief Defines the maximum number of WriteHandler, limits the number of active write transactions on server. - */ -#define CHIP_IM_MAX_NUM_WRITE_HANDLER 2 diff --git a/examples/lock-app/nxp/k32w/k32w0/include/FreeRTOSConfig.h b/examples/lock-app/nxp/k32w/k32w0/include/FreeRTOSConfig.h deleted file mode 100644 index c0e0a81a8fbf42..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/include/FreeRTOSConfig.h +++ /dev/null @@ -1,184 +0,0 @@ -/* - * FreeRTOS Kernel V10.2.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * http://www.FreeRTOS.org - * http://aws.amazon.com/freertos - * - * 1 tab == 4 spaces! - */ - -#pragma once - -/*----------------------------------------------------------- - * Application specific definitions. - * - * These definitions should be adjusted for your particular hardware and - * application requirements. - * - * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE - * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. - * - * See http://www.freertos.org/a00110.html. - *----------------------------------------------------------*/ - -#define configUSE_PREEMPTION 1 - -#if defined(chip_with_low_power) && (chip_with_low_power == 1) -#define configUSE_TICKLESS_IDLE 1 -#else -#define configUSE_TICKLESS_IDLE 0 -#endif - -#define configCPU_CLOCK_HZ (SystemCoreClock) -#define configTICK_RATE_HZ ((TickType_t) 100) -#define configMAX_PRIORITIES (8) - -#if defined(configUSE_TICKLESS_IDLE) && (configUSE_TICKLESS_IDLE == 1) -#define configMINIMAL_STACK_SIZE ((unsigned short) 250) -#else -#define configMINIMAL_STACK_SIZE ((unsigned short) 450) -#endif - -#define configMAX_TASK_NAME_LEN 20 -#define configUSE_16_BIT_TICKS 0 -#define configIDLE_SHOULD_YIELD 1 -#define configUSE_TASK_NOTIFICATIONS 1 -#define configUSE_MUTEXES 1 -#define configUSE_RECURSIVE_MUTEXES 1 -#define configUSE_COUNTING_SEMAPHORES 1 -#define configUSE_ALTERNATIVE_API 0 /* Deprecated! */ -#define configQUEUE_REGISTRY_SIZE 8 -#define configUSE_QUEUE_SETS 0 -/* make sure that Thread task can interrupt lengthy Matter - * processing in case priority inversion occurs - */ -#define configUSE_TIME_SLICING 1 -#define configUSE_NEWLIB_REENTRANT 0 -#define configENABLE_BACKWARD_COMPATIBILITY 1 -#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 - -/* Tasks.c additions (e.g. Thread Aware Debug capability) */ -#define configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H 1 - -/* Used memory allocation (heap_x.c) */ -#define configFRTOS_MEMORY_SCHEME 4 - -/* Memory allocation related definitions. */ -#define configSUPPORT_STATIC_ALLOCATION 0 -#define configSUPPORT_DYNAMIC_ALLOCATION 1 -#define configTOTAL_HEAP_SIZE ((size_t) (gTotalHeapSize_c)) -#define configAPPLICATION_ALLOCATED_HEAP 1 - -#define configUSE_IDLE_HOOK 1 -#define configUSE_TICK_HOOK 0 -#define configCHECK_FOR_STACK_OVERFLOW 0 -#ifndef configUSE_MALLOC_FAILED_HOOK -#define configUSE_MALLOC_FAILED_HOOK 0 -#endif -#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 - -/* Run time and task stats gathering related definitions. */ -#define configGENERATE_RUN_TIME_STATS 0 -#define configUSE_TRACE_FACILITY 1 -#define configUSE_STATS_FORMATTING_FUNCTIONS 0 - -/* Task aware debugging. */ -#define configRECORD_STACK_HIGH_ADDRESS 1 - -/* Co-routine related definitions. */ -#define configUSE_CO_ROUTINES 0 -#define configMAX_CO_ROUTINE_PRIORITIES 2 - -/* Software timer related definitions. */ -#define configUSE_TIMERS 1 -#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) -#define configTIMER_QUEUE_LENGTH 10 -#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 4) - -/* Define to trap errors during development. */ -#if defined gLoggingActive_d && (gLoggingActive_d != 0) -#include "dbg_logging.h" -#define configASSERT(x) \ - if ((x) == 0) \ - { \ - taskDISABLE_INTERRUPTS(); \ - DbgLogDump(1); \ - } -#else -#define configASSERT(x) \ - if ((x) == 0) \ - { \ - taskDISABLE_INTERRUPTS(); \ - for (;;) \ - ; \ - } -#endif - -/* Optional functions - most linkers will remove unused functions anyway. */ -#define INCLUDE_vTaskPrioritySet 1 -#define INCLUDE_uxTaskPriorityGet 1 -#define INCLUDE_vTaskDelete 1 -#define INCLUDE_vTaskSuspend 1 -#define INCLUDE_xResumeFromISR 1 -#define INCLUDE_vTaskDelayUntil 1 -#define INCLUDE_vTaskDelay 1 -#define INCLUDE_xTaskGetSchedulerState 1 -#define INCLUDE_xTaskGetCurrentTaskHandle 1 -#define INCLUDE_uxTaskGetStackHighWaterMark 1 -#define INCLUDE_xTaskGetIdleTaskHandle 0 -#define INCLUDE_eTaskGetState 0 -#define INCLUDE_xEventGroupSetBitFromISR 1 -#define INCLUDE_xTimerPendFunctionCall 1 -#define INCLUDE_xTaskAbortDelay 0 -#define INCLUDE_xTaskGetHandle 0 -#define INCLUDE_xTaskResumeFromISR 1 -#define INCLUDE_xQueueGetMutexHolder 1 - -/* Interrupt nesting behaviour configuration. Cortex-M specific. */ -#ifdef __NVIC_PRIO_BITS -/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */ -#define configPRIO_BITS __NVIC_PRIO_BITS -#else -#define configPRIO_BITS 3 -#endif - -/* The lowest interrupt priority that can be used in a call to a "set priority" -function. */ -#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x7 - -/* The highest interrupt priority that can be used by any interrupt service -routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL -INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER -PRIORITY THAN THIS! (higher priorities are lower numeric values. */ -#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 1 - -/* Interrupt priorities used by the kernel port layer itself. These are generic -to all Cortex-M ports, and do not rely on any particular library functions. */ -#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) -/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! -See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ -#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) - -/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS -standard names. */ -#define vPortSVCHandler SVC_Handler -#define xPortPendSVHandler PendSV_Handler -#define xPortSysTickHandler SysTick_Handler diff --git a/examples/lock-app/nxp/k32w/k32w0/main/AppTask.cpp b/examples/lock-app/nxp/k32w/k32w0/main/AppTask.cpp index 7c6cffaf968c02..5880a6e07c784b 100644 --- a/examples/lock-app/nxp/k32w/k32w0/main/AppTask.cpp +++ b/examples/lock-app/nxp/k32w/k32w0/main/AppTask.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include "Keyboard.h" @@ -41,6 +42,9 @@ #include "PWR_Interface.h" #include "app_config.h" +#if CHIP_CRYPTO_HSM +#include +#endif #ifdef ENABLE_HSM_DEVICE_ATTESTATION #include "DeviceAttestationSe05xCredsExample.h" #endif diff --git a/examples/lock-app/nxp/k32w/k32w0/main/BoltLockManager.cpp b/examples/lock-app/nxp/k32w/k32w0/main/BoltLockManager.cpp deleted file mode 100644 index 6a18ef8214e727..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/main/BoltLockManager.cpp +++ /dev/null @@ -1,235 +0,0 @@ -/* - * - * Copyright (c) 2020 Project CHIP Authors - * Copyright (c) 2019 Google LLC. - * 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 "BoltLockManager.h" - -#include "AppTask.h" -#include "FreeRTOS.h" - -#include "app_config.h" - -BoltLockManager BoltLockManager::sLock; - -TimerHandle_t sLockTimer; // FreeRTOS app sw timer. - -int BoltLockManager::Init() -{ - int err = 0; - - // Create FreeRTOS sw timer for Lock timer. - - sLockTimer = xTimerCreate("LockTmr", // Just a text name, not used by the RTOS kernel - 1, // == default timer period (mS) - false, // no timer reload (==one-shot) - (void *) this, // init timer id = lock obj context - TimerEventHandler // timer callback handler - ); - - if (sLockTimer == NULL) - { - K32W_LOG("lock timer create failed"); - assert(0); - } - - mState = kState_LockingCompleted; - mAutoLockTimerArmed = false; - mAutoRelock = false; - mAutoLockDuration = 0; - - return err; -} - -void BoltLockManager::SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB) -{ - mActionInitiated_CB = aActionInitiated_CB; - mActionCompleted_CB = aActionCompleted_CB; -} - -bool BoltLockManager::IsActionInProgress() -{ - return (mState == kState_LockingInitiated || mState == kState_UnlockingInitiated) ? true : false; -} - -bool BoltLockManager::IsUnlocked() -{ - return (mState == kState_UnlockingCompleted) ? true : false; -} - -void BoltLockManager::EnableAutoRelock(bool aOn) -{ - mAutoRelock = aOn; -} - -void BoltLockManager::SetAutoLockDuration(uint32_t aDurationInSecs) -{ - mAutoLockDuration = aDurationInSecs; -} - -bool BoltLockManager::InitiateAction(int32_t aActor, Action_t aAction) -{ - bool action_initiated = false; - State_t new_state; - - // Initiate Lock/Unlock Action only when the previous one is complete. - if (mState == kState_LockingCompleted && aAction == UNLOCK_ACTION) - { - action_initiated = true; - - new_state = kState_UnlockingInitiated; - } - else if (mState == kState_UnlockingCompleted && aAction == LOCK_ACTION) - { - action_initiated = true; - - new_state = kState_LockingInitiated; - } - - if (action_initiated) - { - if (mAutoLockTimerArmed && new_state == kState_LockingInitiated) - { - // If auto lock timer has been armed and someone initiates locking, - // cancel the timer and continue as normal. - mAutoLockTimerArmed = false; - - CancelTimer(); - } - - StartTimer(ACTUATOR_MOVEMENT_PERIOS_MS); - - // Since the timer started successfully, update the state and trigger callback - mState = new_state; - - if (mActionInitiated_CB) - { - mActionInitiated_CB(aAction, aActor); - } - } - - return action_initiated; -} - -void BoltLockManager::StartTimer(uint32_t aTimeoutMs) -{ - if (xTimerIsTimerActive(sLockTimer)) - { - K32W_LOG("lock timer already started!"); - // appError(err); - CancelTimer(); - } - - // timer is not active, change its period to required value. - // This also causes the timer to start. FreeRTOS- Block for a maximum of - // 100 ticks if the change period command cannot immediately be sent to the - // timer command queue. - if (xTimerChangePeriod(sLockTimer, aTimeoutMs / portTICK_PERIOD_MS, 100) != pdPASS) - { - K32W_LOG("lock timer start() failed"); - // appError(err); - } -} - -void BoltLockManager::CancelTimer(void) -{ - if (xTimerStop(sLockTimer, 0) == pdFAIL) - { - K32W_LOG("lock timer stop() failed"); - // appError(err); - } -} - -void BoltLockManager::TimerEventHandler(TimerHandle_t xTimer) -{ - // Get lock obj context from timer id. - BoltLockManager * lock = static_cast(pvTimerGetTimerID(xTimer)); - - // The timer event handler will be called in the context of the timer task - // once sLockTimer expires. Post an event to apptask queue with the actual handler - // so that the event can be handled in the context of the apptask. - AppEvent event; - event.Type = AppEvent::kEventType_Timer; - event.TimerEvent.Context = lock; - - if (lock->mAutoLockTimerArmed) - { - event.Handler = AutoReLockTimerEventHandler; - GetAppTask().PostEvent(&event); - } - else - { - event.Handler = ActuatorMovementTimerEventHandler; - GetAppTask().PostEvent(&event); - } -} - -void BoltLockManager::AutoReLockTimerEventHandler(void * aGenericEvent) -{ - AppEvent * aEvent = (AppEvent *) aGenericEvent; - BoltLockManager * lock = static_cast(aEvent->TimerEvent.Context); - int32_t actor = 0; - - // Make sure auto lock timer is still armed. - if (!lock->mAutoLockTimerArmed) - { - return; - } - - lock->mAutoLockTimerArmed = false; - - K32W_LOG("Auto Re-Lock has been triggered!"); - - lock->InitiateAction(actor, LOCK_ACTION); -} - -void BoltLockManager::ActuatorMovementTimerEventHandler(void * aGenericEvent) -{ - AppEvent * aEvent = (AppEvent *) aGenericEvent; - Action_t actionCompleted = INVALID_ACTION; - - BoltLockManager * lock = static_cast(aEvent->TimerEvent.Context); - - if (lock->mState == kState_LockingInitiated) - { - lock->mState = kState_LockingCompleted; - actionCompleted = LOCK_ACTION; - } - else if (lock->mState == kState_UnlockingInitiated) - { - lock->mState = kState_UnlockingCompleted; - actionCompleted = UNLOCK_ACTION; - } - - if (actionCompleted != INVALID_ACTION) - { - if (lock->mActionCompleted_CB) - { - lock->mActionCompleted_CB(actionCompleted); - } - - if (lock->mAutoRelock && actionCompleted == UNLOCK_ACTION) - { - // Start the timer for auto relock - lock->StartTimer(lock->mAutoLockDuration * 1000); - - lock->mAutoLockTimerArmed = true; - - K32W_LOG("Auto Re-lock enabled. Will be triggered in %u seconds", lock->mAutoLockDuration); - } - } -} diff --git a/examples/lock-app/nxp/k32w/k32w0/main/ZclCallbacks.cpp b/examples/lock-app/nxp/k32w/k32w0/main/ZclCallbacks.cpp deleted file mode 100644 index b888db03861c7f..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/main/ZclCallbacks.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * - * Copyright (c) 2020 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 - -#include "AppTask.h" -#include "BoltLockManager.h" -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace ::chip; -using namespace ::chip::app::Clusters; -using namespace ::chip::app::Clusters::DoorLock; -using ::chip::app::DataModel::Nullable; - -bool emberAfPluginDoorLockOnDoorLockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, - const Nullable & nodeId, const Optional & pinCode, - OperationErrorEnum & err) -{ - return BoltLockMgr().InitiateAction(0, BoltLockManager::LOCK_ACTION); -} - -bool emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const Nullable & fabricIdx, - const Nullable & nodeId, const Optional & pinCode, - OperationErrorEnum & err) -{ - return BoltLockMgr().InitiateAction(0, BoltLockManager::UNLOCK_ACTION); -} diff --git a/examples/lock-app/nxp/k32w/k32w0/main/include/AppTask.h b/examples/lock-app/nxp/k32w/k32w0/main/include/AppTask.h deleted file mode 100644 index 1addd9236c4bcf..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/main/include/AppTask.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * - * Copyright (c) 2020 Google LLC. - * 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. - */ - -#pragma once - -#include -#include - -#include "AppEvent.h" -#include "BoltLockManager.h" - -#include "CHIPProjectConfig.h" - -#if CONFIG_CHIP_LOAD_REAL_FACTORY_DATA -#include -#if CHIP_DEVICE_CONFIG_USE_CUSTOM_PROVIDER -#include "CustomFactoryDataProvider.h" -#endif -#endif - -#include - -#include "FreeRTOS.h" -#include "timers.h" - -class AppTask -{ -public: - CHIP_ERROR StartAppTask(); - static void AppTaskMain(void * pvParameter); - - void PostLockActionRequest(int32_t aActor, BoltLockManager::Action_t aAction); - void PostEvent(const AppEvent * event); - - void UpdateClusterState(void); - -private: - friend AppTask & GetAppTask(void); - - CHIP_ERROR Init(); - - static void ActionInitiated(BoltLockManager::Action_t aAction, int32_t aActor); - static void ActionCompleted(BoltLockManager::Action_t aAction); - - void CancelTimer(void); - - void DispatchEvent(AppEvent * event); - - static void FunctionTimerEventHandler(void * aGenericEvent); - static void KBD_Callback(uint8_t events); - static void HandleKeyboard(void); - static void JoinHandler(void * aGenericEvent); - static void BleHandler(void * aGenericEvent); - static void BleStartAdvertising(intptr_t arg); - static void LockActionEventHandler(void * aGenericEvent); - static void ResetActionEventHandler(void * aGenericEvent); - static void InstallEventHandler(void * aGenericEvent); - - static void ButtonEventHandler(uint8_t pin_no, uint8_t button_action); - static void TimerEventHandler(TimerHandle_t xTimer); - - static void MatterEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); - static void UpdateClusterStateInternal(intptr_t arg); - static void ThreadStart(); - static void InitServer(intptr_t arg); - static void PrintOnboardingInfo(); - void StartTimer(uint32_t aTimeoutInMs); - - enum Function_t - { - kFunction_NoneSelected = 0, - kFunction_SoftwareUpdate = 0, - kFunction_FactoryReset, - kFunctionLockUnlock, - - kFunction_Invalid - } Function; - - Function_t mFunction = kFunction_NoneSelected; - bool mResetTimerActive = false; - bool mSyncClusterToButtonAction = false; - - static AppTask sAppTask; -}; - -inline AppTask & GetAppTask(void) -{ - return AppTask::sAppTask; -} diff --git a/examples/lock-app/nxp/k32w/k32w0/main/include/BoltLockManager.h b/examples/lock-app/nxp/k32w/k32w0/main/include/BoltLockManager.h deleted file mode 100644 index cd33fada37b4fb..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/main/include/BoltLockManager.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * - * Copyright (c) 2020 Google LLC. - * 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. - */ - -#pragma once - -#include -#include - -#include "AppEvent.h" - -#include "FreeRTOS.h" -#include "timers.h" // provides FreeRTOS timer support - -class BoltLockManager -{ -public: - enum Action_t - { - LOCK_ACTION = 0, - UNLOCK_ACTION, - - INVALID_ACTION - } Action; - - enum State_t - { - kState_LockingInitiated = 0, - kState_LockingCompleted, - kState_UnlockingInitiated, - kState_UnlockingCompleted, - } State; - - int Init(); - bool IsUnlocked(); - void EnableAutoRelock(bool aOn); - void SetAutoLockDuration(uint32_t aDurationInSecs); - bool IsActionInProgress(); - bool InitiateAction(int32_t aActor, Action_t aAction); - - typedef void (*Callback_fn_initiated)(Action_t, int32_t aActor); - typedef void (*Callback_fn_completed)(Action_t); - void SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB); - -private: - friend BoltLockManager & BoltLockMgr(void); - State_t mState; - - Callback_fn_initiated mActionInitiated_CB; - Callback_fn_completed mActionCompleted_CB; - - bool mAutoRelock; - uint32_t mAutoLockDuration; - bool mAutoLockTimerArmed; - - void CancelTimer(void); - void StartTimer(uint32_t aTimeoutMs); - - static void TimerEventHandler(TimerHandle_t xTimer); - static void AutoReLockTimerEventHandler(void * aGenericEvent); - static void ActuatorMovementTimerEventHandler(void * aGenericEvent); - - static BoltLockManager sLock; -}; - -inline BoltLockManager & BoltLockMgr(void) -{ - return BoltLockManager::sLock; -} diff --git a/examples/lock-app/nxp/k32w/k32w0/main/include/app_config.h b/examples/lock-app/nxp/k32w/k32w0/main/include/app_config.h deleted file mode 100644 index cfed43e850c4bc..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/main/include/app_config.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * - * Copyright (c) 2020 Google LLC. - * 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 "LED.h" - -#pragma once - -// ---- Lock Example App Config ---- - -#define RESET_BUTTON 1 -#define LOCK_BUTTON 2 -#define JOIN_BUTTON 3 -#define BLE_BUTTON 4 - -#define RESET_BUTTON_PUSH 1 -#define LOCK_BUTTON_PUSH 2 -#define JOIN_BUTTON_PUSH 3 -#define BLE_BUTTON_PUSH 4 - -#define APP_BUTTON_PUSH 1 - -#define LOCK_STATE_LED LED2 -#define SYSTEM_STATE_LED LED1 - -// Time it takes in ms for the simulated actuator to move from one -// state to another. -#define ACTUATOR_MOVEMENT_PERIOS_MS 2000 - -// ---- Lock Example SWU Config ---- -#define SWU_INTERVAl_WINDOW_MIN_MS (23 * 60 * 60 * 1000) // 23 hours -#define SWU_INTERVAl_WINDOW_MAX_MS (24 * 60 * 60 * 1000) // 24 hours - -// ---- Thread Polling Config ---- -#define THREAD_ACTIVE_POLLING_INTERVAL_MS 100 -#define THREAD_INACTIVE_POLLING_INTERVAL_MS 1000 - -#if K32W_LOG_ENABLED -#define K32W_LOG(...) otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_API, ##__VA_ARGS__); -#else -#define K32W_LOG(...) -#endif diff --git a/examples/lock-app/nxp/k32w/k32w0/main/main.cpp b/examples/lock-app/nxp/k32w/k32w0/main/main.cpp deleted file mode 100644 index 7cb5057175b37b..00000000000000 --- a/examples/lock-app/nxp/k32w/k32w0/main/main.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* - * - * Copyright (c) 2020 Google LLC. - * 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. - */ - -// ================================================================================ -// Main Code -// ================================================================================ - -#include - -#include -#include -#include -#include -#include - -#include "FreeRtosHooks.h" -#include "app_config.h" - -#if PDM_SAVE_IDLE -#include -#endif - -#if defined(chip_with_low_power) && (chip_with_low_power == 1) -#include "fsl_gpio.h" -#include "fsl_iocon.h" -#include "gpio_pins.h" -#endif - -using namespace ::chip; -using namespace ::chip::Inet; -using namespace ::chip::DeviceLayer; -using namespace ::chip::Logging; - -#if defined(chip_with_low_power) && (chip_with_low_power == 1) -#include "Keyboard.h" -#include "OtaSupport.h" -#include "PWR_Configuration.h" -#include "PWR_Interface.h" -#include "RNG_Interface.h" -#include "app_dual_mode_low_power.h" -#include "app_dual_mode_switch.h" -#include "radio.h" -#endif - -#include "MacSched.h" - -typedef void (*InitFunc)(void); - -extern InitFunc __init_array_start; -extern InitFunc __init_array_end; - -extern "C" void sched_enable(); - -/* low power requirements */ -#if defined(chip_with_low_power) && (chip_with_low_power == 1) -extern "C" void setThreadInitialized(bool isInitialized); -extern "C" bool isThreadInitialized(); -#endif - -/* needed for FreeRtos Heap 4 */ -uint8_t __attribute__((section(".heap"))) ucHeap[HEAP_SIZE]; - -extern "C" void main_task(void const * argument) -{ - CHIP_ERROR err = CHIP_NO_ERROR; - - /* Call C++ constructors */ - InitFunc * pFunc = &__init_array_start; - for (; pFunc < &__init_array_end; ++pFunc) - { - (*pFunc)(); - } - - mbedtls_platform_set_calloc_free(CHIPPlatformMemoryCalloc, CHIPPlatformMemoryFree); - err = PlatformMgrImpl().InitBoardFwk(); - if (err != CHIP_NO_ERROR) - { - return; - } - - K32W_LOG("Welcome to NXP ELock Demo App"); - - /* Mbedtls Threading support is needed because both - * Thread and Matter tasks are using it */ - freertos_mbedtls_mutex_init(); - - // Init Chip memory management before the stack - chip::Platform::MemoryInit(); - -#if PDM_SAVE_IDLE - /* OT Settings needs to be initialized - * early as XCVR is making use of it */ - otPlatSettingsInit(NULL, NULL, 0); -#endif - - CHIP_ERROR ret = PlatformMgr().InitChipStack(); - if (ret != CHIP_NO_ERROR) - { - K32W_LOG("Error during PlatformMgr().InitMatterStack()"); - goto exit; - } - - ret = ThreadStackMgr().InitThreadStack(); - if (ret != CHIP_NO_ERROR) - { - K32W_LOG("Error during ThreadStackMgr().InitThreadStack()"); - goto exit; - } - - /* Enable the MAC scheduler after BLEManagerImpl::_Init() and V2MMAC_Enable(). - * This is needed to register properly the active protocols. - */ - sched_enable(); - - ret = ConnectivityMgr().SetThreadDeviceType(ConnectivityManager::kThreadDeviceType_SleepyEndDevice); - if (ret != CHIP_NO_ERROR) - { - goto exit; - } - -#if defined(chip_with_low_power) && (chip_with_low_power == 1) - setThreadInitialized(TRUE); -#endif - - // Start OpenThread task - ret = ThreadStackMgrImpl().StartThreadTask(); - if (ret != CHIP_NO_ERROR) - { - K32W_LOG("Error during ThreadStackMgrImpl().StartThreadTask()"); - goto exit; - } - - ret = GetAppTask().StartAppTask(); - if (ret != CHIP_NO_ERROR) - { - K32W_LOG("Error during GetAppTask().StartAppTask()"); - goto exit; - } - - ret = PlatformMgr().StartEventLoopTask(); - if (ret != CHIP_NO_ERROR) - { - K32W_LOG("Error during PlatformMgr().StartEventLoopTask();"); - goto exit; - } - - GetAppTask().AppTaskMain(NULL); - -exit: - return; -} - -/** - * Glue function called directly by the OpenThread stack - * when system event processing work is pending. - */ -extern "C" void otSysEventSignalPending(void) -{ - -#if defined(chip_with_low_power) && (chip_with_low_power == 1) - /* make sure that 15.4 radio is initialized before waking up the Thread task */ - if (isThreadInitialized()) -#endif - { - BaseType_t yieldRequired = ThreadStackMgrImpl().SignalThreadActivityPendingFromISR(); - portYIELD_FROM_ISR(yieldRequired); - } -} - -#if defined(chip_with_low_power) && (chip_with_low_power == 1) -extern "C" void vOptimizeConsumption(void) -{ - /* BUTTON2 change contact, BUTTON4 start adv/factoryreset */ - uint32_t u32SkipIO = (1 << IOCON_USER_BUTTON1_PIN) | (1 << IOCON_USER_BUTTON2_PIN); - - /* Pins are set to GPIO mode (IOCON FUNC0), pulldown and analog mode */ - uint32_t u32PIOvalue = (IOCON_FUNC0 | IOCON_MODE_PULLDOWN | IOCON_ANALOG_EN); - - const gpio_pin_config_t pin_config = { .pinDirection = kGPIO_DigitalInput, .outputLogic = 1U }; - - if (u32PIOvalue != 0) - { - for (int i = 0; i < 22; i++) - { - if (((u32SkipIO >> i) & 0x1) != 1) - { - /* configure GPIOs to Input mode */ - GPIO_PinInit(GPIO, 0, i, &pin_config); - IOCON_PinMuxSet(IOCON, 0, i, u32PIOvalue); - } - } - } -} -#endif diff --git a/examples/lock-app/nxp/zap/lock-app.zap b/examples/lock-app/nxp/zap/lock-app.zap index aeb737f5ac6bb6..de2ffe615ee4e5 100644 --- a/examples/lock-app/nxp/zap/lock-app.zap +++ b/examples/lock-app/nxp/zap/lock-app.zap @@ -3460,4 +3460,4 @@ "parentEndpointIdentifier": null } ] -} \ No newline at end of file +} diff --git a/examples/all-clusters-app/nxp/common/main/AppAssert.cpp b/examples/platform/nxp/common/app_assert/source/AppAssert.cpp similarity index 100% rename from examples/all-clusters-app/nxp/common/main/AppAssert.cpp rename to examples/platform/nxp/common/app_assert/source/AppAssert.cpp diff --git a/examples/platform/nxp/common/app_task/include/AppTaskBase.h b/examples/platform/nxp/common/app_task/include/AppTaskBase.h new file mode 100644 index 00000000000000..02fdf513eb6679 --- /dev/null +++ b/examples/platform/nxp/common/app_task/include/AppTaskBase.h @@ -0,0 +1,151 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2021 Google LLC. + * Copyright 2024 NXP + * 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. + */ + +#pragma once + +#include +#include + +#include "AppEvent.h" +#include +#include + +namespace chip { +namespace NXP { +namespace App { +class AppTaskBase +{ +public: + virtual ~AppTaskBase() = default; + + /** + * \brief Entry point of AppTaskBase. This function should be overridden. + * + * This function should be called by the main function in order to initialize the matter stack and all other components + * required. + * Should be overriden depending on the OS, for example FreeRTOS start the AppTaskMain task, but Zephyr didn't need to start any + * task. + * + */ + virtual CHIP_ERROR Start() = 0; + + /** + * \brief This function could be overridden in order to execute code at the beginning of the InitServer function. + * + * This function is called at the begging of the InitServer function. + * + */ + virtual void PreInitMatterServerInstance(void){}; + + /** + * \brief This function could be overridden in order to execute code at the end of the InitServer function. + * + * Example of usage: all-cluster-apps example disable last fixed endpoint after InitServer function execution. + * + */ + virtual void PostInitMatterServerInstance(void){}; + + /** + * \brief This function could be overridden in order to execute code before matter stack init function. + * + * Example of usage: if some initialization has to be done before the matter stack initialization. + * + */ + virtual void PreInitMatterStack(void){}; + + /** + * \brief This function could be overridden in order to execute code after matter stack init function. + * + * Example of usage: The laundry whasher example requires the TemperatureControl cluster initialization, this initialization is + * done after matter stack init. Developper can override this function to add cluster initialization/customization. + * + */ + virtual void PostInitMatterStack(void){}; + + /** + * \brief This function could be overridden in order to register features. + * + * Example of usage: Could be overridden in order to register matter CLI or button features. + * + * \return CHIP_ERROR + * + */ + virtual CHIP_ERROR AppMatter_Register(void) { return CHIP_NO_ERROR; }; + + /** + * \brief This function could be overridden in order to register custom CLI commands. + * + * Example of usage: Laundry washer application used additionnal CLI commands. + * + */ + virtual void AppMatter_RegisterCustomCliCommands(void){}; + + /** + * \brief This function could be overridden in order to dispatch event. + * + * Example of usage: FreeRtos dispatch event using the event handler. + * + */ + virtual void DispatchEvent(const AppEvent & event){}; + + /** + * \brief Return a pointer to the NXP Wifi Driver instance. + * + * \return NXP Wifi Driver instance pointeur + */ +#if CONFIG_CHIP_WIFI || CHIP_DEVICE_CONFIG_ENABLE_WPA + virtual chip::DeviceLayer::NetworkCommissioning::WiFiDriver * GetWifiDriverInstance(void) = 0; +#endif + + /** + * \brief Stack initializations. + * + * Init matter stack and all other components (openthread, wifi, cli ...). + * + */ + CHIP_ERROR Init(); + + /** + * \brief Initialize the ZCL Data Model and start server. + * + * Call by Init function to initialize the ZCL Data Model and start server. + * + */ + static void InitServer(intptr_t arg); + + /* Commissioning handlers */ + virtual void StartCommissioningHandler(void){}; + virtual void StopCommissioningHandler(void){}; + virtual void SwitchCommissioningStateHandler(void){}; + virtual void FactoryResetHandler(void){}; + +private: + inline static chip::CommonCaseDeviceServerInitParams initParams; +}; + +/** + * Returns the application-specific implementation of the AppTaskBase object. + * + * Applications can use this to gain access to features of the AppTaskBase. + */ +extern AppTaskBase & GetAppTask(); +} // namespace App +} // namespace NXP +} // namespace chip diff --git a/examples/platform/nxp/common/app_task/include/AppTaskFreeRTOS.h b/examples/platform/nxp/common/app_task/include/AppTaskFreeRTOS.h new file mode 100644 index 00000000000000..9fbaf8063df8b7 --- /dev/null +++ b/examples/platform/nxp/common/app_task/include/AppTaskFreeRTOS.h @@ -0,0 +1,93 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright 2024 NXP + * 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. + */ + +#pragma once + +#include "AppTaskBase.h" + +namespace chip { +namespace NXP { +namespace App { +class AppTaskFreeRTOS : public AppTaskBase +{ +public: + virtual ~AppTaskFreeRTOS() = default; + + /** + * \brief Entry point of AppTaskBase. + * + * This function: + * - Create application message queue + * - Create the dedicated application task + * + */ + virtual CHIP_ERROR Start() override; + + /** + * \brief Application task. + * + * This function: + * - Init matter stack and other needed components + * - Dispatch event comming from the app event queue + * + */ + static void AppTaskMain(void * pvParameter); + + /** + * \brief Send event to the event queue. + * + */ + void PostEvent(const AppEvent & event); + + /** + * \brief Return a pointer to the NXP Wifi Driver instance. + * + * \return NXP Wifi Driver instance pointeur + */ +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + virtual chip::DeviceLayer::NetworkCommissioning::WiFiDriver * GetWifiDriverInstance(void) override; +#endif + + /** + * \brief This function register matter CLI and button features. + * + * \return CHIP_ERROR + * + */ + virtual CHIP_ERROR AppMatter_Register(void) override; + + /* Functions that would be called in the Matter task context */ + static void StartCommissioning(intptr_t arg); + static void StopCommissioning(intptr_t arg); + static void SwitchCommissioningState(intptr_t arg); + + /* Commissioning handlers */ + virtual void StartCommissioningHandler(void) override; + virtual void StopCommissioningHandler(void) override; + virtual void SwitchCommissioningStateHandler(void) override; + + /* FactoryResetHandler */ + virtual void FactoryResetHandler(void) override; + +private: + void DispatchEvent(const AppEvent & event); +}; +} // namespace App +} // namespace NXP +} // namespace chip diff --git a/examples/platform/nxp/common/app_task/include/AppTaskZephyr.h b/examples/platform/nxp/common/app_task/include/AppTaskZephyr.h new file mode 100644 index 00000000000000..163e3e76a9d87c --- /dev/null +++ b/examples/platform/nxp/common/app_task/include/AppTaskZephyr.h @@ -0,0 +1,62 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright 2024 NXP + * 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. + */ + +#pragma once + +#include "AppTaskBase.h" + +namespace chip { +namespace NXP { +namespace App { +class AppTaskZephyr : public AppTaskBase +{ +public: + virtual ~AppTaskZephyr() = default; + + /** + * \brief Entry point of AppTaskBase. + * + * This function: + * - Init matter stack and other needed components + * - Dispatch event comming from the app event queue + * + */ + virtual CHIP_ERROR Start() override; + + /** + * \brief Send event to the event queue. + * + */ + void PostEvent(const AppEvent & event); + + /** + * \brief Return a pointer to the NXP Wifi Driver instance. + * + * \return NXP Wifi Driver instance pointeur + */ +#if defined(CONFIG_CHIP_WIFI) + virtual chip::DeviceLayer::NetworkCommissioning::WiFiDriver * GetWifiDriverInstance(void) override; +#endif + +private: + void DispatchEvent(const AppEvent & event); +}; +} // namespace App +} // namespace NXP +} // namespace chip diff --git a/examples/platform/nxp/common/app_task/source/AppTaskBase.cpp b/examples/platform/nxp/common/app_task/source/AppTaskBase.cpp new file mode 100644 index 00000000000000..e03634e9807aa5 --- /dev/null +++ b/examples/platform/nxp/common/app_task/source/AppTaskBase.cpp @@ -0,0 +1,252 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * Copyright (c) 2021 Google LLC. + * Copyright 2024 NXP + * 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 "AppTaskBase.h" +#include "AppEvent.h" +#include "AppFactoryData.h" +#include "CHIPDeviceManager.h" +#include "CommonDeviceCallbacks.h" + +#include +#include + +#include +#include +#include + +#include +#include + +#include "lib/core/ErrorStr.h" + +#include + +#ifdef EMBER_AF_PLUGIN_BINDING +#include "binding-handler.h" +#endif + +#if CONFIG_NET_L2_OPENTHREAD +#include +#include +#endif + +#if CONFIG_CHIP_TCP_DOWNLOAD +#include "TcpDownload.h" +#endif + +#if CONFIG_CHIP_OTA_PROVIDER +#include +#endif + +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +#include "OTARequestorInitiator.h" +#endif + +#if CONFIG_CHIP_TEST_EVENT && CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +#include +#endif + +using namespace chip; +using namespace chip::TLV; +using namespace ::chip::Credentials; +using namespace ::chip::DeviceLayer; +using namespace ::chip::DeviceManager; +using namespace ::chip::app::Clusters; + +chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider; + +#if CONFIG_CHIP_WIFI || CHIP_DEVICE_CONFIG_ENABLE_WPA +app::Clusters::NetworkCommissioning::Instance sNetworkCommissioningInstance(0, + chip::NXP::App::GetAppTask().GetWifiDriverInstance()); +#endif + +#if CONFIG_CHIP_TEST_EVENT && CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +static uint8_t sTestEventTriggerEnableKey[TestEventTriggerDelegate::kEnableKeyLength] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, + 0xcc, 0xdd, 0xee, 0xff }; +#endif + +#if CONFIG_NET_L2_OPENTHREAD +void LockOpenThreadTask(void) +{ + chip::DeviceLayer::ThreadStackMgr().LockThreadStack(); +} + +void UnlockOpenThreadTask(void) +{ + chip::DeviceLayer::ThreadStackMgr().UnlockThreadStack(); +} +#endif + +void chip::NXP::App::AppTaskBase::InitServer(intptr_t arg) +{ + GetAppTask().PreInitMatterServerInstance(); + +#if CONFIG_CHIP_TEST_EVENT && CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + static OTATestEventTriggerDelegate testEventTriggerDelegate{ ByteSpan(sTestEventTriggerEnableKey) }; + initParams.testEventTriggerDelegate = &testEventTriggerDelegate; +#endif + (void) initParams.InitializeStaticResourcesBeforeServerInit(); +#if CONFIG_NET_L2_OPENTHREAD + // Init ZCL Data Model and start server + chip::Inet::EndPointStateOpenThread::OpenThreadEndpointInitParam nativeParams; + nativeParams.lockCb = LockOpenThreadTask; + nativeParams.unlockCb = UnlockOpenThreadTask; + nativeParams.openThreadInstancePtr = chip::DeviceLayer::ThreadStackMgrImpl().OTInstance(); + initParams.endpointNativeParams = static_cast(&nativeParams); +#endif + + VerifyOrDie((chip::Server::GetInstance().Init(initParams)) == CHIP_NO_ERROR); + + gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage()); + chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider); + + GetAppTask().PostInitMatterServerInstance(); + +#if CONFIG_CHIP_OTA_PROVIDER + InitOTAServer(); +#endif +} + +CHIP_ERROR chip::NXP::App::AppTaskBase::Init() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + /* Init Chip memory management before the stack */ + chip::Platform::MemoryInit(); + + /* Initialize Matter factory data before initializing the Matter stack */ + err = AppFactoryData_PreMatterStackInit(); + + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Pre Factory Data Provider init failed"); + goto exit; + } + + /* + * Initialize the CHIP stack. + * Would also initialize all required platform modules + */ + err = PlatformMgr().InitChipStack(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "PlatformMgr().InitChipStack() failed: %s", ErrorStr(err)); + goto exit; + } + + /* Initialize Matter factory data after initializing the Matter stack */ + err = AppFactoryData_PostMatterStackInit(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Post Factory Data Provider init failed"); + goto exit; + } + + /* + * Register all application callbacks allowing to be informed of stack events + */ + err = CHIPDeviceManager::GetInstance().Init(&GetDeviceCallbacks()); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "CHIPDeviceManager.Init() failed: %s", ErrorStr(err)); + goto exit; + } + +#if CONFIG_NET_L2_OPENTHREAD + err = ThreadStackMgr().InitThreadStack(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Error during ThreadStackMgr().InitThreadStack()"); + return err; + } + + err = ConnectivityMgr().SetThreadDeviceType(ConnectivityManager::kThreadDeviceType_Router); + if (err != CHIP_NO_ERROR) + { + return err; + } +#endif + + /* + * Schedule an event to the Matter stack to initialize + * the ZCL Data Model and start server + */ + PlatformMgr().ScheduleWork(InitServer, 0); + +#ifdef EMBER_AF_PLUGIN_BINDING + /* Init binding handlers */ + err = InitBindingHandlers(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "InitBindingHandlers failed: %s", ErrorStr(err)); + goto exit; + } +#endif + +#if CONFIG_CHIP_WIFI || CHIP_DEVICE_CONFIG_ENABLE_WPA + sNetworkCommissioningInstance.Init(); +#endif +#if CONFIG_CHIP_OTA_REQUESTOR + if (err == CHIP_NO_ERROR) + { + /* If an update is under test make it permanent */ + OTARequestorInitiator::Instance().HandleSelfTest(); + } +#endif + + err = AppMatter_Register(); + if (err != CHIP_NO_ERROR) + { + goto exit; + } + + ConfigurationMgr().LogDeviceConfig(); + + // QR code will be used with CHIP Tool +#if CONFIG_NETWORK_LAYER_BLE + PrintOnboardingCodes(chip::RendezvousInformationFlag(chip::RendezvousInformationFlag::kBLE)); +#else + PrintOnboardingCodes(chip::RendezvousInformationFlag(chip::RendezvousInformationFlag::kOnNetwork)); +#endif /* CONFIG_NETWORK_LAYER_BLE */ + + /* Start a task to run the CHIP Device event loop. */ + err = PlatformMgr().StartEventLoopTask(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Error during PlatformMgr().StartEventLoopTask()"); + goto exit; + } + +#if CONFIG_NET_L2_OPENTHREAD + // Start OpenThread task + err = ThreadStackMgrImpl().StartThreadTask(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Error during ThreadStackMgrImpl().StartThreadTask()"); + } +#endif + +#if CONFIG_CHIP_TCP_DOWNLOAD + EnableTcpDownloadComponent(); +#endif + +exit: + return err; +} diff --git a/examples/platform/nxp/common/app_task/source/AppTaskFreeRTOS.cpp b/examples/platform/nxp/common/app_task/source/AppTaskFreeRTOS.cpp new file mode 100644 index 00000000000000..8508884b672b23 --- /dev/null +++ b/examples/platform/nxp/common/app_task/source/AppTaskFreeRTOS.cpp @@ -0,0 +1,240 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2021 Google LLC. + * Copyright 2024 NXP + * 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 "AppTaskFreeRTOS.h" +#include +#include + +#include "AppMatterButton.h" +#include "AppMatterCli.h" + +#include "CHIPDeviceManager.h" +#include + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA +#include +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +#include + +#include + +#if WIFI_CONNECT_TASK +#include "WifiConnect.h" +#endif + +#ifndef APP_TASK_STACK_SIZE +#define APP_TASK_STACK_SIZE ((configSTACK_DEPTH_TYPE) 6144 / sizeof(portSTACK_TYPE)) +#endif +#ifndef APP_TASK_PRIORITY +#define APP_TASK_PRIORITY 2 +#endif +#define APP_EVENT_QUEUE_SIZE 10 + +static QueueHandle_t sAppEventQueue; + +using namespace chip; +using namespace chip::TLV; +using namespace ::chip::Credentials; +using namespace ::chip::DeviceLayer; +using namespace ::chip::DeviceManager; +using namespace ::chip::app::Clusters; + +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + +chip::DeviceLayer::NetworkCommissioning::WiFiDriver * chip::NXP::App::AppTaskFreeRTOS::GetWifiDriverInstance() +{ + return static_cast( + &(::chip::DeviceLayer::NetworkCommissioning::NXPWiFiDriver::GetInstance())); +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_WPA + +CHIP_ERROR chip::NXP::App::AppTaskFreeRTOS::AppMatter_Register() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + /* Register Matter CLI cmds */ + err = AppMatterCli_RegisterCommands(); + AppMatter_RegisterCustomCliCommands(); + AppMatterCli_StartTask(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Error during AppMatterCli_RegisterCommands"); + return err; + } + /* Register Matter buttons */ + err = AppMatterButton_registerButtons(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "Error during AppMatterButton_registerButtons"); + return err; + } + return err; +} + +CHIP_ERROR chip::NXP::App::AppTaskFreeRTOS::Start() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + TaskHandle_t taskHandle; + + sAppEventQueue = xQueueCreate(APP_EVENT_QUEUE_SIZE, sizeof(AppEvent)); + if (sAppEventQueue == NULL) + { + err = CHIP_ERROR_NO_MEMORY; + ChipLogError(DeviceLayer, "Failed to allocate app event queue"); + assert(err == CHIP_NO_ERROR); + } + + /* AppTaskMain function will loss actual object instance, give it as parameter */ + if (xTaskCreate(&AppTaskFreeRTOS::AppTaskMain, "AppTaskMain", APP_TASK_STACK_SIZE, this, APP_TASK_PRIORITY, &taskHandle) != + pdPASS) + { + err = CHIP_ERROR_NO_MEMORY; + ChipLogError(DeviceLayer, "Failed to start app task"); + assert(err == CHIP_NO_ERROR); + } + + return err; +} + +void chip::NXP::App::AppTaskFreeRTOS::AppTaskMain(void * pvParameter) +{ + CHIP_ERROR err; + AppEvent event; + + /* AppTaskMain function will loss AppTask object instance (FreeRTOS task context), AppTask instance is given as parameter */ + AppTaskFreeRTOS * sAppTask = static_cast(pvParameter); + + sAppTask->PreInitMatterStack(); + err = sAppTask->Init(); + sAppTask->PostInitMatterStack(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "AppTask.Init() failed"); + assert(err == CHIP_NO_ERROR); + } + + while (true) + { + BaseType_t eventReceived = xQueueReceive(sAppEventQueue, &event, portMAX_DELAY); + while (eventReceived == pdTRUE) + { + sAppTask->DispatchEvent(event); + eventReceived = xQueueReceive(sAppEventQueue, &event, 0); + } + } +} + +void chip::NXP::App::AppTaskFreeRTOS::PostEvent(const AppEvent & event) +{ + if (sAppEventQueue != NULL) + { + if (!xQueueSend(sAppEventQueue, &event, 0)) + { + ChipLogError(DeviceLayer, "Failed to post event to app task event queue"); + } + } +} + +void chip::NXP::App::AppTaskFreeRTOS::DispatchEvent(const AppEvent & event) +{ + if (event.Handler) + { + event.Handler(event); + } + else + { + ChipLogProgress(DeviceLayer, "Event received with no handler. Dropping event."); + } +} + +void chip::NXP::App::AppTaskFreeRTOS::StartCommissioning(intptr_t arg) +{ + /* Check the status of the commissioning */ + if (ConfigurationMgr().IsFullyProvisioned()) + { + ChipLogProgress(DeviceLayer, "Device already commissioned"); + } + else if (chip::Server::GetInstance().GetCommissioningWindowManager().IsCommissioningWindowOpen()) + { + ChipLogProgress(DeviceLayer, "Commissioning window already opened"); + } + else + { + chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow(); + } +} + +void chip::NXP::App::AppTaskFreeRTOS::StopCommissioning(intptr_t arg) +{ + /* Check the status of the commissioning */ + if (ConfigurationMgr().IsFullyProvisioned()) + { + ChipLogProgress(DeviceLayer, "Device already commissioned"); + } + else if (!chip::Server::GetInstance().GetCommissioningWindowManager().IsCommissioningWindowOpen()) + { + ChipLogProgress(DeviceLayer, "Commissioning window not opened"); + } + else + { + chip::Server::GetInstance().GetCommissioningWindowManager().CloseCommissioningWindow(); + } +} + +void chip::NXP::App::AppTaskFreeRTOS::SwitchCommissioningState(intptr_t arg) +{ + /* Check the status of the commissioning */ + if (ConfigurationMgr().IsFullyProvisioned()) + { + ChipLogProgress(DeviceLayer, "Device already commissioned"); + } + else if (!chip::Server::GetInstance().GetCommissioningWindowManager().IsCommissioningWindowOpen()) + { + chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow(); + } + else + { + chip::Server::GetInstance().GetCommissioningWindowManager().CloseCommissioningWindow(); + } +} + +void chip::NXP::App::AppTaskFreeRTOS::StartCommissioningHandler(void) +{ + /* Publish an event to the Matter task to always set the commissioning state in the Matter task context */ + PlatformMgr().ScheduleWork(StartCommissioning, 0); +} + +void chip::NXP::App::AppTaskFreeRTOS::StopCommissioningHandler(void) +{ + /* Publish an event to the Matter task to always set the commissioning state in the Matter task context */ + PlatformMgr().ScheduleWork(StopCommissioning, 0); +} + +void chip::NXP::App::AppTaskFreeRTOS::SwitchCommissioningStateHandler(void) +{ + /* Publish an event to the Matter task to always set the commissioning state in the Matter task context */ + PlatformMgr().ScheduleWork(SwitchCommissioningState, 0); +} + +void chip::NXP::App::AppTaskFreeRTOS::FactoryResetHandler(void) +{ + /* Emit the ShutDown event before factory reset */ + chip::Server::GetInstance().GenerateShutDownEvent(); + chip::Server::GetInstance().ScheduleFactoryReset(); +} diff --git a/examples/platform/nxp/common/app_task/source/AppTaskZephyr.cpp b/examples/platform/nxp/common/app_task/source/AppTaskZephyr.cpp new file mode 100644 index 00000000000000..03a86a572a82c2 --- /dev/null +++ b/examples/platform/nxp/common/app_task/source/AppTaskZephyr.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020 Project CHIP Authors + * Copyright 2024 NXP + * 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. + */ + +/* -------------------------------------------------------------------------- */ +/* Includes */ +/* -------------------------------------------------------------------------- */ +#include "AppTaskZephyr.h" + +#include "CHIPDeviceManager.h" +#include +#include +#include +#include +#include + +#ifdef CONFIG_CHIP_WIFI +#include +#endif + +#if CONFIG_CHIP_FACTORY_DATA +#include +#else +#include +#endif + +#include "AppFactoryData.h" + +LOG_MODULE_DECLARE(app, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace ::chip; +using namespace ::chip::app; +using namespace ::chip::Credentials; +using namespace ::chip::DeviceLayer; +using namespace ::chip::DeviceManager; + +/* -------------------------------------------------------------------------- */ +/* Private memory */ +/* -------------------------------------------------------------------------- */ + +constexpr size_t kAppEventQueueSize = 10; +K_MSGQ_DEFINE(sAppEventQueue, sizeof(AppEvent), kAppEventQueueSize, alignof(AppEvent)); + +/* -------------------------------------------------------------------------- */ +/* Class implementation */ +/* -------------------------------------------------------------------------- */ + +#if defined(CONFIG_CHIP_WIFI) +chip::DeviceLayer::NetworkCommissioning::WiFiDriver * chip::NXP::App::AppTaskZephyr::GetWifiDriverInstance() +{ + return static_cast(&(NetworkCommissioning::NxpWifiDriver::Instance())); +} +#endif // CONFIG_CHIP_WIFI + +CHIP_ERROR chip::NXP::App::AppTaskZephyr::Start() +{ + + ReturnErrorOnFailure(Init()); + + AppEvent event{}; + + while (true) + { + k_msgq_get(&sAppEventQueue, &event, K_FOREVER); + DispatchEvent(event); + } + + return CHIP_NO_ERROR; +} + +void chip::NXP::App::AppTaskZephyr::PostEvent(const AppEvent & event) +{ + if (k_msgq_put(&sAppEventQueue, &event, K_NO_WAIT) != 0) + { + LOG_INF("Failed to post event to app task event queue"); + } +} + +void chip::NXP::App::AppTaskZephyr::DispatchEvent(const AppEvent & event) +{ + if (event.Handler) + { + event.Handler(event); + } + else + { + LOG_INF("Event received with no handler. Dropping event."); + } +} diff --git a/examples/platform/nxp/common/device_callbacks/include/CommonDeviceCallbacks.h b/examples/platform/nxp/common/device_callbacks/include/CommonDeviceCallbacks.h new file mode 100644 index 00000000000000..4cbd5028ce58b3 --- /dev/null +++ b/examples/platform/nxp/common/device_callbacks/include/CommonDeviceCallbacks.h @@ -0,0 +1,69 @@ +/* + * + * Copyright (c) 2020-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. + */ + +/** + * @file CommonDeviceCallbacks.h + * + * Common Implementations for the DeviceManager callbacks for all applications + * + **/ + +#pragma once + +#include "CHIPDeviceManager.h" +#include +#include + +namespace chip { +namespace NXP { +namespace App { +class CommonDeviceCallbacks : public chip::DeviceManager::CHIPDeviceManagerCallbacks +{ +public: + virtual void DeviceEventCallback(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + virtual void OnWiFiConnectivityChange(const chip::DeviceLayer::ChipDeviceEvent * event); + virtual void OnInternetConnectivityChange(const chip::DeviceLayer::ChipDeviceEvent * event); + virtual void OnSessionEstablished(const chip::DeviceLayer::ChipDeviceEvent * event); + virtual void OnInterfaceIpAddressChanged(const chip::DeviceLayer::ChipDeviceEvent * event); +#if CHIP_ENABLE_OPENTHREAD && CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED + virtual void OnComissioningComplete(const chip::DeviceLayer::ChipDeviceEvent * event); +#endif +}; + +class DeviceCallbacksDelegate +{ +public: + static DeviceCallbacksDelegate & Instance() + { + static DeviceCallbacksDelegate instance; + return instance; + } + DeviceCallbacksDelegate * mDelegate = nullptr; + void SetAppDelegate(DeviceCallbacksDelegate * delegate) { mDelegate = delegate; } + DeviceCallbacksDelegate * GetAppDelegate() { return mDelegate; } +}; + +/** + * Returns the application-specific implementation of the CommonDeviceCallbacks object. + * + * Applications can use this to gain access to features of the CommonDeviceCallbacks. + */ +extern chip::DeviceManager::CHIPDeviceManagerCallbacks & GetDeviceCallbacks(); +} // namespace App +} // namespace NXP +} // namespace chip diff --git a/examples/platform/nxp/common/device_callbacks/source/CommonDeviceCallbacks.cpp b/examples/platform/nxp/common/device_callbacks/source/CommonDeviceCallbacks.cpp new file mode 100644 index 00000000000000..d38d6f59f8c572 --- /dev/null +++ b/examples/platform/nxp/common/device_callbacks/source/CommonDeviceCallbacks.cpp @@ -0,0 +1,177 @@ +/* + * + * Copyright (c) 2020-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. + */ + +/** + * @file CommonDeviceCallbacks.cpp + * + * Implements all the callbacks to the application from the CHIP Stack + * + **/ +#include "CommonDeviceCallbacks.h" + +#include +#include +#include +#include +#include + +#include +#if CHIP_ENABLE_OPENTHREAD && CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED +#include "openthread-system.h" +#include "ot_platform_common.h" +#endif /* CHIP_ENABLE_OPENTHREAD && CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED */ + +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR +#include "OTARequestorInitiator.h" +#endif + +using namespace chip::app; +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::System; +using namespace ::chip::DeviceLayer; + +void chip::NXP::App::CommonDeviceCallbacks::DeviceEventCallback(const ChipDeviceEvent * event, intptr_t arg) +{ + ChipLogDetail(DeviceLayer, "DeviceEventCallback: 0x%04x", event->Type); + switch (event->Type) + { + case DeviceEventType::kWiFiConnectivityChange: + OnWiFiConnectivityChange(event); + break; + + case DeviceEventType::kInternetConnectivityChange: + OnInternetConnectivityChange(event); + break; + + case DeviceEventType::kInterfaceIpAddressChanged: +#if !CHIP_ENABLE_OPENTHREAD // No need to do this for OT mDNS server + if ((event->InterfaceIpAddressChanged.Type == InterfaceIpChangeType::kIpV4_Assigned) || + (event->InterfaceIpAddressChanged.Type == InterfaceIpChangeType::kIpV6_Assigned)) + { + // MDNS server restart on any ip assignment: if link local ipv6 is configured, that + // will not trigger a 'internet connectivity change' as there is no internet + // connectivity. MDNS still wants to refresh its listening interfaces to include the + // newly selected address. + chip::app::DnssdServer::Instance().StartServer(); + } +#endif + OnInterfaceIpAddressChanged(event); + break; + +#if CHIP_ENABLE_OPENTHREAD && CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED + case DeviceEventType::kCommissioningComplete: + CommonDeviceCallbacks::OnComissioningComplete(event); + break; +#endif + case DeviceLayer::DeviceEventType::kDnssdInitialized: + ChipLogProgress(DeviceLayer, "kDnssdInitialized"); +#if CHIP_DEVICE_CONFIG_ENABLE_OTA_REQUESTOR + /* Initialize OTA Requestor */ + OTARequestorInitiator::Instance().InitOTA(reinterpret_cast(&OTARequestorInitiator::Instance())); +#endif + break; + } +} + +void chip::NXP::App::CommonDeviceCallbacks::OnWiFiConnectivityChange(const ChipDeviceEvent * event) +{ + if (event->WiFiConnectivityChange.Result == kConnectivity_Established) + { + ChipLogProgress(DeviceLayer, "WiFi connection established"); + } + else if (event->WiFiConnectivityChange.Result == kConnectivity_Lost) + { + ChipLogProgress(DeviceLayer, "WiFi connection lost"); + } +} + +void chip::NXP::App::CommonDeviceCallbacks::OnInternetConnectivityChange(const ChipDeviceEvent * event) +{ + if (event->InternetConnectivityChange.IPv4 == kConnectivity_Established) + { + char ip_addr[Inet::IPAddress::kMaxStringLength]; + event->InternetConnectivityChange.ipAddress.ToString(ip_addr); + ChipLogProgress(DeviceLayer, "Server ready at: %s:%d", ip_addr, CHIP_PORT); +#if !CHIP_ENABLE_OPENTHREAD // No need to do this for OT mDNS server + chip::app::DnssdServer::Instance().StartServer(); +#endif + } + else if (event->InternetConnectivityChange.IPv4 == kConnectivity_Lost) + { + ChipLogProgress(DeviceLayer, "Lost IPv4 connectivity..."); + } + if (event->InternetConnectivityChange.IPv6 == kConnectivity_Established) + { + char ip_addr[Inet::IPAddress::kMaxStringLength]; + event->InternetConnectivityChange.ipAddress.ToString(ip_addr); + ChipLogProgress(DeviceLayer, "IPv6 Server ready at: [%s]:%d", ip_addr, CHIP_PORT); +#if !CHIP_ENABLE_OPENTHREAD // No need to do this for OT mDNS server + chip::app::DnssdServer::Instance().StartServer(); +#endif + } + else if (event->InternetConnectivityChange.IPv6 == kConnectivity_Lost) + { + ChipLogProgress(DeviceLayer, "Lost IPv6 connectivity..."); + } +} + +void chip::NXP::App::CommonDeviceCallbacks::OnInterfaceIpAddressChanged(const ChipDeviceEvent * event) +{ + switch (event->InterfaceIpAddressChanged.Type) + { + case InterfaceIpChangeType::kIpV4_Assigned: + ChipLogProgress(DeviceLayer, "Interface IPv4 address assigned"); + break; + case InterfaceIpChangeType::kIpV4_Lost: + ChipLogProgress(DeviceLayer, "Interface IPv4 address lost"); + break; + case InterfaceIpChangeType::kIpV6_Assigned: + ChipLogProgress(DeviceLayer, "Interface IPv6 address assigned"); + break; + case InterfaceIpChangeType::kIpV6_Lost: + ChipLogProgress(DeviceLayer, "Interface IPv6 address lost"); + break; + } +} + +void chip::NXP::App::CommonDeviceCallbacks::OnSessionEstablished(chip::DeviceLayer::ChipDeviceEvent const *) +{ + /* Empty */ +} + +#if CHIP_ENABLE_OPENTHREAD && CHIP_DEVICE_CONFIG_CHIPOBLE_DISABLE_ADVERTISING_WHEN_PROVISIONED +void chip::NXP::App::CommonDeviceCallbacks::OnComissioningComplete(const chip::DeviceLayer::ChipDeviceEvent * event) +{ + /* + * If a transceiver supporting a multiprotocol scenario is used, a check of the provisioning state is required, + * so that we can inform the transceiver to stop BLE to give the priority to another protocol. + * For example it is the case when a K32W0 transceiver supporting OT+BLE+Zigbee is used. When the device is already provisioned, + * BLE is no more required and the transceiver needs to be informed so that Zigbee can be switched on and BLE switched off. + * + * If a transceiver does not support such vendor property the cmd would be ignored. + */ + if (ConfigurationMgr().IsFullyProvisioned()) + { + ChipLogDetail(DeviceLayer, "Provisioning complete, stopping BLE\n"); + ThreadStackMgrImpl().LockThreadStack(); + PlatformMgrImpl().StopBLEConnectivity(); + ThreadStackMgrImpl().UnlockThreadStack(); + } +} +#endif diff --git a/examples/all-clusters-app/nxp/common/main/include/CHIPDeviceManager.h b/examples/platform/nxp/common/device_manager/include/CHIPDeviceManager.h similarity index 100% rename from examples/all-clusters-app/nxp/common/main/include/CHIPDeviceManager.h rename to examples/platform/nxp/common/device_manager/include/CHIPDeviceManager.h diff --git a/examples/all-clusters-app/nxp/common/main/CHIPDeviceManager.cpp b/examples/platform/nxp/common/device_manager/source/CHIPDeviceManager.cpp similarity index 100% rename from examples/all-clusters-app/nxp/common/main/CHIPDeviceManager.cpp rename to examples/platform/nxp/common/device_manager/source/CHIPDeviceManager.cpp diff --git a/examples/shell/nxp/k32w/k32w0/args.gni b/examples/platform/nxp/common/diagnostic_logs/BUILD.gn similarity index 62% rename from examples/shell/nxp/k32w/k32w0/args.gni rename to examples/platform/nxp/common/diagnostic_logs/BUILD.gn index e9f2a25ea6f722..04d864753a6633 100644 --- a/examples/shell/nxp/k32w/k32w0/args.gni +++ b/examples/platform/nxp/common/diagnostic_logs/BUILD.gn @@ -1,4 +1,4 @@ -# Copyright (c) 2021 Project CHIP Authors +# Copyright (c) 2024 Project CHIP Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,11 +13,12 @@ # limitations under the License. import("//build_overrides/chip.gni") -import("//build_overrides/pigweed.gni") -import("${chip_root}/config/standalone/args.gni") -import("${chip_root}/examples/platform/nxp/k32w/k32w0/args.gni") -import("${chip_root}/src/platform/nxp/k32w/k32w0/args.gni") -k32w0_sdk_target = get_label_info(":sdk", "label_no_toolchain") +source_set("nxp_diagnostic_logs") { + sources = [ + "DiagnosticLogsProviderDelegateImpl.cpp", + "DiagnosticLogsProviderDelegateImpl.h", + ] -chip_enable_ble = false + include_dirs = [ "${chip_root}/src" ] +} diff --git a/examples/platform/nxp/common/diagnostic_logs/DiagnosticLogsProviderDelegateImpl.cpp b/examples/platform/nxp/common/diagnostic_logs/DiagnosticLogsProviderDelegateImpl.cpp new file mode 100644 index 00000000000000..359edc3dedc9a6 --- /dev/null +++ b/examples/platform/nxp/common/diagnostic_logs/DiagnosticLogsProviderDelegateImpl.cpp @@ -0,0 +1,168 @@ +/* + * + * Copyright (c) 2024 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 "DiagnosticLogsProviderDelegateImpl.h" + +#include +#include + +using namespace chip; +using namespace chip::app::Clusters::DiagnosticLogs; + +namespace { +bool IsValidIntent(IntentEnum intent) +{ + return intent != IntentEnum::kUnknownEnumValue; +} +} // namespace + +LogProvider LogProvider::sInstance; + +LogProvider::~LogProvider() {} + +CHIP_ERROR LogProvider::GetLogForIntent(IntentEnum intent, MutableByteSpan & outBuffer, Optional & outTimeStamp, + Optional & outTimeSinceBoot) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + LogSessionHandle sessionHandle = kInvalidLogSessionHandle; + + err = StartLogCollection(intent, sessionHandle, outTimeStamp, outTimeSinceBoot); + VerifyOrReturnError(CHIP_NO_ERROR == err, err, outBuffer.reduce_size(0)); + + bool unusedOutIsEndOfLog; + err = CollectLog(sessionHandle, outBuffer, unusedOutIsEndOfLog); + VerifyOrReturnError(CHIP_NO_ERROR == err, err, outBuffer.reduce_size(0)); + + err = EndLogCollection(sessionHandle); + VerifyOrReturnError(CHIP_NO_ERROR == err, err, outBuffer.reduce_size(0)); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR LogProvider::StartLogCollection(IntentEnum intent, LogSessionHandle & outHandle, Optional & outTimeStamp, + Optional & outTimeSinceBoot) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + VerifyOrReturnValue(IsValidIntent(intent), CHIP_ERROR_INVALID_ARGUMENT); + + auto key = GetKeyForIntent(intent); + VerifyOrReturnValue(key.IsInitialized(), CHIP_ERROR_NOT_FOUND); + + uint16_t diagSize = GetSizeForIntent(intent); + VerifyOrReturnError(diagSize, CHIP_ERROR_NOT_FOUND); + + uint8_t * diagData = (uint8_t *) calloc(1, diagSize); + VerifyOrReturnError(diagData, CHIP_ERROR_NO_MEMORY); + + err = chip::Server::GetInstance().GetPersistentStorage().SyncGetKeyValue(key.KeyName(), diagData, diagSize); + VerifyOrReturnValue(err == CHIP_NO_ERROR, err); + + MutableByteSpan * mutableSpan = reinterpret_cast(calloc(1, sizeof(MutableByteSpan))); + VerifyOrReturnValue(mutableSpan, CHIP_ERROR_NO_MEMORY, free(diagData)); + + *mutableSpan = MutableByteSpan(diagData, diagSize); + + mLogSessionHandle++; + // If the session handle rolls over to UINT16_MAX which is invalid, reset to 0. + VerifyOrDo(mLogSessionHandle != kInvalidLogSessionHandle, mLogSessionHandle = 0); + + outHandle = mLogSessionHandle; + mSessionSpanMap[mLogSessionHandle] = mutableSpan; + mSessionDiagMap[mLogSessionHandle] = diagData; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR LogProvider::EndLogCollection(LogSessionHandle sessionHandle) +{ + VerifyOrReturnValue(sessionHandle != kInvalidLogSessionHandle, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnValue(mSessionSpanMap.count(sessionHandle), CHIP_ERROR_INVALID_ARGUMENT); + + free(mSessionDiagMap[sessionHandle]); + free(mSessionSpanMap[sessionHandle]); + + mSessionSpanMap.erase(sessionHandle); + mSessionDiagMap.erase(sessionHandle); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR LogProvider::CollectLog(LogSessionHandle sessionHandle, MutableByteSpan & outBuffer, bool & outIsEndOfLog) +{ + VerifyOrReturnValue(sessionHandle != kInvalidLogSessionHandle, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnValue(mSessionSpanMap.count(sessionHandle), CHIP_ERROR_INVALID_ARGUMENT); + + MutableByteSpan * mutableSpan = mSessionSpanMap[sessionHandle]; + auto diagSize = mutableSpan->size(); + auto count = std::min(diagSize, outBuffer.size()); + + VerifyOrReturnError(CanCastTo(count), CHIP_ERROR_INVALID_ARGUMENT, outBuffer.reduce_size(0)); + + ReturnErrorOnFailure(CopySpanToMutableSpan(ByteSpan(mutableSpan->data(), count), outBuffer)); + + outIsEndOfLog = diagSize == count; + + if (!outIsEndOfLog) + { + // reduce the span after reading count bytes + *mutableSpan = mutableSpan->SubSpan(count); + } + + return CHIP_NO_ERROR; +} + +size_t LogProvider::GetSizeForIntent(IntentEnum intent) +{ + uint16_t sizeForIntent = 0; + CHIP_ERROR err = CHIP_NO_ERROR; + + auto key = GetKeyForIntent(intent); + VerifyOrReturnValue(key.IsInitialized(), 0); + + uint16_t bufferLen = CHIP_DEVICE_CONFIG_MAX_DIAG_LOG_SIZE; + Platform::ScopedMemoryBuffer buffer; + + buffer.Calloc(bufferLen); + VerifyOrReturnValue(buffer.Get() != nullptr, 0); + + err = Server::GetInstance().GetPersistentStorage().SyncGetKeyValue(key.KeyName(), buffer.Get(), sizeForIntent); + VerifyOrReturnValue(err == CHIP_NO_ERROR, 0); + + return sizeForIntent; +} + +StorageKeyName LogProvider::GetKeyForIntent(IntentEnum intent) const +{ + StorageKeyName key = StorageKeyName::Uninitialized(); + + switch (intent) + { + case IntentEnum::kEndUserSupport: + return GetKeyDiagUserSupport(); + case IntentEnum::kNetworkDiag: + return GetKeyDiagNetwork(); + case IntentEnum::kCrashLogs: + return GetKeyDiagCrashLog(); + case IntentEnum::kUnknownEnumValue: + // It should never happen. + chipDie(); + } + + return key; +} diff --git a/examples/platform/nxp/common/diagnostic_logs/DiagnosticLogsProviderDelegateImpl.h b/examples/platform/nxp/common/diagnostic_logs/DiagnosticLogsProviderDelegateImpl.h new file mode 100644 index 00000000000000..73d60f542aacdc --- /dev/null +++ b/examples/platform/nxp/common/diagnostic_logs/DiagnosticLogsProviderDelegateImpl.h @@ -0,0 +1,75 @@ +/* + * + * Copyright (c) 2024 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. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace DiagnosticLogs { + +/** + * The LogProvider class serves as the sole instance delegate for handling diagnostic logs. + * + * It implements the DiagnosticLogsProviderDelegate interface + */ + +class LogProvider : public DiagnosticLogsProviderDelegate +{ +public: + static inline LogProvider & GetInstance() { return sInstance; } + + /////////// DiagnosticLogsProviderDelegate Interface ///////// + CHIP_ERROR StartLogCollection(IntentEnum intent, LogSessionHandle & outHandle, Optional & outTimeStamp, + Optional & outTimeSinceBoot) override; + CHIP_ERROR EndLogCollection(LogSessionHandle sessionHandle) override; + CHIP_ERROR CollectLog(LogSessionHandle sessionHandle, MutableByteSpan & outBuffer, bool & outIsEndOfLog) override; + size_t GetSizeForIntent(IntentEnum intent) override; + CHIP_ERROR GetLogForIntent(IntentEnum intent, MutableByteSpan & outBuffer, Optional & outTimeStamp, + Optional & outTimeSinceBoot) override; + + static inline StorageKeyName GetKeyDiagUserSupport() { return StorageKeyName::FromConst("nxp/diag/usr"); } + + static inline StorageKeyName GetKeyDiagNetwork() { return StorageKeyName::FromConst("nxp/diag/nwk"); } + + static inline StorageKeyName GetKeyDiagCrashLog() { return StorageKeyName::FromConst("nxp/diag/crash"); } + +private: + static LogProvider sInstance; + LogProvider() = default; + ~LogProvider(); + + LogProvider(const LogProvider &) = delete; + LogProvider & operator=(const LogProvider &) = delete; + + // This tracks the ByteSpan for each session + std::map mSessionSpanMap; + std::map mSessionDiagMap; + LogSessionHandle mLogSessionHandle = kInvalidLogSessionHandle; + + StorageKeyName GetKeyForIntent(IntentEnum intent) const; +}; + +} // namespace DiagnosticLogs +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/examples/all-clusters-app/nxp/common/main/include/AppFactoryData.h b/examples/platform/nxp/common/factory_data/include/AppFactoryData.h similarity index 89% rename from examples/all-clusters-app/nxp/common/main/include/AppFactoryData.h rename to examples/platform/nxp/common/factory_data/include/AppFactoryData.h index 9c84a89b0f4bc2..35d7ba9ac0745f 100644 --- a/examples/all-clusters-app/nxp/common/main/include/AppFactoryData.h +++ b/examples/platform/nxp/common/factory_data/include/AppFactoryData.h @@ -1,7 +1,7 @@ /* * * Copyright (c) 2023 Project CHIP Authors - * Copyright 2023 NXP + * Copyright 2023-2024 NXP * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,9 @@ extern "C" { #endif +namespace chip { +namespace NXP { +namespace App { /** * Allows to register Matter factory data before initializing the Matter stack */ @@ -35,6 +38,9 @@ CHIP_ERROR AppFactoryData_PreMatterStackInit(void); * Allows to register Matter factory data after initializing the Matter stack */ CHIP_ERROR AppFactoryData_PostMatterStackInit(void); +} // namespace App +} // namespace NXP +} // namespace chip #ifdef __cplusplus } diff --git a/examples/all-clusters-app/nxp/common/main/AppFactoryDataDefaultImpl.cpp b/examples/platform/nxp/common/factory_data/source/AppFactoryDataDefaultImpl.cpp similarity index 75% rename from examples/all-clusters-app/nxp/common/main/AppFactoryDataDefaultImpl.cpp rename to examples/platform/nxp/common/factory_data/source/AppFactoryDataDefaultImpl.cpp index 0c28e5029eac99..4fd176075f00d2 100644 --- a/examples/all-clusters-app/nxp/common/main/AppFactoryDataDefaultImpl.cpp +++ b/examples/platform/nxp/common/factory_data/source/AppFactoryDataDefaultImpl.cpp @@ -1,7 +1,7 @@ /* * * Copyright (c) 2023 Project CHIP Authors - * Copyright 2023 NXP + * Copyright 2023-2024 NXP * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,14 +25,16 @@ #if CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA #include "FactoryDataProvider.h" +#if CONFIG_CHIP_ENCRYPTED_FACTORY_DATA /* * Test key used to encrypt factory data before storing it to the flash. */ static const uint8_t aes128TestKey[] __attribute__((aligned)) = { 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; +#endif /* CONFIG_CHIP_ENCRYPTED_FACTORY_DATA */ #else #include -#endif +#endif /* CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA */ using namespace chip; using namespace ::chip::Credentials; @@ -42,7 +44,7 @@ using namespace ::chip::DeviceLayer; * Allows to register Matter factory data before initializing the Matter stack * Empty content here nothing particular to do. */ -CHIP_ERROR AppFactoryData_PreMatterStackInit(void) +CHIP_ERROR chip::NXP::App::AppFactoryData_PreMatterStackInit(void) { return CHIP_NO_ERROR; } @@ -54,23 +56,22 @@ CHIP_ERROR AppFactoryData_PreMatterStackInit(void) * In this example we assume that the matter factory dataset is encrypted. * This example demonstrates the usage of AES ecb with a software key. */ -CHIP_ERROR AppFactoryData_PostMatterStackInit(void) +CHIP_ERROR chip::NXP::App::AppFactoryData_PostMatterStackInit(void) { - CHIP_ERROR err = CHIP_NO_ERROR; #if CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA +#if CONFIG_CHIP_ENCRYPTED_FACTORY_DATA FactoryDataPrvdImpl().SetEncryptionMode(FactoryDataProvider::encrypt_ecb); FactoryDataPrvdImpl().SetAes128Key(&aes128TestKey[0]); +#endif /* CONFIG_CHIP_ENCRYPTED_FACTORY_DATA */ - err = FactoryDataPrvdImpl().Init(); - if (err == CHIP_NO_ERROR) - { - SetDeviceInstanceInfoProvider(&FactoryDataPrvd()); - SetDeviceAttestationCredentialsProvider(&FactoryDataPrvd()); - SetCommissionableDataProvider(&FactoryDataPrvd()); - } + ReturnErrorOnFailure(FactoryDataPrvdImpl().Init()); + + SetDeviceInstanceInfoProvider(&FactoryDataPrvd()); + SetDeviceAttestationCredentialsProvider(&FactoryDataPrvd()); + SetCommissionableDataProvider(&FactoryDataPrvd()); #else // Initialize device attestation with example one (only for debug purpose) SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider()); -#endif - return err; +#endif /* CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA */ + return CHIP_NO_ERROR; } diff --git a/examples/platform/nxp/common/icd/include/ICDUtil.h b/examples/platform/nxp/common/icd/include/ICDUtil.h new file mode 100644 index 00000000000000..de3a5bcc273194 --- /dev/null +++ b/examples/platform/nxp/common/icd/include/ICDUtil.h @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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. + */ + +#pragma once + +#include + +namespace chip { +namespace NXP { +namespace App { +class ICDUtil : public chip::app::ReadHandler::ApplicationCallback +{ + CHIP_ERROR OnSubscriptionRequested(chip::app::ReadHandler & aReadHandler, + chip::Transport::SecureSession & aSecureSession) override; + friend ICDUtil & GetICDUtil(); + static ICDUtil sICDUtil; +}; + +inline ICDUtil & GetICDUtil() +{ + return ICDUtil::sICDUtil; +} +} // namespace App +} // namespace NXP +} // namespace chip diff --git a/examples/platform/nxp/common/icd/source/ICDUtil.cpp b/examples/platform/nxp/common/icd/source/ICDUtil.cpp new file mode 100644 index 00000000000000..648bb6fe202629 --- /dev/null +++ b/examples/platform/nxp/common/icd/source/ICDUtil.cpp @@ -0,0 +1,36 @@ +/* + * + * 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 "ICDUtil.h" + +chip::NXP::App::ICDUtil chip::NXP::App::ICDUtil::sICDUtil; + +CHIP_ERROR chip::NXP::App::ICDUtil::OnSubscriptionRequested(chip::app::ReadHandler & aReadHandler, + chip::Transport::SecureSession & aSecureSession) +{ + uint16_t agreedMaxInterval = kSubscriptionMaxIntervalPublisherLimit; + uint16_t requestedMinInterval = 0; + uint16_t requestedMaxInterval = 0; + aReadHandler.GetReportingIntervals(requestedMinInterval, requestedMaxInterval); + + if (requestedMaxInterval < agreedMaxInterval) + { + agreedMaxInterval = requestedMaxInterval; + } + return aReadHandler.SetMaxReportingInterval(agreedMaxInterval); +} diff --git a/examples/all-clusters-app/nxp/common/main/include/AppMatterButton.h b/examples/platform/nxp/common/matter_button/include/AppMatterButton.h similarity index 87% rename from examples/all-clusters-app/nxp/common/main/include/AppMatterButton.h rename to examples/platform/nxp/common/matter_button/include/AppMatterButton.h index 0c107269be620a..dd00a5c80d719f 100644 --- a/examples/all-clusters-app/nxp/common/main/include/AppMatterButton.h +++ b/examples/platform/nxp/common/matter_button/include/AppMatterButton.h @@ -1,7 +1,7 @@ /* * * Copyright (c) 2022 Project CHIP Authors - * Copyright 2023 NXP + * Copyright 2023-2024 NXP * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,11 +26,18 @@ extern "C" { #endif +namespace chip { +namespace NXP { +namespace App { /** * API allowing to register matter buttons */ CHIP_ERROR AppMatterButton_registerButtons(void); +} // namespace App +} // namespace NXP +} // namespace chip + #ifdef __cplusplus } #endif diff --git a/examples/platform/nxp/common/matter_button/source/AppMatterButton.cpp b/examples/platform/nxp/common/matter_button/source/AppMatterButton.cpp new file mode 100644 index 00000000000000..be5adc975c2f79 --- /dev/null +++ b/examples/platform/nxp/common/matter_button/source/AppMatterButton.cpp @@ -0,0 +1,81 @@ +/* + * + * Copyright (c) 2022 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 "AppMatterButton.h" +#include "AppTaskBase.h" +#include "board_comp.h" +#include "fsl_component_timer_manager.h" +#include "fwk_platform.h" +#include + +static BUTTON_HANDLE_DEFINE(sdkButtonHandle); + +static button_status_t AppMatterButton_ButtonCallback(void * buttonHandle, button_callback_message_t * message, + void * callbackParam) +{ + switch (message->event) + { + case kBUTTON_EventShortPress: + case kBUTTON_EventOneClick: + chip::NXP::App::GetAppTask().SwitchCommissioningStateHandler(); + break; + case kBUTTON_EventLongPress: + chip::NXP::App::GetAppTask().FactoryResetHandler(); + break; + default: + break; + } + return kStatus_BUTTON_Success; +} + +CHIP_ERROR chip::NXP::App::AppMatterButton_registerButtons(void) +{ + button_config_t buttonConfig; + button_status_t bStatus; + CHIP_ERROR err = CHIP_NO_ERROR; + + do + { + /* Init the Platform Timer Manager */ + if (PLATFORM_InitTimerManager() != 0) + { + err = CHIP_ERROR_UNEXPECTED_EVENT; + ChipLogError(DeviceLayer, "tmr init error"); + break; + } + + /* Init board buttons */ + bStatus = BOARD_InitButton((button_handle_t) sdkButtonHandle); + if (bStatus != kStatus_BUTTON_Success) + { + err = CHIP_ERROR_UNEXPECTED_EVENT; + ChipLogError(DeviceLayer, "button init error"); + break; + } + bStatus = BUTTON_InstallCallback((button_handle_t) sdkButtonHandle, AppMatterButton_ButtonCallback, NULL); + + if (bStatus != kStatus_BUTTON_Success) + { + err = CHIP_ERROR_UNEXPECTED_EVENT; + ChipLogError(DeviceLayer, "button init error"); + break; + } + } while (0); + + return err; +} diff --git a/examples/all-clusters-app/nxp/common/main/AppMatterButtonEmpty.cpp b/examples/platform/nxp/common/matter_button/source/AppMatterButtonEmpty.cpp similarity index 89% rename from examples/all-clusters-app/nxp/common/main/AppMatterButtonEmpty.cpp rename to examples/platform/nxp/common/matter_button/source/AppMatterButtonEmpty.cpp index dfd3eca9eeb105..c9f602cbd8d97d 100644 --- a/examples/all-clusters-app/nxp/common/main/AppMatterButtonEmpty.cpp +++ b/examples/platform/nxp/common/matter_button/source/AppMatterButtonEmpty.cpp @@ -1,7 +1,7 @@ /* * * Copyright (c) 2022 Project CHIP Authors - * Copyright 2023 NXP + * Copyright 2023-2024 NXP * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ #include "AppMatterButton.h" -CHIP_ERROR AppMatterButton_registerButtons(void) +CHIP_ERROR chip::NXP::App::AppMatterButton_registerButtons(void) { /* Empty content could be re-defined in a dedicated platform AppMatterButton_registerButtons function */ return CHIP_NO_ERROR; diff --git a/examples/all-clusters-app/nxp/common/main/include/AppMatterCli.h b/examples/platform/nxp/common/matter_cli/include/AppMatterCli.h similarity index 61% rename from examples/all-clusters-app/nxp/common/main/include/AppMatterCli.h rename to examples/platform/nxp/common/matter_cli/include/AppMatterCli.h index ee043e52320782..68f24fc8fcbfdc 100644 --- a/examples/all-clusters-app/nxp/common/main/include/AppMatterCli.h +++ b/examples/platform/nxp/common/matter_cli/include/AppMatterCli.h @@ -1,5 +1,5 @@ /* - * Copyright 2022 NXP + * Copyright 2022-2024 NXP * All rights reserved. * * SPDX-License-Identifier: BSD-3-Clause @@ -14,10 +14,21 @@ extern "C" { #endif +namespace chip { +namespace NXP { +namespace App { /** * API allowing to register matter cli command */ CHIP_ERROR AppMatterCli_RegisterCommands(void); +/** + * API allowing to start matter cli task + */ +CHIP_ERROR AppMatterCli_StartTask(void); + +} // namespace App +} // namespace NXP +} // namespace chip #ifdef __cplusplus } diff --git a/examples/all-clusters-app/nxp/common/main/AppMatterCli.cpp b/examples/platform/nxp/common/matter_cli/source/AppMatterCli.cpp similarity index 80% rename from examples/all-clusters-app/nxp/common/main/AppMatterCli.cpp rename to examples/platform/nxp/common/matter_cli/source/AppMatterCli.cpp index 663bc85920ddb4..e206ad0a2d4127 100644 --- a/examples/all-clusters-app/nxp/common/main/AppMatterCli.cpp +++ b/examples/platform/nxp/common/matter_cli/source/AppMatterCli.cpp @@ -1,7 +1,7 @@ /* * * Copyright (c) 2022 Project CHIP Authors - * Copyright 2023 NXP + * Copyright 2023-2024 NXP * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +18,15 @@ */ #include "AppMatterCli.h" -#include "AppTask.h" +#include "AppTaskBase.h" #include #include #include +#ifdef CONFIG_CHIP_APP_DEVICE_TYPE_LAUNDRY_WASHER +#include +#endif /* CONFIG_CHIP_APP_DEVICE_TYPE_LAUNDRY_WASHER */ + #ifdef ENABLE_CHIP_SHELL #include "task.h" #include @@ -38,6 +42,9 @@ static bool isShellInitialized = false; #define MATTER_CLI_LOG(...) #endif /* ENABLE_CHIP_SHELL */ +using namespace chip; +using namespace chip::app::Clusters; + void AppMatterCliTask(void * args) { #ifdef ENABLE_CHIP_SHELL @@ -52,11 +59,11 @@ CHIP_ERROR commissioningManager(int argc, char * argv[]) CHIP_ERROR error = CHIP_NO_ERROR; if (strncmp(argv[0], "on", 2) == 0) { - GetAppTask().StartCommissioningHandler(); + chip::NXP::App::GetAppTask().StartCommissioningHandler(); } else if (strncmp(argv[0], "off", 3) == 0) { - GetAppTask().StopCommissioningHandler(); + chip::NXP::App::GetAppTask().StopCommissioningHandler(); } else { @@ -68,7 +75,7 @@ CHIP_ERROR commissioningManager(int argc, char * argv[]) CHIP_ERROR cliFactoryReset(int argc, char * argv[]) { - GetAppTask().FactoryResetHandler(); + chip::NXP::App::GetAppTask().FactoryResetHandler(); return CHIP_NO_ERROR; } @@ -84,7 +91,7 @@ CHIP_ERROR cliReset(int argc, char * argv[]) return CHIP_NO_ERROR; } -CHIP_ERROR AppMatterCli_RegisterCommands(void) +CHIP_ERROR chip::NXP::App::AppMatterCli_RegisterCommands(void) { #ifdef ENABLE_CHIP_SHELL if (!isShellInitialized) @@ -123,15 +130,21 @@ CHIP_ERROR AppMatterCli_RegisterCommands(void) }; Engine::Root().RegisterCommands(kCommands, sizeof(kCommands) / sizeof(kCommands[0])); - - if (xTaskCreate(&AppMatterCliTask, "AppMatterCli_task", MATTER_CLI_TASK_SIZE, NULL, 1, &AppMatterCliTaskHandle) != pdPASS) - { - ChipLogError(Shell, "Failed to start Matter CLI task"); - return CHIP_ERROR_INTERNAL; - } isShellInitialized = true; } #endif /* ENABLE_CHIP_SHELL */ return CHIP_NO_ERROR; } + +CHIP_ERROR chip::NXP::App::AppMatterCli_StartTask() +{ +#ifdef ENABLE_CHIP_SHELL + if (xTaskCreate(&AppMatterCliTask, "AppMatterCli_task", MATTER_CLI_TASK_SIZE, NULL, 1, &AppMatterCliTaskHandle) != pdPASS) + { + ChipLogError(Shell, "Failed to start Matter CLI task"); + return CHIP_ERROR_INTERNAL; + } +#endif /* ENABLE_CHIP_SHELL */ + return CHIP_NO_ERROR; +} diff --git a/examples/platform/nxp/common/OTARequestorInitiator.h b/examples/platform/nxp/common/ota_requestor/include/OTARequestorInitiator.h similarity index 78% rename from examples/platform/nxp/common/OTARequestorInitiator.h rename to examples/platform/nxp/common/ota_requestor/include/OTARequestorInitiator.h index 808a58d03dfe91..4054bb4a2a473f 100644 --- a/examples/platform/nxp/common/OTARequestorInitiator.h +++ b/examples/platform/nxp/common/ota_requestor/include/OTARequestorInitiator.h @@ -1,7 +1,7 @@ /* * * Copyright (c) 2022 Project CHIP Authors - * Copyright 2023 NXP + * Copyright 2023-2024 NXP * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,11 +23,25 @@ #include "app/clusters/ota-requestor/DefaultOTARequestor.h" #include "app/clusters/ota-requestor/DefaultOTARequestorDriver.h" #include "app/clusters/ota-requestor/DefaultOTARequestorStorage.h" + +#ifdef CONFIG_CHIP_OTA_IMAGE_PROCESSOR_HEADER +#include CONFIG_CHIP_OTA_IMAGE_PROCESSOR_HEADER +#else +#ifdef CONFIG_CHIP +#include +#else #include "platform/nxp/common/OTAImageProcessorImpl.h" +#endif /* CONFIG_CHIP */ +#endif /* CONFIG_CHIP_OTA_IMAGE_PROCESSOR_HEADER */ + #include using namespace chip; +using namespace chip::DeviceLayer; +namespace chip { +namespace NXP { +namespace App { class OTARequestorInitiator { public: @@ -48,3 +62,6 @@ class OTARequestorInitiator BDXDownloader gDownloader; OTAImageProcessorImpl gImageProcessor; }; +} // namespace App +} // namespace NXP +} // namespace chip diff --git a/examples/platform/nxp/common/ota_requestor/source/OTARequestorInitiator.cpp b/examples/platform/nxp/common/ota_requestor/source/OTARequestorInitiator.cpp new file mode 100644 index 00000000000000..3f71a8268cc1bf --- /dev/null +++ b/examples/platform/nxp/common/ota_requestor/source/OTARequestorInitiator.cpp @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * Copyright 2023-2024 NXP + * 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 "OTARequestorInitiator.h" + +extern "C" { +#include "mflash_drv.h" +} +using namespace chip; + +void chip::NXP::App::OTARequestorInitiator::HandleSelfTest() +{ + /* If application is in test mode after an OTA update + mark image as "ok" to switch the update state to permanent + (if we have arrived this far, the bootloader had validated the image) */ + + mflash_drv_init(); + + OtaImgState_t update_state; + + /* Retrieve current update state */ + update_state = OTA_GetImgState(); + + if (update_state == OtaImgState_RunCandidate) + { + if (OTA_UpdateImgState(OtaImgState_Permanent) != gOtaSuccess_c) + { + ChipLogError(SoftwareUpdate, "Self-testing : Failed to switch update state to permanent"); + return; + } + + ChipLogProgress(SoftwareUpdate, "Successful software update... applied permanently"); + } + + OTA_Initialize(); + + /* If the image is not marked ok, the bootloader will automatically revert back to primary application at next reboot */ +} diff --git a/examples/platform/nxp/common/OTARequestorInitiator.cpp b/examples/platform/nxp/common/ota_requestor/source/OTARequestorInitiatorCommon.cpp similarity index 61% rename from examples/platform/nxp/common/OTARequestorInitiator.cpp rename to examples/platform/nxp/common/ota_requestor/source/OTARequestorInitiatorCommon.cpp index a7a898bb3718d1..cbf4b0ef01ce2d 100644 --- a/examples/platform/nxp/common/OTARequestorInitiator.cpp +++ b/examples/platform/nxp/common/ota_requestor/source/OTARequestorInitiatorCommon.cpp @@ -1,7 +1,7 @@ /* * * Copyright (c) 2022 Project CHIP Authors - * Copyright 2023 NXP + * Copyright 2023-2024 NXP * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,13 +19,9 @@ #include "OTARequestorInitiator.h" -extern "C" { -#include "mflash_drv.h" -} - using namespace chip; -void OTARequestorInitiator::InitOTA(intptr_t context) +void chip::NXP::App::OTARequestorInitiator::InitOTA(intptr_t context) { auto * otaRequestorInit = reinterpret_cast(context); // Set the global instance of the OTA requestor core component @@ -40,32 +36,3 @@ void OTARequestorInitiator::InitOTA(intptr_t context) // Set the image processor instance used for handling image being downloaded otaRequestorInit->gDownloader.SetImageProcessorDelegate(&otaRequestorInit->gImageProcessor); } - -void OTARequestorInitiator::HandleSelfTest() -{ - /* If application is in test mode after an OTA update - mark image as "ok" to switch the update state to permanent - (if we have arrived this far, the bootloader had validated the image) */ - - mflash_drv_init(); - - OtaImgState_t update_state; - - /* Retrieve current update state */ - update_state = OTA_GetImgState(); - - if (update_state == OtaImgState_RunCandidate) - { - if (OTA_UpdateImgState(OtaImgState_Permanent) != gOtaSuccess_c) - { - ChipLogError(SoftwareUpdate, "Self-testing : Failed to switch update state to permanent"); - return; - } - - ChipLogProgress(SoftwareUpdate, "Successful software update... applied permanently"); - } - - OTA_Initialize(); - - /* If the image is not marked ok, the bootloader will automatically revert back to primary application at next reboot */ -} diff --git a/examples/platform/nxp/common/ota_requestor/source/OTARequestorInitiatorZephyr.cpp b/examples/platform/nxp/common/ota_requestor/source/OTARequestorInitiatorZephyr.cpp new file mode 100644 index 00000000000000..f176c3e67804f7 --- /dev/null +++ b/examples/platform/nxp/common/ota_requestor/source/OTARequestorInitiatorZephyr.cpp @@ -0,0 +1,41 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * Copyright 2023-2024 NXP + * 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 "OTARequestorInitiator.h" + +#include +#include + +using namespace chip; + +void chip::NXP::App::OTARequestorInitiator::HandleSelfTest() +{ + if (mcuboot_swap_type() == BOOT_SWAP_TYPE_REVERT) + { + int img_confirmation = boot_write_img_confirmed(); + if (img_confirmation) + { + ChipLogError(SoftwareUpdate, "Self-testing : Failed to switch update state to permanent"); + } + else + { + ChipLogProgress(SoftwareUpdate, "Successful software update... applied permanently"); + } + } +} diff --git a/examples/platform/nxp/k32w/k32w0/BUILD.gn b/examples/platform/nxp/k32w/k32w0/BUILD.gn index 8abd38f2a250eb..eb0c79742f3b99 100644 --- a/examples/platform/nxp/k32w/k32w0/BUILD.gn +++ b/examples/platform/nxp/k32w/k32w0/BUILD.gn @@ -13,9 +13,11 @@ # limitations under the License. import("//build_overrides/chip.gni") -import("//build_overrides/k32w0_sdk.gni") +import("//build_overrides/nxp_sdk.gni") -import("${k32w0_sdk_build_root}/k32w0_sdk.gni") +import("${nxp_sdk_build_root}/nxp_sdk.gni") + +import("${nxp_sdk_build_root}/${nxp_sdk_name}/${nxp_sdk_name}.gni") config("chip_examples_project_config") { include_dirs = [ diff --git a/examples/platform/nxp/k32w/k32w0/app/args.gni b/examples/platform/nxp/k32w/k32w0/app/args.gni index 13a35134496eb4..44a96b88a65b1b 100644 --- a/examples/platform/nxp/k32w/k32w0/app/args.gni +++ b/examples/platform/nxp/k32w/k32w0/app/args.gni @@ -16,9 +16,6 @@ import("//build_overrides/chip.gni") import("${chip_root}/src/platform/nxp/k32w/k32w0/args.gni") -arm_float_abi = "soft" -arm_cpu = "cortex-m4" - openthread_project_core_config_file = "OpenThreadConfig.h" chip_ble_project_config_include = "" diff --git a/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld b/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld index d3ea6ac41daf78..c51182b104ec53 100644 --- a/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld +++ b/examples/platform/nxp/k32w/k32w0/app/ldscripts/chip-k32w0x-linker.ld @@ -94,7 +94,7 @@ OUTPUT_FORMAT ("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") */ BOOT_RESUME_STACK_SIZE = 1024; -STACK_SIZE = (4096); +STACK_SIZE = DEFINED(__stack_size__) ? __stack_size__ : 0x01000; MEM_RAM0_BASE = 0x4000400; MEM_RAM0_SIZE = 0x0015c00; @@ -130,9 +130,10 @@ MEMORY { Flash640 (rx) : ORIGIN = m_app_start, LENGTH = m_app_size - SCRATCH_RAM(rwx) : ORIGIN = 0x4000000, LENGTH = 0x400 /* 1K bytes (alias SCRATCH_RAM) */ - RAM0 (rwx) : ORIGIN = 0x4000400, LENGTH = 0x0015c00 /* 87K bytes (alias RAM) */ - RAM1 (rwx) : ORIGIN = 0x4020000, LENGTH = 0x10000 /* 64K bytes (alias RAM2) */ + SCRATCH_RAM(rwx) : ORIGIN = 0x4000000, LENGTH = 0x400 /* 1K bytes (alias SCRATCH_RAM) */ + RAM0 (rwx) : ORIGIN = 0x4000400, LENGTH = 0x0015BE0 /* [87K - 32] bytes (alias RAM) */ + reserved (rwx) : ORIGIN = 0x4015FE0, LENGTH = 0x20 /* 32 bytes (reserved for ROM code) */ + RAM1 (rwx) : ORIGIN = 0x4020000, LENGTH = 0x10000 /* 64K bytes (alias RAM2) */ } /* Define a symbol for the top of each memory region */ @@ -431,6 +432,7 @@ SECTIONS PROVIDE(_scratch_buf_end = __scratch_area_top__); __StackLimit = _vStackTop - STACK_SIZE; + ASSERT(__StackLimit >= _end_heap, "Stack and heap spaces are overlapping!") __MATTER_FACTORY_DATA_START = FACTORY_DATA_START_ADDRESS; __MATTER_FACTORY_DATA_SIZE = m_factory_data_size; diff --git a/examples/platform/nxp/k32w/k32w0/app/support/BUILD.gn b/examples/platform/nxp/k32w/k32w0/app/support/BUILD.gn index 014199c60a7619..7b10c468a02fe6 100644 --- a/examples/platform/nxp/k32w/k32w0/app/support/BUILD.gn +++ b/examples/platform/nxp/k32w/k32w0/app/support/BUILD.gn @@ -13,7 +13,7 @@ # limitations under the License. import("//build_overrides/chip.gni") -import("//build_overrides/k32w0_sdk.gni") +import("//build_overrides/nxp_sdk.gni") config("support_config") { include_dirs = [ "../../../../.." ] diff --git a/examples/platform/nxp/k32w/k32w0/args.gni b/examples/platform/nxp/k32w/k32w0/args.gni index 954fad9a2faee2..5af5e1d18b3123 100644 --- a/examples/platform/nxp/k32w/k32w0/args.gni +++ b/examples/platform/nxp/k32w/k32w0/args.gni @@ -16,11 +16,6 @@ import("//build_overrides/chip.gni") import("${chip_root}/src/platform/nxp/k32w/k32w0/args.gni") -arm_float_abi = "soft" -arm_cpu = "cortex-m4" -arm_arch = "armv7e-m" - -chip_openthread_ftd = false openthread_core_config_deps = [] openthread_core_config_deps = [ "${chip_root}/examples/platform/nxp/k32w/k32w0:openthread_core_config_k32w0_chip_examples" ] diff --git a/examples/platform/nxp/k32w/k32w0/scripts/sign-outdir.py b/examples/platform/nxp/k32w/k32w0/scripts/sign-outdir.py index ea4a888c9e3555..8660b4bc5f4b06 100644 --- a/examples/platform/nxp/k32w/k32w0/scripts/sign-outdir.py +++ b/examples/platform/nxp/k32w/k32w0/scripts/sign-outdir.py @@ -7,7 +7,8 @@ def main(args): if "NXP_K32W0_SDK_ROOT" in os.environ and os.environ["NXP_K32W0_SDK_ROOT"] != "": sign_images_path = os.environ["NXP_K32W0_SDK_ROOT"] + "/tools/imagetool/sign_images.sh" else: - sign_images_path = os.getcwd() + "/../../../../../../../third_party/nxp/k32w0_sdk/repo/core/tools/imagetool/sign_images.sh" + sign_images_path = os.path.abspath( + __file__ + "/../../../../../../../third_party/nxp/k32w0_sdk/repo/core/tools/imagetool/sign_images.sh") # Give execute permission if needed if os.access(sign_images_path, os.X_OK) is False: diff --git a/examples/platform/nxp/k32w/k32w1/util/LEDWidget.cpp b/examples/platform/nxp/k32w/k32w1/util/LEDWidget.cpp index b7a01ae6c7545f..695da50e3dae32 100644 --- a/examples/platform/nxp/k32w/k32w1/util/LEDWidget.cpp +++ b/examples/platform/nxp/k32w/k32w1/util/LEDWidget.cpp @@ -23,6 +23,10 @@ #include "app.h" +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED +#include "LED_Dimmer.h" +#endif + #if (defined(gAppLedCnt_c) && (gAppLedCnt_c > 0)) void LEDWidget::Init(uint8_t led, bool inverted) @@ -48,6 +52,13 @@ void LEDWidget::Set(bool state) DoSet(state); } +void LEDWidget::SetLevel(uint8_t level) +{ +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED + move_to_level(level); +#endif +} + void LEDWidget::Blink(uint32_t changeRateMS) { Blink(changeRateMS, changeRateMS); @@ -70,7 +81,12 @@ void LEDWidget::Animate() if (nextChangeTimeMS < nowMS) { +#if CHIP_CONFIG_ENABLE_DIMMABLE_LED + SetLevel(!mState * 254); + mState = !mState; +#else DoSet(!mState); +#endif mLastChangeTimeMS = nowMS; } } diff --git a/examples/platform/nxp/k32w/k32w1/util/LED_Dimmer.cpp b/examples/platform/nxp/k32w/k32w1/util/LED_Dimmer.cpp new file mode 100644 index 00000000000000..02a62724716e53 --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/util/LED_Dimmer.cpp @@ -0,0 +1,193 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2019 Google LLC. + * 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 "LED_Dimmer.h" +#include "fsl_common.h" +#include "fsl_port.h" +#include "fsl_tpm.h" + +#include + +#define BOARD_TPM_BASEADDR TPM0 +#define TPM_SOURCE_CLOCK CLOCK_GetIpFreq(kCLOCK_Tpm0) +#define BOARD_FIRST_TPM_CHANNEL kTPM_Chnl_0 +#define BOARD_SECOND_TPM_CHANNEL kTPM_Chnl_1 +#define BOARD_THIRD_TPM_CHANNEL kTPM_Chnl_2 +#ifndef TPM_LED_ON_LEVEL +#define TPM_LED_ON_LEVEL kTPM_HighTrue +#endif +#ifndef DEMO_PWM_FREQUENCY +#define DEMO_PWM_FREQUENCY (24000U) +#endif + +volatile uint8_t updatedDutycycle = 0U; + +void init_dimmable() +{ + const port_pin_config_t porta20_pin17_config = { /* Internal pull-up/down resistor is disabled */ + (uint16_t) kPORT_PullDisable, + /* Low internal pull resistor value is selected. */ + (uint16_t) kPORT_LowPullResistor, + /* Fast slew rate is configured */ + (uint16_t) kPORT_FastSlewRate, + /* Passive input filter is disabled */ + (uint16_t) kPORT_PassiveFilterDisable, + /* Open drain output is disabled */ + (uint16_t) kPORT_OpenDrainDisable, + /* Low drive strength is configured */ + (uint16_t) kPORT_LowDriveStrength, + /* Normal drive strength is configured */ + (uint16_t) kPORT_NormalDriveStrength, + /* Pin is configured as TPM0_CH0 */ + (uint16_t) kPORT_MuxAlt5, + /* Pin Control Register fields [15:0] are not locked */ + (uint16_t) kPORT_UnlockRegister + }; + /* PORTA20 (pin 17) is configured as TPM0_CH0 */ + PORT_SetPinConfig(PORTA, 20U, &porta20_pin17_config); + + const port_pin_config_t porta21_pin18_config = { /* Internal pull-up/down resistor is disabled */ + (uint16_t) kPORT_PullDisable, + /* Low internal pull resistor value is selected. */ + (uint16_t) kPORT_LowPullResistor, + /* Fast slew rate is configured */ + (uint16_t) kPORT_FastSlewRate, + /* Passive input filter is disabled */ + (uint16_t) kPORT_PassiveFilterDisable, + /* Open drain output is disabled */ + (uint16_t) kPORT_OpenDrainDisable, + /* Low drive strength is configured */ + (uint16_t) kPORT_LowDriveStrength, + /* Normal drive strength is configured */ + (uint16_t) kPORT_NormalDriveStrength, + /* Pin is configured as TPM0_CH0 */ + (uint16_t) kPORT_MuxAlt5, + /* Pin Control Register fields [15:0] are not locked */ + (uint16_t) kPORT_UnlockRegister + }; + /* PORTA21 (pin 18) is configured as TPM0_CH0 */ + PORT_SetPinConfig(PORTA, 21U, &porta21_pin18_config); + + const port_pin_config_t porta19_pin14_config = { /* Internal pull-up/down resistor is disabled */ + (uint16_t) kPORT_PullDisable, + /* Low internal pull resistor value is selected. */ + (uint16_t) kPORT_LowPullResistor, + /* Fast slew rate is configured */ + (uint16_t) kPORT_FastSlewRate, + /* Passive input filter is disabled */ + (uint16_t) kPORT_PassiveFilterDisable, + /* Open drain output is disabled */ + (uint16_t) kPORT_OpenDrainDisable, + /* Low drive strength is configured */ + (uint16_t) kPORT_LowDriveStrength, + /* Normal drive strength is configured */ + (uint16_t) kPORT_NormalDriveStrength, + /* Pin is configured as TPM0_CH0 */ + (uint16_t) kPORT_MuxAlt5, + /* Pin Control Register fields [15:0] are not locked */ + (uint16_t) kPORT_UnlockRegister + }; + /* PORTA19 (pin 14) is configured as TPM0_CH0 */ + PORT_SetPinConfig(PORTA, 19U, &porta19_pin14_config); + + init_tpm(); +} + +void init_tpm() +{ + tpm_config_t tpmInfo; + tpm_chnl_pwm_signal_param_t tpmParam[3]; + + /* TPM 0 Clock Gate Control: Clock enabled */ + CLOCK_EnableClock(kCLOCK_Tpm0); + /* Set the source for the LPIT module */ + CLOCK_SetIpSrc(kCLOCK_Tpm0, kCLOCK_IpSrcFro6M); + + /* Fill in the TPM config struct with the default settings */ + TPM_GetDefaultConfig(&tpmInfo); + /* Calculate the clock division based on the PWM frequency to be obtained */ + tpmInfo.prescale = TPM_CalculateCounterClkDiv(BOARD_TPM_BASEADDR, DEMO_PWM_FREQUENCY, TPM_SOURCE_CLOCK); + /* Initialize TPM module */ + TPM_Init(BOARD_TPM_BASEADDR, &tpmInfo); + + /* Configure tpm params with frequency 24kHZ */ + tpmParam[0].chnlNumber = (tpm_chnl_t) BOARD_FIRST_TPM_CHANNEL; +#if (defined(FSL_FEATURE_TPM_HAS_PAUSE_LEVEL_SELECT) && FSL_FEATURE_TPM_HAS_PAUSE_LEVEL_SELECT) + tpmParam[0].pauseLevel = kTPM_ClearOnPause; +#endif + tpmParam[0].level = TPM_LED_ON_LEVEL; + tpmParam[0].dutyCyclePercent = updatedDutycycle; + + tpmParam[1].chnlNumber = (tpm_chnl_t) BOARD_SECOND_TPM_CHANNEL; +#if (defined(FSL_FEATURE_TPM_HAS_PAUSE_LEVEL_SELECT) && FSL_FEATURE_TPM_HAS_PAUSE_LEVEL_SELECT) + tpmParam[1].pauseLevel = kTPM_ClearOnPause; +#endif + tpmParam[1].level = TPM_LED_ON_LEVEL; + tpmParam[1].dutyCyclePercent = updatedDutycycle; + + tpmParam[2].chnlNumber = (tpm_chnl_t) BOARD_THIRD_TPM_CHANNEL; +#if (defined(FSL_FEATURE_TPM_HAS_PAUSE_LEVEL_SELECT) && FSL_FEATURE_TPM_HAS_PAUSE_LEVEL_SELECT) + tpmParam[2].pauseLevel = kTPM_ClearOnPause; +#endif + tpmParam[2].level = TPM_LED_ON_LEVEL; + tpmParam[2].dutyCyclePercent = updatedDutycycle; + + if (kStatus_Success != + TPM_SetupPwm(BOARD_TPM_BASEADDR, tpmParam, 2U, kTPM_EdgeAlignedPwm, DEMO_PWM_FREQUENCY, TPM_SOURCE_CLOCK)) + { + return; + } + + TPM_StartTimer(BOARD_TPM_BASEADDR, kTPM_SystemClock); +} + +void move_to_level(uint8_t level) +{ + uint8_t control; + + updatedDutycycle = static_cast(level * 90) / 255; + + control = TPM_GetChannelContorlBits(BOARD_TPM_BASEADDR, (tpm_chnl_t) BOARD_FIRST_TPM_CHANNEL); + + /* Disable output on each channel of the pair before updating the dutycycle */ + TPM_DisableChannel(BOARD_TPM_BASEADDR, (tpm_chnl_t) BOARD_FIRST_TPM_CHANNEL); + TPM_DisableChannel(BOARD_TPM_BASEADDR, (tpm_chnl_t) BOARD_SECOND_TPM_CHANNEL); + TPM_DisableChannel(BOARD_TPM_BASEADDR, (tpm_chnl_t) BOARD_THIRD_TPM_CHANNEL); + + /* Update PWM duty cycle */ + if ((kStatus_Success == + TPM_UpdatePwmDutycycle(BOARD_TPM_BASEADDR, (tpm_chnl_t) BOARD_FIRST_TPM_CHANNEL, kTPM_EdgeAlignedPwm, updatedDutycycle)) && + (kStatus_Success == + TPM_UpdatePwmDutycycle(BOARD_TPM_BASEADDR, (tpm_chnl_t) BOARD_SECOND_TPM_CHANNEL, kTPM_EdgeAlignedPwm, + updatedDutycycle)) && + (kStatus_Success == + TPM_UpdatePwmDutycycle(BOARD_TPM_BASEADDR, (tpm_chnl_t) BOARD_THIRD_TPM_CHANNEL, kTPM_EdgeAlignedPwm, updatedDutycycle))) + { + ChipLogError(NotSpecified, "TPM: Duty cycle updated successfully"); + } + else + { + ChipLogError(NotSpecified, "ERR: Duty cycle failed to updated"); + } + + /* Start output on each channel of the pair with updated dutycycle */ + TPM_EnableChannel(BOARD_TPM_BASEADDR, (tpm_chnl_t) BOARD_FIRST_TPM_CHANNEL, control); + TPM_EnableChannel(BOARD_TPM_BASEADDR, (tpm_chnl_t) BOARD_SECOND_TPM_CHANNEL, control); + TPM_EnableChannel(BOARD_TPM_BASEADDR, (tpm_chnl_t) BOARD_THIRD_TPM_CHANNEL, control); +} diff --git a/examples/platform/nxp/k32w/k32w1/util/include/LEDWidget.h b/examples/platform/nxp/k32w/k32w1/util/include/LEDWidget.h index 4d55f246d49e05..a73ab8a904018b 100644 --- a/examples/platform/nxp/k32w/k32w1/util/include/LEDWidget.h +++ b/examples/platform/nxp/k32w/k32w1/util/include/LEDWidget.h @@ -25,6 +25,7 @@ class LEDWidget public: void Init(uint8_t gpioNum, bool inverted); void Set(bool state); + void SetLevel(uint8_t level); void Invert(void); void Blink(uint32_t changeRateMS); void Blink(uint32_t onTimeMS, uint32_t offTimeMS); diff --git a/examples/platform/nxp/k32w/k32w1/util/include/LED_Dimmer.h b/examples/platform/nxp/k32w/k32w1/util/include/LED_Dimmer.h new file mode 100644 index 00000000000000..424ffa20c97bbd --- /dev/null +++ b/examples/platform/nxp/k32w/k32w1/util/include/LED_Dimmer.h @@ -0,0 +1,21 @@ +/* + * + * Copyright (c) 2020 Google LLC. + * 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. + */ + +void init_dimmable(); +void init_tpm(); +void move_to_level(uint8_t level); diff --git a/examples/platform/nxp/rt/rw61x/app/ldscripts/RW610_flash.ld b/examples/platform/nxp/rt/rw61x/app/ldscripts/RW610_flash.ld index e3b51876eca963..eec10d6fddafde 100644 --- a/examples/platform/nxp/rt/rw61x/app/ldscripts/RW610_flash.ld +++ b/examples/platform/nxp/rt/rw61x/app/ldscripts/RW610_flash.ld @@ -78,12 +78,12 @@ _stated_ | m_text | ' fw_storage ' ACTIVE_APP (slot - - - +---------------+ - - - - - - - - - - - - - - - - - - - - + + + + - - - - +---------------+ - - - - - - - - - - - - - - - - - - - - - 4k | FactoryData | - - - +---------------+ - - - - - - - - - - - - - - - - - - - - | | NV_STORAGE_START_ADDRESS | NVM_region | | | NV_STORAGE_END_ADDRESS + - - - +---------------+ - - - - - - - - - - - - - - - - - - - - + 4k | FactoryData | - - - +---------------+ - - - - - - - - - - - - - - - - - - - - 0x0C00_0000 FLASH_END @@ -174,20 +174,20 @@ FW_UPDATE_STORAGE_END = FW_UPDATE_STORAGE_START + FW_UPDATE_STORAG fw_top = FW_UPDATE_STORAGE_END + 1; +/*** FactoryData space 1 sector is reserved ***/ +__FACTORY_DATA_SIZE = m_sector_size; +__FACTORY_DATA_END = m_flash_end; +__FACTORY_DATA_START = __FACTORY_DATA_END - __FACTORY_DATA_SIZE + 1; +__FACTORY_DATA_START_OFFSET = __FACTORY_DATA_START - m_flash_start; + /* FileSystem Configuration */ -NV_STORAGE_END_ADDRESS = m_flash_end; +NV_STORAGE_END_ADDRESS = __FACTORY_DATA_START - 1; NV_STORAGE_MAX_SECTORS = DEFINED(gNVMSectorCountLink_d) ? gNVMSectorCountLink_d : 16; NV_STORAGE_SECTOR_SIZE = m_sector_size; NV_STORAGE_SIZE = NV_STORAGE_SECTOR_SIZE * NV_STORAGE_MAX_SECTORS; NV_STORAGE_START_ADDRESS = NV_STORAGE_END_ADDRESS - NV_STORAGE_SIZE + 1; NV_STORAGE_START_ADDRESS_OFFSET = NV_STORAGE_START_ADDRESS - m_flash_start; -/*** FactoryData space 1 sector is reserved ***/ -__FACTORY_DATA_SIZE = m_sector_size; -__FACTORY_DATA_END = NV_STORAGE_START_ADDRESS -1; -__FACTORY_DATA_START = __FACTORY_DATA_END - __FACTORY_DATA_SIZE + 1; -__FACTORY_DATA_START_OFFSET = __FACTORY_DATA_START - m_flash_start; - /* -------------------------------------------------------------------------- */ /* RAM */ /* -------------------------------------------------------------------------- */ @@ -491,6 +491,21 @@ SECTIONS } > m_data __DATA_END = __DATA_ROM + (__data_end__ - __data_start__); + + /* Place holder for RAM function. + * By default CodeQuickAccess is placed in .data section, but if there's dedicated mem region + * allocated on code bus (SRAM address starts from 0x0), CodeQuickAccess can be inserted in + * .ram_function and the .ram_function can be located in the code bus region instead of m_data + * to get better performance. Remember to define __STARTUP_INITIALIZE_RAMFUNCTION macro to + * build startup_xx.S to ensure the RAM function copy from flash to RAM. */ + .ram_function : AT(__DATA_END) + { + . = ALIGN(4); + __ram_function_start__ = .; + /* RAM function sections */ + . = ALIGN(4); + __ram_function_end__ = .; + } > m_data text_end = ORIGIN(m_text) + LENGTH(m_text); diff --git a/examples/platform/nxp/rt/rw61x/app/project_include/freeRTOS/FreeRTOSConfig.h b/examples/platform/nxp/rt/rw61x/app/project_include/freeRTOS/FreeRTOSConfig.h index cb7d563f756f8c..b61ed92c03d286 100644 --- a/examples/platform/nxp/rt/rw61x/app/project_include/freeRTOS/FreeRTOSConfig.h +++ b/examples/platform/nxp/rt/rw61x/app/project_include/freeRTOS/FreeRTOSConfig.h @@ -51,7 +51,7 @@ /* stack size increased for NVM/LITTLE_FS save in idle task */ #define configMINIMAL_STACK_SIZE ((uint16_t) 2048) #ifndef configTOTAL_HEAP_SIZE -#define configTOTAL_HEAP_SIZE ((size_t) (122 * 1024)) +#define configTOTAL_HEAP_SIZE ((size_t) (126 * 1024)) #endif #define configAPPLICATION_ALLOCATED_HEAP 1 #define configSUPPORT_STATIC_ALLOCATION 0 diff --git a/examples/platform/nxp/rt/rw61x/app/project_include/openthread/OpenThreadConfig.h b/examples/platform/nxp/rt/rw61x/app/project_include/openthread/OpenThreadConfig.h index 166222d1295311..a5bc2999e80cfb 100644 --- a/examples/platform/nxp/rt/rw61x/app/project_include/openthread/OpenThreadConfig.h +++ b/examples/platform/nxp/rt/rw61x/app/project_include/openthread/OpenThreadConfig.h @@ -43,13 +43,18 @@ // disable unused features #define OPENTHREAD_CONFIG_COAP_API_ENABLE 0 #define OPENTHREAD_CONFIG_JOINER_ENABLE 0 -#define OPENTHREAD_CONFIG_COMMISSIONER_ENABLE 0 #define OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE 0 -#define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 0 #define OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE 0 #define OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE 0 #define OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE 0 +#ifndef OPENTHREAD_CONFIG_COMMISSIONER_ENABLE +#define OPENTHREAD_CONFIG_COMMISSIONER_ENABLE 0 +#endif +#ifndef OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE +#define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 0 +#endif + // Enable usage of external heap allocator for ot #define OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE 1 diff --git a/examples/platform/nxp/rt/rw61x/doc/images/mcuboot_demo.PNG b/examples/platform/nxp/rt/rw61x/doc/images/mcuboot_demo.PNG index c22b1c772b7c41ee59e6522fcac4c5e2d0e9288e..6584f2d53b5301162c1753fbaa8a90b4e087598a 100644 GIT binary patch literal 109544 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%0Am0nTfS@p3i+ZYQ$scL!^)wZE!W+HIuw zrMUKKkMmocVv=C*&dK15QpBlZdX`aHiT8U14ed4Y?R__!cUYekscJY~Fxc>&*Nf{ztd z_;t$lm;11M5h#YjABe^!Ux$QzKt$<8f7=YwsOJ~fLL}*ua}`xMPqV;-7sKY5c<8;o zWYRIPIL6fzV?A%US$)FOeSRCmaA%GhL^UbKT+r~R5qje_c_yO+;9&lI>+MU%SO3Fm zQaFF{p_%44&>f19&n|{EQB#$_K=0U!_n;vCsA9lIFwnUW=k=Sqn(L)#YRZIQVzhc| zmXii28SH*mvU}Pdz%@nPWwX2Lu`M+fntibk3Vwsunsx4{rd(=umNkcatu){ypZg6R zX_YfcKp@}Kf41<6x_z;*VNNHK(u8CM-GYzn)gvxdWYR4LMJ^QBAv-%hfO?|x)ROYv zI>j`V5EhVgFuDr+e@<$?kpBSr?#hs#bgL;8kX`eFSiX&|QxqQ8ahnBbBbpzh(d7b3 zZYk6O2rY(MieaSpCbMODFKm*5X?%*o^2l7%??8H6)O=<-o_n;|sIPxery!VMG-u#N z6^3I@nZ<$fD5GZis$Rn;?&E3Sr(7OqxnzkCp$`abD+;G$Y}Z9u^@kZqEL*$P$8m5n z3DN_7W#h@V)2ob+EKP=sA_ca9(2^7tg(c2Sq^L4)__DuG-*{(_(BIePGLNt)X?-R*Crq=GVLLH zXq3h9szeR6b00hvrZvvH=LGdc4pgC;vB{;)DiRwmQDu@IoJ&uerutlw)=Ym5 zLywJaBf~%G^9-aUqsz3zV58oNM;Pp28+^{~9}o$32;uNk_3y;N*4f=IO53;U0_;{77OsxxMV=7y4op^1sJ^66- zemDQcZFpy-h^&D6!giC0z-jwB`*DJ8>fX(XA9;Rz&pbl-6c@(LL{M-zdH;NAC>r8R z2eXaOex-zTx*tu^OjS8@)%MfT3U!2`|1fy=qv>X;otyoAR_4uK_m@$FNn8C*)|#N$ z;ld{~VQf;n$x>79St8B{>IKgh+Q>+_4E#X|x}x~Lsur_4D66qX0KYHy!1+MMSTLB^ zF7tjX5w32$7cc*mHS$7+#C`b5o(LYZLm&xSc!a@C+9AV153b2atS%+u6J^1WWi;fj zcSsaMFh62zpY|^1IPp7>tw>ej!*%P<)#L!;CRmGWZ-1}ZjDYbKDw!XH&gQQWLE%b! z+e5^8z1~VVtupN1FHpc+o@CK%(GgPt?fG*;$y1f9xUt;~&!D=vylN-jqZyC2!07@i z8s3BxLN?;^jY)lBSX_Rsji|6%Ba!6cW{<>HJrX=m^9O(^33z_iz5aln`Dt}iUjZSA;q7N&Odb_xlyt!)oh4BPdqus`;c~u{G zw{u$R(}^Zu-fO-WVydBPAQ%vaO6v4X+x*;~)vU}%)wxE9O=vQ;*-Xa0<7r(Zv8tOkeVNw?x^R6UV!`QG| zS4#$oDckl}9)Cd^G`TxXL^FvAd2F-S6a0L>n~mgRO^1TH2CTjAw$q->JXF`Gy9FQO ztJjRS{B)SW+bO?h$-Jr|f{H-n-C2B7B7QpW#mp7Av&XR>JX<9pOZ4}J0XmNzX!Qla zdWQE2oJn5;EGbIzurMt`TZ5I%8RSyBML95S4-~B2Jy24!R~#*0;bwxJuGSn=hGEE4M}z=rUa zrsw5)VBp=OHc@>;L&cY0JWTn9eY(3g$^+s(YpuUMo%NZ7A#t4@@eYa=kX4@BWbB1l zNXb_|AGLom>9WafOeUINzoB(!iR|hvCcD}hvhHaJ@8x~Dr%I+@t9r+9zE-oAL)_evBIy3e%0eZR(#QTl-M`=;D(L|oz0kGs{*E1 z(yf5@zkPl`e4wzRRWEl*lP2Yr9#B#;ka}rEOU^Ktt5ZCX8WwG`H{7LJw~wvti&QM# zey-O`Z2SNvJxKQ=tbE2nROmR*B%q%({PAs!fHu6_n{_klRev#3I)(?%Lg?bM8uWuS zL6PVwFxb6DieTf|(7K2c_BO;3pa4Ye-F~qO!TB|mL2>95wYUTMP@`;(3k4R02#|5F zwZL6PG+MmhiB8u)hn~Ot&rf%e|4Bx>9*pwe_y^{1W&Hao1ph&<&{!T|2uX~?sA4$` z--FNQi@57B4>PzQ_xpX|pf)G9LnOmS^7>mtWFUhE6T`XkFGeNwVJqLBW9qOg)k410 z$&bqY-HAC7I9CLsC9=`o zI0iF2c9=}^;_p-+@EqZk?*x(_R~L?m3bfWAjEBBV11%>i>USn`#-nKU%rJ{f9{q~{ zbRk6Jv4Qrqo6tFBKlR!^2O3(f}?Pv+rXH1v1qCoj`0NR+b@({Q&j=ET5IO{ zujq>GvWl@D%i+cHsu~=c!;N}`S4Z$;p$+Sp9m9FOEJW;gO(tF%?7S`(Dm050kK?Sk zsNbHYxOgQJ5%qYWu1cdqUe>TQ%>V$=E>0R<(1lz(SI~82qt}@M9U9a|XTM zeyy)j;>(6!{pcqaqw=Xz_;CBRZtOq8qFE@3C4vt$FtC|%Kk5Kv#z!Eu!SY)%el1ZC zW6!eta+u!3LcTY*`8{RNb5xJQR(hLJlbFj#R^XlWjm?uik|W9xzgLb4#aMv4wh*H) zoappoA>*^Kz@-|(H~_)}Df!)8^{FY`b?c!skaot2xF<(-B0TmshgPc5n_Z!T<^T*voeIWT{_S`TOLB+WmgA9FXh2PXmQPXuQy>@@dHKV) z(WCM@|B^R)6m*U8DI@atHmb2n5n4EdLC0eZcfe|DeC+Qba-u`1$mc%-Ja$^}4eQ_i z1S$RxB9aW0yXdLy)#Xx1oqaBzJMr#E>o6&n6t}CW`oGFb*0)1GK2UA5*lAU5 zAr~WA43sSlB;6oj)%EjlO^XKkKE}i-X7yh1=9`r59W((?T!Bb(_}05|?%fA?--z-OdS~X|ZG;$!y_4@eddh96v!lVw# z(LUf9c|{2lbGTe75IV+TCWrJe7(2TyuOubqG~eKg-u_%+1E+o>iCIen&;sKv0!?XE zds*Tmo-7_GHR+@W7Gr^2z&?IsVkTLBl~hxed9)i!PPzDk9#wqE4T z39a<`F$s4T9#Tq7>MD1dWDxo`5IuKTe>;gdKTLYP7?hGw#oLyFPqMTo{1JWUuBUnn z;o^tz?fQ`_umU#ZJR||=a)!?uavE+PYF{pi)q8!q14h0Vx>XFS>(q>&gecs1n7C}K zq{0&ftnK5u+mx)yGIi<&^%9;#qk&CPtHsV|6&$GY}>!0E5lB?TI0!3&jCo@6uH z0MIB~X^3wd(ju9-dOt+ZPeP7&P6y=;KE~Te?a1pwJd8gBv(y82&u-hem!0&E^n@4L zFqc1y07oG1!#GYtoZFaW(4T71B4jh4pK1;yk@>Z(ybt{_1do(IM?8S*hx8Bh7p7 zV6}$Tv_gIKDaiz{)^l0jmx>k0)8pBd>ShN_V0j!r2L^Re)A!=hpWzXTC=F|}f%x0WWzTg+vvP~5(uv6J z8S{L$iTq2V>e2NGI*R#LLSH1E@CG#h1%vsddd~CpXd?#1M^qmr7Bivpob0%f5`4bV z8g0HhY(J;3pw9cj=)z?=yl(S@mRDu(Ky6JsXjCfHpAl00?#V8a{vA=*Z-NKB)~~1K zu9#lMI5Iv66m6)`q?C@a-KQTBU;dE46G%ioP+cLOr_sw@DVF=^c9Cm?vq7c3)Dxeb z>hhiC{nI^T8IG^j?5pe|o2OswNvA9>Jry`c<-=xlg?(H}Lg&OKo{IgEmmfX@Z9Z1z z___Ntn-=VFg`$SH6r0*dgt(dUjxJ9Zu)};&&$uTa=|zYtJR0Esdmep1g1;*y4pjKF zc4*|rS&X~NugMLYB1i)?|ql{zY>_`vFLL^Qi zq8h(HbuJw!*)oeU3bspPn&Ncnp*D0%p9B`?_+UtLWnd8N>yx=>dV+sOgXGu($Sptu zpaUkl$^Eu=SZZnO!^DUKGlR;H5aBdj&-PH~pqn2qPGp*36hRP(m_W+cSdgw~E&RpV z7S3ZI=>0|w;Z-BvuV_Qd4hv(^e;Dab8YuU)6?4P5>@^Mi8yxgbg45?ea6Ypz zet>GXl18Dfp4`jawgsG1P}Uy)02%>k-N0*G_Tiqr1*evhzR@HnxVY}SS*=9HHn`3X zQt5gx1DNh*sj;-lg9P7-d7)99YEduk8sT8>$Bv_F>8K)|Ypp@SWSt;l6gLm3$*n?O z{m`p(<4rq|?97X`6U9BJg7XKIgdhZRG^>K)G_qEZJXj zfCg`HD4hNUlX{+)pGkjW>K^Sn&P@~?yTGn!d~T=jENJl_m4joRE7WZS1)g>7?nm6B zm;phcZ&5G=(9K(RgnY?#sX*#vfVtztqiCIv=Vvn%sMt>}?N|gqWg@W$jSCOS$WGe?g{NJ*B+dfah$%zj3iU-46$6D> zT+71qL8)M0ihfqBkzOZSkMsVWV#7_22w~oQ;jW;6ZfU!YSQ1;rvymL_H=}TBAi7ty zXRTJZBvVwJhjQjvR_g?Rxy@MnS`%cebXjEHpcH*dvRdOx!ZlYRrl^K3_zIK=fbZMU z@C~z^{6seXgdIrYaH}(dt~V3K9EbP~1*9{w%l({d`mXb#`uy8jj)-_SR?#!R5_39M z%*Nd-1+a^rNG<~{l~H?U%lisAJzT68I&6Q4E&O0c$tYoZZ0DzcC~tbZSVUND}1&oK>;HGk6RCqnG=phF!hs@ zsQOUDP6%DCSae}TM+8_Gg#KO%_fte;6L8VORk@&HkN9fO=NF6ngnI>NSi62+9LD8%d&V5Rp=!$xN0!FCintpS%RW27w9hkU zENC0h(8`k<0LjA%v>;2c7f8l^oFAo_CJZwCxW$@dS(?`6Q`5LTHgRcxuI3~-MG1Ej zrwC4tvX+=(RCQ5B?b+Nf`~OU+)@3k5ZvFEqV`ly|CW)Y&Hof2z`KF=EN2KoT`GrWZ zk4myE*g{vA6}%gMs7Jtv>;6Rjp84_BhE?zTlqkW=w~e};Z9`cUd&-;|<#+%ZlfEIc zF)kZ(8-uu+<+GZDGbn%&)JFH>tOA?f9WYgs zELzVCGgVL-E66wJ$Ug_ELhct)Dy`^4?tTxg$xBlCo zQDH3gSiz{2w+6htZvHf58m2`;zB4>iPmAxvy+UQ27p~?Pp&Bo^%jH=+U-n4n_K22q5jf~zh#fr4L;u5a4$o9E1I^!+A=(XB`yb+&mu+^p$AhlVE z0Sx-{0P+*0{~J&De-t(Fqr+Cc*%nuA))sYi@_}KH$6;6OFY`xK-<^|Rz@ipBY!B_~ z`Wh~_n$|uT#0g3dXrHd}kZ}2-=(~0~USy-*Ig@CBCo+Xa8U2M!+R2lFGPlf)ppxU5 zd?eSJ6sJ7-sCHdlwTM*%b#*QAK&JgThF!v7^6C5|=WkmAaQd&%zfRu5T?O#ktE_L@ ziEj$XnWnzXq3lRy80}tOc|;w>AylT0U7qoa);0`gKfdjE)}eert+$8E{RQFG3!b51-K*H-fv1Gv zRm`HmaVfj8ZwKD+C5h4ug7BWDOXE79n!pET@1HCSlIS_REJW!!irid%_5(#>L)v=KO>LWch7L&wJ@qLQhVG80AfN(iUj=8B&IvP$S3@lPH;_2TtbilPW5O1 zUu*-W$fwN2O_0pPhyzxE4AyHb)^BUF4=)vX6a6@@Y=g^n_r3d8 z4wTc4N~mME!`@IA0U^J(+4c)7vmluli)Ez*OqlkE!6eWFW}yMN<;E?`-}KYt!xYsA9Z~K zwv!zl!4RJg-%9E6Er3a*;NaQYKHXeSw8ebL@8Y+=xp7>x1M^QU*wKvvQE!7L6u51# z!pN@C{bxYE0o{7BT14iY+OBEk-yOG{DS`p-O-MjwE?=Kbae~WTTxv>C9v%c0RJ({I za$WDbG%36*oc9{1)6grNrTkwsd9wbl7j_K^`RnJXWS3FpGkDII&hvha7e@g=Bv}jJ zXvHH&M!9oi9!afcd`G8Zz#174;4v|dalA#?t52MEM1UHFVhdH+RP^1)Bn+Ft*#SE0 z>8nSWq8T>;X|mh7q1}69=Qfv<|DOq>|77Nhc!x-w&dIOM7K1-alZ*@loXwaTxL4u) z^rsFDQ1*jhMi(#pdP^kwvhh1RFORx9JtQy4b0@sauLKL zU8&>{AFbsF@SWurZ=j^`K@ArsZAD~xI{MLRo+?Qud+E;+T%AUBd3$hg3L%B%VyUN4&#ZQx+_M-+UnvB z&Ilj={B#+S%S*>Lw7p*`6s2YX28P^cf8;_a^uZmV$w4Bk8^|lwpq~;VOTMAsHpM~A z|B_kcQ_vKJR=VY$ic;y8!oyZr2%s3l1%&Xh87VP*>982{F6qavBq8CCV?rwcBk|YJ z(V8(I=2X=(CmujuwCCF#RD95s0H$H#^?ZIYI*9Tn9i8ksRA;auB7!pb?J#*mNp!b# zu@EjN8Pw?~-z36`-DzpnE1;B|gx$-XnY*nAbw>s14~6mg@APhq-Y0P0tQq;&*Cq$H z{=LvFmo<5Y`cZ4KQiD7Q{o{_7w2_Y6g82sz56J_u$U5Z1s;zz5Nh0tvu~w?2+pBdq za2aST-zLo`vbCuf3|SDI{Gz;u?U09`cs;)%`cTRjJD6;k9UU2y5N)QeZbu#nf5qZ- zYucfMLA`>gJN21?T}-xR&qv+rf6n&cV^V`;6u>Y57PKheH_x*kYvf$Ln}P(N-Z=(^ zy;=TX!OPM1{wd98^S>fh91TGERHR??3bZZrK0S!O%jNeO5)w&m#%9<-M16i_el=i_ z3&Ll}sPPA4-wVrki5?8+TG!swKbl`-xk1UFOD5_DN`L6fH{csIz85jDAiA5L#kUt& zjH6`w)3=|(a@(Giz|n#5OfJUlmA%Te+w2;L-+XJjVMYT;9y1XOzFg{5$i~`QL*}em*!VAS<%7UVH?tENMpV?8xn90v=2E#s<0BGZW%f;AZ*Df$ zs;cvyvy?%-8}&KY1ai;e-i#}ZIrMwI{M_Z8KUx-0Zx8}Jk*%y#CNgYcKxd@uL)yp( zfEUYd#TxZvlBIX6)8xC&P|91J^|F)WkW@MHrZBzt>08IUP3lyCA`JaUUEft41rNFh zcV~4TbI2O>8&m8b6a26 zbXW)!4FB^uKoU%fA@~c^0^Z?oZr?wGsfnB|g7{!Z^1odE7kME1xpOAX{9o3--T)Xm z`HDO1``WN6WTA`*vUo_eg0Lgzqw)STP(+SBf?!I6L*?tavse5@zZ&=a#Vi=6n)TFg zE&B0)M@oT_S|{_w+(%8bHjZg6yUz|DD%jTzXgyiyCc_@Iz8K)Evio$dxf0K6V)M1% zxh}oqM1G9D;@L{$%)mmj4G8C6NhTa_m@?M6pMWdzHTsJ7WCOt;mQFFUkFYhyN;==)9wKb+(3n{(!5yF`~xYDKTo87k(v86Wq8T9T)zthiz zB+7c-tU~!-a!Dkb6tQF{nnpav8IpQTYeD;NBUBGtY`@v}Q)otzW4+N&?1YtI;__0vW;D^vmbpM6Y}y zD_WWj=op^ut+p6#!Wom57-kcS1iKs_E3&blOK3topJ%?llD=?TDre3ey+kB>w5Wrn zPPlyO>6~qy%{TT|NSOZ>m&j&*0>g}hRJ0s#dY897*DsLIqkZOD^lCZdV~0(Rn8yw? zbCoOxP9c7@Ha$L_R??)?(+XMsFLSf^J)$ovW$Th`+G<|%uWOqgf@WMe9HX_n<@U~v zl=1cpyJCVgF1V`a8D!UN3)9_Vau#svzLt!_u9sE?v9vqA&zy5TJ`Pi^Hf%3Fk=T7k z(q>`r*kjSmgOfZtSj1K|9Wxylslnz~ID_C(_jjug z)0PS8WQpc{e1203j1sk*VN8*zMv}K5ZVh6C3!2?~c{GtOL3f~Q-XtWKsqdE3sCksx z!kcy#zp-nVf~EM?3Z_gHnziR$QcQL{ggn@k9q6%IA(4&Q8A0Z19)@a0vj*i%c%vt< z7Q=)Qe)P+?RYQo&tY)0=^b_;-ekI(9Kx(X+Xtl*QJ!4Xdnz6SXpi3bo{cKhAT+pd< z@Oeeuv0!BI6RQZibMx5w=eQ&xa=fW)HjS5LRaRkbKlt?r(ZXk-BQ1&deO&Vo3m7U? zrgFq-1~_nR6Ca2?)Ul7PlnLY7iq>+)$#i+dKjHVWcW3BFmEq1<)xGW92VD@v8 zgw@(P7#Ko3q>5*C>+xSF(my`w({YgOvkaeH;p^A+<_uO=+-N#mCGs5M(9(%phd$|n zS|sV5=^r6Fdn#Ms!=e_2I`F;XQYXy~XDCJ;j9m+PV02=SF9pfbuEv*_(i z+FIZ6f`yTdGlf}cGKb}BGKDc~GDP%!Vhjt3V+zBGV~8k+Foa5VrF9xgMn@lcI>kko zXtFx4N7Jp6+cg#?6shs&ZjQUkLu9t09F`rj7e=OAm)@sIx2?CA(5z0P(l+2fR$LE| z85(<)XA>=dxh2s!?+FlcdWwD3|3Of#{yD^cKFL~rRXeuSvOG||NQlh1uNHYg*Wl$L zgpqI3qJ5mEOXXX~>Qnk5Z?XZ870=!iA9Qcu{?u;wBE2q;i%_&#ugRq*IlX}o@*cFz z@q#?);;w^b7u?J5icG7YJKi+P*^4hQkCs-{Q&O%J%t2MMLo?^$Rat)h>pjVU`zhze zuvKv-(Az;nod`N6Yjw^vv$JIxrk8FJN|NT)h?3@&ijsas&x=Ae%Z)<)vYN9v zot<;=;_LSoq=1UK28-oH?^AMAAzPLo`nw;sPDcd~CszNd*g95(TZ+m9Gzdelw^91y4h%?-(XCmbXPBNrtiWID)*LDV(f6S zmkZ?h(FJlyZ?Mpu-%RV{dUfy zA@EjvBu8JQa!C&qD#@IqA7V7)+z8jqe%cxkGHx|=F!a-T!mv4+#AS%M02W5G%8L@Q znw=m-vLu63G^qHRBjR1-aM=Tx8({qE4ZL_dv=@V7+igt0*dC&~%Bh!UierjxFM3wr z4li=9<4m1=`9lKL0&k!Ib}#i|8RLGLV7!YE(C-gb`{iHX(ruZGm~{1gagY)A(Vs#$5VL219P3prg z74@y&i^!`8Rw;87O~gKFUhFNzC2MQY6ed6=3h5mXMc($PV!tQ|f`U%cg&ke}78*v!}lJLmYw4o;?xxscdeu*cY4+O}-Ed znnx=wm1jbsiZ{;NFb+UdqvBYiCDd``#7nD@9tgk_gXsvBVv^bU*YzWN)2k9O-=z&3m+w4lR0v4Kc7 zrheEJ*vcp!cs+?XmOEZbjJ?`VDvp|Rr(q(GYqeA=6nh+&?af*YoSc#m(}$>i&qu`& zwpNP_(4P&X){a3`6{V(OlbUSmWt+YHF3khR!<-eF-5LjP3$!;CXK`uTiKQw*|Ai8RUR(7GuuO1oy{h$F0gGyF88? zuW$m9W_M5^FFrIBZa?##*Av|R!!gyDRp2AqR<z9#$DhbZT!zlZal_sh5U-nQhNqXa8*xE&{h$#_TN7yRyeCX>IsP zZ}J>;J>*r1(pK9qoOifF;>aG~MAKXi4Iq-QO)zRUa1hq{pWBh}Bdj#OA2oZvQoxx1 zULeIunlUV~){DD>li`5@?Fv*ERS?4c8c#0I4L5@hz!CE>X}l#Tx73((YMy*W&~+yYZd3AGv!5N7jd%>~_9n;F~BV zIAKa3pnNY`5IdJzTy|^=;~2CXksQMExhr_DaA0`Lw@?3{b$lbua-$@ zJpPKDcA-*VlBhtVPvJ>}+uH@yoL}=6(LKJO8`A@9Z^SHH^v%-PVDUUh*HE z6geUPo+qinkKrPXDA&=8ryg7!)Y{rR$OTMzqJ2r0t?al%`fA_z5{7$A6bSr~hId}L z`<$X)Vu@MwUs`4N3QWrDS!mu7s{Lt&kYSC=Qtw3ScLk!$ub=M60j&&39kjVninKKd;A4$r~%cjIA) z8ozP2AjKjw<;fSs%?DC@Q&)`P<#7z*ToC5)5eQSbDuf|i@gTZG>L9AqwlsPvkcn=q zP81!JgH2_5WMjl*_vM9&X!+-jbA1_10w+)jPzVR_9i;57FXpEzE#^ZbeA^ZwGSbkq zY+Ew{%K*DiMJkFz-uMM3bEBG$B%vn~RR<6ezFfVUkm33AU#NzdL-;*^CEcsGyQy6pbISY>Pm*{c|ow#A;7Q8C?{V820@+ zioPqf+9@+IXw>S!uzgp2x5eVTT=48M)934_lv^A_H0ZD0Mf^_uMPg1y0;p)_sYh1! z5zEDrr+$$^vZQ`~Wp7!+vx}?e6YLfYD;eV%>H1~jA%qju76ELg!!ZAH##FFxnRB2f zTwt%0@w-XIA14e@7=}<4?{i=I`kT1b!G`q9e`$fmZ447PR=PkZpl!h&q*AkvK zpj+Lp+p48=Wl@dCz$4A$bt`5kicA}HP4@VRZ8g|jxZ`lw3agOmPNSZoN3}^4ay)-; z`0A(%iqWbsvs)CN?bNK*Ao_ijCVxT8It84*)+W+mv!mN|(v{GNpAC1H<+&QUf6A;d z^K-2@|3dTbW2h7yTyrFO%b>aBn0>5cFFOK*r)O8MVNT>`G>n#K%g3C+QGUWXl#<+7+=B)enC-UT0V>@m*VT8z!c_1HFlaq8j+*IZ06E?TO<+ru6kk#G+JR zYMX8ra=^@o?J)&}0xIz_f|4mM2N`~E&T#E%wSMm>h5msT6>~&-L|D54Wyq}$QTlYG!}?2)mcECrHM;?8%8hsVE$WI=hGSIFxBh zT|}Ac*4>}1pXdZeW7Nj{j9Xej%S}U0oJ`GBK-#e6F637+mF9* z`1&&C3gCDLA2cPg2O;x z=XEhsc}qY2qcbg3$5={!AM54YI+8_;SRWX33PxHgr#{;CB)$2^hQ>Qzx;T6eb@*ag zFesF(EB}8Od+V?$zqM@`gKm+K6c9l|2BlM^k%keJ7Lo355TsK=ln#lZ1d;9*rKN`) zxx&zvF$6_aFbknS-0P?zOJ#yv~pmTPW34i^;TgtQK-FpD)EGrSEnQ z39zQ4C^UFjP{lIrsRX)Ua{>FGI>T+Z>GX(~?x2jn98{6XV_)(U@|4m29U6YauGoH#Ve_!DnEaoWuVR0bh zUtzpW>~9jgvN%WHvNh*J16_-XBvE>N=4BL50+Y;Vfr~|&4@10^)6Cn_7m_o~#t)*A zPbJT*A~bz;OJ4HTUR|Q1I0RzEj+3jq4R6#p&p22S!&%0xjf@EMQ8id7lZm6-OYXaVK*@X14L#)YQU)h)S?C{xvczo z=sBNxHw>$<>4@NU5c~#U1rxaBKDGIGQ_md2HnbLBq+kNsa2Os|C zH6Pl+PPkfzsY{|Cr%<+YR(pTx0+@gd5P7v?Qmz%!&-Obcdv|&n2GtrEw86W3Mp!=R zF%gTO7`amE5hE{d-B(K9q*$+$`(c%9wc=K6zD&MNA0}_!sJf-mY>O}!^4h>y%T(aQX zc3@NA4$6AHc5?d2{-^i4h-K4nYH`&%qBK@I?16aK8!L(<22M%^Mn;g6={e~)eU9*v zyf9kdnw}IpVOxK7Cz!!N``QrG<~U8rb%#+q{b0h!^y)3;q)ke9;sUW{hKHraAR@s; zRGHw;?ABpruFsyDp(5i-HJ8vIV2G}Ge@XRnD|{a zBE;>VEWM+om8iG1O|DSLJ!c}_rj1KSO->FFGXF~A>XO>u@6KlTSk(*Q3IyB_`20sR z#}6)|9s^}2AquSN=cG^ZC>jXc1LuCPk3^{wt^G2Yi;=i2*g*Q61)!32ZPlAEQW+$K zZ>_0WJ6xPdJeTkl&87b$;mZ(vJg41Yb`WTg700YvtWN9Ynwi^dv(w(!M66hcxJSY*R1ES z%_w7!W*R3~b<(4}Ml!P^4b%v!&+YX{@~m$?XqJ~2k+Tb#9-!Oy{bIToQl#!7%g4S` z5zVq2it@Wc`c0-$M&5mLw$tyxc^ZDygyY0@j^o5IEm|uQi#EDEcADcJYLl+F9V+!o zZotxiyj$Lu@IfqJ9vR9YvtSVFYAa8Js&`(`p1I4%2YoY;Zll*~YBORB%tiMdBC5@RTgBM5;{PrP=1n(L>stb6~pSN3~vzo=xAp?IhocdB@p^tlKA`N1Qv zupHxf?VfoBj@+vg^82zxFJ7paebeSf7^^jVY}-ve1#XA)>x51#p}z_dR3b1f2jMzK z15a};eU|DuK>sc7bLk-MGN$5b`%c{SKJu7+I!z=cO&so|Dk$~++cSivYfUDuM|CZZA|~yF*;QSDjWbp!J!Q_qKPm<={pE79rh7c9>*)pi^@7Ny~1--q$6$Rh+~NF_(BuzWv# zlQOTNZAl;$g7hm!UV9=&5I71*g&*XoPTxB1fvgT(pauzk3rWZ|vyG3s6Fr=uAz(8) z725>W;I~bD{rTSKqe2gwvCfDzm3)|{BTru>!HaLb4$a4({LFrDmZh!J1A>NkQLp;NYBho4XT|Wi{Ra{B>Mf`b9^C zK0r+4%}+7GpK0{*Tg%sxQVXsFsp}owx?`u0qR9=gGD}pIUD{3)n>WhVYL10C#~N10 z%iHtFtS*7!y(+I1E`9pUJlEYeM3!G3Sl0UDvwcJa^>g=>uztK*_Oo&wXisQJ$ZLV) zs6}>7#^yYB!sh;#aj=qVz75wp_w1dGY&8g}^Kjp_?lwS>w8ZPS!9`eo=^V2psO3q* z;2ghIi@`c@{#&u_>rNafjb<+Bf$f;ROevl06!~j`$V=iRv6-)Bvj6o&1NX~H`EIF? zHNbkw%XL*S&ReFQyUhS!;?MccQP%KEPbk%)>y9uH4%z)-7ksCg_9ecxPqi`RRSj)$ zW`pqFMv@gcI-~;_k_L)`;0aGyj|`>$J(KPgZ}rCo#c}+3m+oQ6GqC>V;HBNVVA%A* zFk%DJiz^$w+E%{Yte_73zsu=N50Mf6ox-$zrWE(QF;D(*$4@c7)K4)`#ZPgD$WQU8 zitMxFW|&6R`b{!sY$Yr8W@WLrZBekY%H}&5XvIczas&x*cKH$tj>U=P>oxQ3P2=THWte^n z!OU(R#TwD#ReOy;Wc3ZiiWb3XKB&%dv(_cGnmyc{Fdhlx^9zc7%lav+WBS@xJGK4T zx@Q+BF(Kgp;vhUiJD5kA%d$*3oWz$u}jqWi|5mg5noP8)9nwgOyk_?!n54X2Cr(bOd5$X2w2QA ze5xFlvBqPAZ{6Ys_#@t8kwBP6S^5a0jBeGdck)LY(e)OZWKZ^|?fQjj0Vy<1CsL{X z6BACz!0=18-Im?L{7AXm^j@5HIt>MTm3=@f6kl4>94k=6Lw!f~@*Td6@0FNY>o~tn z>cx*FAzKk9%!>I5dqcN0CxZ>&c%Wx!yla_U5mtSk4KCB69V0G^i1C?<&tzAPNa>Y6 zr`~LnZg|G+V-7!2{JZH~o9+kb^D|yW6n%bj9%qpe)>3-Q?mrlaVMrrEN?ATTLWH2i zju097zUM!F#Wd%f?W2@2?KM@=Z(IMJS=r~>vJ76r4%!hHU(DIIpOXwyp2m4~&!i#-dT@Y>QC1 zXXJepY%|>PQ5XSo)mROu^*{La-qs0AGbyUl0|Kpk%au0N?(cpehx0&iuuxe0A{n^ITX{RS=n5bMIa_F+1kLS$L$;ObaB&|4ci|c4P?I}` zCk*e7-31A@MfS)7CUFD5{!deE9sGIz(act2SuE#hMR2I5jNd))FAB39Y#=^%_gWJh ze*mlK)>WmYNgH9+GR;`99E9AP@x8c)`jVXMRU;l9#VtR-@IClYSC>U;vP0K*DAYMx z=iV>$?=D@>&^C)+?{VUVFTpn{CV+Qgbt^#0iG9F>xo_TTCZ~^ry~daE;aK54rE_=?Q>W)S4uLC6HBv~ zeK|e4bzgOV78_zY1KOyrL>j$z;P&2G;|SaZUbY1>cG7_MT$43Aj_~^ms+7lHa0IIN zw{>6awA%>y05JxA{5>MDCnA4GJ+c|r`6z%;{V3w&h?b1Lww^NActAbBsYgYAC--<; zx#gC;`a>E6?WghzH!@N>AXj%6iU?T9laz;UMCf`pH!ZR#QGss{1I>O!iEedI9bb{@ zQMf6Z7NhxVqDnc}ZqA77#jEQ{Pr@B1_v&svRivKQ>KxaG+DpFFc*`M<{vstXSwHD2c%15UAAEju^Mk&^>%%v4 zGKs~UaX*z1YSwVJ_n$0jWeA!Z7)05C;<5)`+wYsYdv^4Ol`*%c;svyUhNYJs-`&E6o`->MDDTLq1{v zT%{9OS>{RxVoh3tk{krWJp06N^XUfzJd=G+mk7GN+0F|sRDSq|aOy9tfzk?etu2!D z(&57!C;tO$?Av=%(PUc_b-Li!y}@*e3HmHJi;uZK{ZQp-J5|W(_1=6dfB~|kLVAl| ze0}=u9U%>WBsCTj3pQAYe*|iGceZ4#Ojp{zJu+#1`BB)eMCoJw^ZNrYILORO689gE z_fFigdj0!1-y`o{OEPBHbU%Y70G8w=?=40!fwG1cQ%N#mPGmP!^fcFj&2{JYWO+LP zZ=@Wy8$B4rrsmiHhi~$3&~5fT*CSu3x{kik*j)cr{PC;Z6mR#u{Z1`Ye#XOtHhgr`+5|C5cvA9`snIhG z#wOh_B$ul~XEv8D1Ekq2k?bdzUflzIi=9lWVl0ls3jO=Wf?t?pBv4AZ24X-i{5rHp z3`rGo{q*w0B(79n=3$-uQss!W@7eb)XVU)w3FB%&+9CfAugf@DKozJSrO^1&u^+3x?!p@DpL?jb=7PmT2 zlRwqWI&CA}P903WVBTUddxn_pY6>P9c!dd*6oK-Y?>q`+-CZLv1X*svC1uT*FG0vm z8KM?9zRZ0$0y6&NSu%>s1E!u6{BRYYyFtw`9(c-*ZMlH*{HIHV^Gwb%V0!(zE5co4 zq_e-w;T!i8rkwy^aQzQQ5Ast1uHjQ1Nh_qW8=*BOU;n1>({PQ0i4aJ4DW+5hh^Y+SQ&&X6)u1-3)|F&t5$DX z4B5Qp-QktoxZ*LetfJvg)Q$RZ(AWJPd8Dk$d!0^1m`?N8j{-NSSmq4G0HdK3$iCu$ zCCp^y_c#~>A1v*?<*0O56xl~Xp8WU-K9)BB2b4H|N25$WXM$aXHO~@noi<;Qt^ZO0 z((&%y88_v7GILGQb46w0z+dB$d2CO9MtJedWuP-vvdgbCea0FsGn(W0lP5avriJt7 z%2xBC3fU9V66+^S%amc}s|D$Nj&~Hbbnm*&n9|~n25+7~i^7#Mc9vEduAa1;UT3pD zEpnBA|2~yZQi~>4e{6q=ZsbjDp3RTghj2B=3WUI`AcU;Wd_QWeBzj-zTgqg2kniQ> zJ}5kWy9#Bm5&J#{5?*nkc}5U{5;H1wJY;MR0zimRrA6=MSa145p_u&oP$L{9F*IPI zRpb^O^j)O;nt3L30Jd%oDTB3U;?y&khL;}HGDWNN`a%FhCIYI=XWoO79?fh72u?@m zOB~}CEsUUU-Bxk1cN}i)Xuu><5~G;)1Q|bdXC6;I?q0M7QNx~|ww4ldD zN~SUEm<;aJl}Vr%0GO;EPZty>OeClbp41+NOb`>Km?>scAG9H z58eu(HmV;SZPE~fOuOjj(erpfkXaGnM(0}fb>Dy@pC;s*DM1be+8`E` z!q6DnJO=;9OwE|;_qKxpPu;&5LX9SNwhLSKp5Xh57N8u|pgj|YbDE5Zk{V^2h7xu@ z=ZZG}g@LsRbkpApntLB>|2W>Ufpn|SIEiAi(f@+%7D`GnySr$-1o*e<0kosZkE!m9 zmQahpZQUkN*G%mj^CjD-SHTIaLcQoAm)Za#m?qN@ItS#NKBUJlgF3w!m{r{oJD3&- z-)sM>KkFH0(I_3Vye&yP*{kE3dM#yknl7+7o=pc<)UGH}aFEc*PC$ZceT{oLlziJ4 z-Oy`=VU>yZ9>_%hHzt>RRWLd1)?vNQ=UwGYBdjY-oX$dr-T&V8;*G*))7M;?-H*eS zt4f7)romxx?eAGs^4+UeH`_<@gf3lwUl(p15Z|<}2ksOf=>stZK;Y?84I=dl?WrRT z+Wmu-6rg(7BC+$a7TKk;fmkJd6*G&TT)$!P$XzycBkqBL6t1k5duwe1H$$oQEw1Lf zU`;(`)rfJsvh3^Q7&^L-TKqVaOeCi>eRZi50xUP3vNo*fC`<`o^@*5|_TC}AY2R?X zEi?7aiDD45Mh+OyR0SaQKJf#nrIn2gjK>~OOFvF%ST8&QhTcW(c2M#GYiNKmN)Wu~ z&7*T#n86{sqO>RF($F59kP}Eq$?wA9=7dH|I{}dF*_? z{R>Cb&m)7pJeZ=*NHM5>)^mw8?t>wJG+RL(drW!uOFlC(JOLfUo83<@-j$Ub(|8bE zHw@aRR)i?FTCvsc%OdOpT+0+dpZece4vnTBiQl4hH{H$Nmio(#GzydHaGgUUNZU{n z{v~2FAaMPC7XQ8o1(1;wQ~#&K+Vs2;d#5mVJzEtj&lCr_NQG@+eWPfOl67!SBal zl84law!bX~2ji?jr2v4$zkrkMhJ8pQgBsQ>rp?SBh^N@X-@U5+jpU1|e^7KUu7;q- zm^fBv`9>oy+*Ia2fLw&@O#8}z19CrV`;vL2{Gga!H;YMM;j&vbd$T%TyedHi0tnzQ zYz|r|JUmcMV;Zw5s7x0pCe*zc<0zS)H@6ATDLjl{8t_nfWUTT&ERIGgz9n>~GlC9z ze%f#Dznb9&$I!%T?26dl&?uPu1}lyVxt_y6zq;*>owWfIH8y73cMUDc>KOAH5L=Cm zrV{9PxDna^qsP9=?_7N;K{M4RbLtMXb%ihCuCfT-P4AN|gxRb zm~?gV^8Mu5Q7vWd5r^OC^CfnG@eu8KyKEHJMyZ1^F+|9fZG%UCR#0rVGOv`vpcKrf zrc=$PW+vm6UKu-NH#^T@Z;Vt^ZI6QLC%|@AE)hTQDSieZU6rbeun4=I+%~peUIS;_ zf}6qGO^-Q>X_Z*9@l`F;7=gm%eA`z0Ljujos{(3X&e!IW-MDmEz#h}$xdWd$0}PAM z*OjpyVoGLrD#JV_1XeE)f|N=|u_ra;NUiXTo4$Fo;X+`&O4X49U~idZs|A)VAM4!@ z;p80D48_XFrN>{9&ypcSrMkJB^!#O#|GE-a==XE}T#1Ap`zkI0q&L=IzU`gj-j*&i z1igQ}A$K|aDz2A3R6h8Yo)opBe8ZsHD=I!gWQ;h)+eKFfQDtG`S7X9s8oknvn19*v>cWWPE=pOHpO-ol5WD*IeF7dVTA6A7a}t+~ z2OVTgI;%aaEJ55l9iueI5!|W2;m$i-C#I%CJAv3~E^5}knNIzrFRJ?|n@vJ`!2ETq zJ%RXF*hJO*#K|zx*ua;CXbJiv)f9~If9>ump;?EJo_CbO(=D5_-c*r$M^rQn1EV`- z11E_RdGyT1XzY&2_hJ z<2k2g(sv_h;1N0T%5wV?`yg1$JaH>zud@F7p>Y{3mb9xYU{5VlXg?_trV+T%y3;*Q z`^0NBGYfG;f|Kba$a3(Dea4)wi^O^VCjMw9qnJP%G!RPyS^Mg;p^rEyYh@GEN!7VX zaLMk}OY)h!_jFf*OI4dUU7hd~VaURY4#v=}e+FUQB3jJ;+(kMKJO69|Lj6fd1wE-4(}pjxQm_5>*#y8Uy%I6 zA0|euyCf?Lr2au3`T@`f*rN-AdVQ?)C1ZRSLHti;n$cQoe4tNGbt%$L)(Xe+Iu;yJ zfBQPWIzjq)`;eQgs7;SwMrb#Pc%AXa>+hB&CB_0jTkz3)*g4@(-?Q)5k-0*>l(2H=fMdAEdXDN-TMObW{A-^ zo%OrAa9mE*rlt&g2U&g=ejk*%4RA2@>T)snWA!_-Lub;Vxg;P_AcSJpFZXU1AgVa3 z*#V+TSch_g$nI@GcL2B!Q2zDcbE9z`+8$VG9&1@4j-yBD2y$K^vI`0=iNct!wi`GGJwg2b#b)U0rOAbS+tdF6V-RK3 zJPB6Oxf|Hc6?@HuQn7gmKNnemIc#!reO=r-fOnHeirOuO(M4EUfalsf18HqaK<9zBoSETaVOVnf+7{o)-oVlUAiV$pO^`fkBXS zG3h#2@)u3;$!e#3`KwE&G-08IhOJ5FM~=BY41R*$8G=LmH%3MaAHG<$*45BR-Ah6{ zv>^54jy7e0ndO0mTj}IFlg6YD8hBPpo?j}G!j6*+lm;N22mxCTqg3jnmct(#qi;g4 zEe9V+-zgF{_#{C2qCw~NFE+B4&MF%fk6lMX_i)w=AJor&oFHYlPQ2AArqnqxk_Tzh zy=f%xsmQdJ--YxFiA%sKq06X#k=?bBO@OGu=G-YiO87NR0w4PC=2Uuj`=Vh_w=&r~ zBwHGX4`2>b?)be({n45d?I>M=(Hh1pCm-tSfAEX#m)ViQ1-*2p{{5$xs>}h7 zQXbinLje}0)ES*H2b$%P8XL_hnqIFDMGg_*?{Fa4KB_xqp4aTsqcH796kE8 zwbA>RM2DO3w>wAjfERE)>ldacK~mE2r1)rSYx1j$K5>qJiJ5)#H}j#VC<)*yH#P7q zm;XA*E2KOC{G0F7cFiT^!H^^7Xv%Fjtn3-Tkj0m=@BcMZ=G05)Jp6F~M=HzrM^@_Z zl?;>bX|Aw-Ujp@z>xw5Cdoq|?^-CCnyjAI)Ms47CCjGFkXZ`%T3m00n4aELhpO?)i zzNGp9KU<*P2lN;=KqFy_5lgE7N*4z7{z6Autl|dmjkH$FyM?jHew9#(MMC^gDI<5P ziuE2GWL$-U_;vue33fkssVshh*pk>4jng)8sN#ZH*GIKN5(>_qbys(vqUK~aMjv0h zJ=BYZo)EfU`Izx`TaG5nBjmde=7gXickMgav$2d5)T=}un1M2lpo-yPr^cBmYUKHbW>uJT* zPDrs`?2V|${en{YVROX8a=tTkqhH2((&vnQ({yva#57(^S2iLI!5~)2qf>Lhz`hkz zIGO>`9W&l6M&$)2b<9<^Gce?WaEnpUHY7)}?N^N8m3i}52+~l}5MQGLP#4umg5Uov z-hN;1QJ*XIahiq8q&}D3W};I6J-_YathegTHnms2X42Qxj^g=+Yx1*Ub<$|gqMkL} zoL^7RJxf0Reitbw702a1ah%0SWXRLeJ1`9=*;_h;VG{e&2(^N$XEcf$`V2Ro8f-a* z!A3yBa;goG$Y;}cMM(viAOy+oQxVJ5yGCcgQuvH}@5deBGjVabiF1DZ_(lp~=S+zl z!0mSUUL12Y3BcPO5VQ{cvqO9WbE)I8H<{G~vAVM4?_J0TDImg~**=;f&n9-a@2tb4 zQpecai&Dw)O2ZUoYu?aQu>NfUf2b9u8y_1*)4$IEb7)VI$EOc|Z-DM&E+xV?d*i}^SK7KLv`x9rM*ZG61O<@IUm-Kt+dGFLV>$BQClg~AlJLl2IY`Cm@{IVrRF)_9J7>Ah7*Zriw5LWj4C8Mq7IEB#iZ3JZE zJoD0Dl3^Or^$~EReB2DA6uP59OmvL6CGA7B+4jb0d*UC86fo7zVTr{WvpEz`F;T=j z#>5?Q#^~QK%ulruxPdLaP;89L{bauC-X~v@@?6yLv;Hbf(@01Sb{inn)RH|t2r&ot z5IGB7O&*oRVLK|5zEH-+ZJJ{f#vhQba2J@Ptb27ihEW0UIS(z3Q6F|g$x+UGOZXl? z$wF2;6hSe@Ht!bw4T^9L&v*EG+UkL@;j7|#EfQJ}MXYS&7)?}HGy7M!8eA4qS8u=^ z0HSuP?&~XEpOoC??<3i!h2ihy-J8GPdK)+BYw)#(nULxC=4X=9_1dxNbGL6sK55|t zFq-tEgrHi#CzSw3(*!WuEQ26xgMwS2AP(6tMm^xSF)Lx(dmDl9j}F)_=yIepxu9Da zuYFfo{kE6d0(-`9U#}V9rK(~gAZCqj*2ekZI)YAC$vy0sY|EV3+ndX{&E`QR49pk( zZE(M&w{;Y`OOjGyS$7Fo!ZLl!HbNdeG6~*?D1BNwF}Wb}pKF|(vS&?OicC!w<};J- z1fFr*OITSO9MCwbkZS`sk~I)-*3?)fbgRRS*hQ**HpZ-XYNR@>yt{<&8AyfA9n$qH zjQV8t-XA)Mz#=$>76~((#~Q2;M>kuhRCL)J$L(<#;s+1;nNbTU z)t_{Z)-21Nff+o9{6HDPs|}ZR&FXqvP}plWT&Js`?^c6+E({!77XeP5eIvc<|M#oKHP2;061+>lC47hne`zut~CEoZH$XKxbp}1~$zGY?Fy+mEr_j8|=d3hY2&em1T4(27hOE$!yVWFu`(FiQNrgbGVhxqgz=jSt4 z)u3;;dX~U$Dw+-SwW69&U{I&A6Z^-n(OTPu5&jJT8o7Qr` zbjw};$sn{=nh~i;{48k_brZ3I$FXDlt@9zVkS{AUtW$%q}0VJ&9EOR(?_M z9pXZ4Wl#@zir&pYz*6V55^n}u)hlR6(G0tCH-vEW8}(uQG$Rh26M7D6$}u7Pz|M2{ zYE*T6GSXH~8Y8t?U;g%KX99+RU=nyyd1p2sOk^!r9a8>bj>reqCYcrW(n)QXPh`;` zv)}tJTa}MQeo=#5dgT+{`_z>31sUFWU+2m2t8uOJ@q0?i09hefzr;n8Yc>G62Gw3h z+t}4nUGfVhq?wB z%_7CE4nKLimre<A{cG!utbatd8x3(GEFpl+Dz&elZ(oq}X%XO=S2w4%?fE9J*!=O@5^_ z!Oep-9VPVuAAidhrzX$0bwsOsr>p%q)7|D`UI^hk#an z*^e7-F2B(yVn<4;e$d$Le{S<&Vua+dh&SDQNd|IZNW5SQQ=puUp28dRMDbb^y?;pm zfY!T{bQlSbVMBEN)8E(Z&b}C61e+i@Ac7BPulGAb`%hrk#-6n@1slQ?+g6Z5&gH&TF?e(>L~wPg;D4UoxRhnK=9RkaPlQ|zF?2b)y0W^S z-L3iHTc&2jfxkUnBiK(LAV@r6Gaag1nlyXIblvTqH+QUV-H$}MU2E7eN2;RP&YFPj z3}^6NUb`qk1*}U^5e9kPJU!niD*L_oX$K`byGga;mdvn9v0^|BfLk=B=kMOSPDO4+ z(t4W)?pELgvBbuD;P$b0-->zfGtCJmS8AkBk2Z<%MmOYdJ!pWh0n({DAfEDu03k5B zzS#ewLFEmX9_t8YaeU$7SnraMmFeQvw@1EXFYgHTy;v8eHkYE|zYRh`ZwABB*4&it zIfhwpAAgq7PqF^eBy)Puf16ZkJ%#r>E(qSl*d~k+9<6`Ee=t(~v1hiiODZmI!5edB z+>l30HP;IXXIA=hUo(+iWp+wNw;8zqRECE)G~MJ+JmT3mh#-QN-xZ`$Ig53@tfWDWPf*vEy?yJ zu9G23U$fanre5N_cL=3Rd|fjv){k>5OtofF=R~PA#l6&IV$L{wmEza{iKcvU?Hfpt z6gB|GI?k_WI^!UVz2dB{_2jn2?+`q^ZqBvsItsb%m*F- zIZhTa3Fk~E6ZPZ8>G&w|7}@spkJc;KKG1@8D9`2{AY3RAT=vTw2Z0l6XU6Mwf24$| zdB(%xe5cKSU_J4uXmv};aENE=gr?q);PF%v4fw|*c42~6B8}Xvg-*JpD2Z`Br>IM7 zyL)I1@*b9-SuO;CLm@YK{_Ql**a?U||G1EmJpI2o`u`W6_aAKmT_~{WlpjcE3d9QYMy5?F;5_(MsgYG4XeS^>W|I0zf9H2 zRn=D#PL<15v3LF`Yo=(9UTXvqvfEOU4xG(Wovaz54d)dOY!uuSXt+S}gDZhPK4mzs zHJjs%%Y6)hKYq2D=0B=G_o}))D)R%B{6hwzg&;D=(?BnPOc^EgBFT>0{U$Hkx$pWy z6A#@wiyetHn@(lGx3~S&1Z<&dxMPF^`On>ZYoF+0bFWDNy)UVqNaSZtI5y@!>a@r= z9(W`t>Do0yqa`lgg%FXCm(tv3<3L=&SAi=2Hb+F1KrgVDH_;0TZax!dWdS2pf}D`w ze7faix2s)Z_J^~l-A@hwrCdY(Z{=DuKRuTOA1#;RIKAnsj()G*Fwv_9hudTZ-s(%= z#v^n5A4D%BwR!9_d#)}rfI}x9ChB-Q9?+)Eia@|~ou(#;Y1X@(mw}!(xfZa`u{Gqg zudYVCSoUW7Q7xEcXpEIMz?$JbT!Rw!fuo9=u&9%v|`Kj)UVUB0(`OiRK?rheg_{XhL5BM;a-4@>UA17+B1R=>7bL}BcD@sQxqDo0zx^KK4 zpU5?76>1zznq{m*1`G4kVH@eGz1YN)lv4~%uR>uYLkblOS+cVL6{?0VZ$%$w6**QB zAJ{I~aJKwP1i^;nvh^_7nepZl<5#ILoLRke6Osh_63JPcpr; zx}gY@_RRR;s~!8yLQDAkZd3E3f}htD1h{e~Uw+Vc{YbxV(mo$mhx!zRivW(neyn$g zFUYvc;z|qW%Cl#r1oJtd?NI{1`F~pErzLH^ycsa`n7w3!FAWl z)QvMI@(l+!piv!SX^t}YjY*K$p&D|S^YWXe=Nuq6Mye9UP2t#Ym3Q|77R zorGNL!VA(q3ZoUgTVWhnfPF)5&D(BrkxE&s5y<;qF+G^|84nvrTOR_ZRfR*&*C-3V z*}`HvVL@5C2r>SC!ugW{QN+pQYSv$DB*4WhPPRK5%<(GJw-xz!n|Biw-sJ78W$OJp z45AfXtJxOfx817YV$I+L2^S7jgAcur5*^rXDM;Lcm>14fzkv&OO z82k{ww{K==;a&TBJ`K>}X#erJeA;6O4oDNN8T(|V4DlI228 zEp3+F9g#j>t-0I0_WSADxwSd~jOd7$cMg5Ep%tOybE7qcno-lTBQ?e^qukPh zi2#(;4uo`yIEj^56{@M6y#Hoz?VEKcgrG%GqTae_7dXcvVZj~rjG$*4FsbjoD?4<$% z#d*Y;R7LltOTA`eR@1#yF85>ndS3nf2K~J=qnnT5Pw4myAavKhs~r#Qoyyy8yt-BE zJJidO?&%5?$-*jsyyXq->E=8)xe&dK8Z-f<4ya=_ZFgJy9>cz1ieh{JN zb4et~s8GaG?X3^xBp}@o_;_(V3v$X3WgE8%Df+#SUzkn*c#EkbSNlo^SoTXlY`mRV zHhBjVdevi74YXF4(A5cBHM16C*?8rYR2yxv57?33I3{fSDQBUTx|KNEe=rzeUD|a$ z(zUff=CC+6iq2&pP+cW1fg7wq$VWv8|U+CL8Inq}!d4&BRCqW4u@7fbXv zYsi!(Zc&N-9+c|mFC`uBh@j3ThJ2%!rN*D~(oXp-*VHnI$a~I(CiEx)gaW=&cp?`? z{kc779p3&i=9V!FKhnqMj#+fIAIY?5k!s*jF*Z6h-cH zwj~@Qdaf~AlVw7R)$CLZux#3CLi*5c4?B%0nyMM%eSASCfzoW^xXL-!?`BE@CmSb8qx%-MSfn~$X*6mV_gjSF~Ax<#(&%quuUa00SJbD zj8XIS#0|)!Hdj%T+UW8>gO#>lx{{_*m4Q3W?xlvw2jThYX<#lD&9I){ZcY$cwRfv9 zszr;QiiXpqJ37&6Ky19`ZP;^(U?DjPDTUlk5I6g#QOtQw?d$lpt{ec)#mb9Fg%kg! zioXAp&O(!*!T(0V@nzCI$-);EL{qUWR+<&BqlRIxM^VRLfHCfV+T(6_s==-nzr^wq zqq?tiUk8zMB6Po0C0r=RJG7#%&gGUqe^BygD>##bZgd$JydX==b)X6+c02g$QiLPB zce*^;r|-6`FG`+61i%ca_R7M~$7zQ+;H^wDyr<@eV4;e{`=Gy~BvPL9(CA$Dj4Dhs zM>g6BV|_^K9!n=~h|}*BSd&ve8H4Hd!QdANOnKf5c>=2WTtdjBn>P4U?T`EI&%Y2G zmX{asKjVAZm6ZuNC~bd~eJeAO^kOeVx@i>WeRMi+aLHnd=CZRZskXEuZ|X9heUXOt z%iBfgJIaP&Tox%UM}8AM7iOO;>e^<47J9>|tB1j966?Y#dZN+O)wfH_2wXsyTbJbiHR3nV^~_*P=VI_U zS=1S~M^5HxFUE`~3&9jc?jwftpgrxg>~$0=xa~ZXiIzJJ$Vl!h-IG#C{?|zZ*k8NE zsqsiRqp0lT>K#lZ_OX{pIhCoANP4+mw~k-PsfX>*MhRN5>5DY{19Nr09C+o7H!Sl~ z(#T;PP>pA596_%0);IrF3@k&Ok=Nvw;C!&5ZV??}Qj)ZMGI$Rv7oIt<^DI;6Vd-<&)&A>U= zfwNL9StV@y?5IzL!RWk`4M$*Y$%~N1EX|bmVJv?n;!k55@s3U;wX9j^MgX7lyx<>% zP}U(cRXtDs*~M(d_n6;-Ew@>7^~B};h&WbEt|cE zM?khw4I7VYnUXd)S#YiX_W7Ae#%ujXY7T9YH2vv?Z<_a8fNbAaRl-Iz+uudL!I(pf zL};5AL?+(6Km5%mrVlY7A!3zVthb&Tk|qrDb-vD(2JpKUBZ4^v`QZYyYmw(IKLW{A zNQ6+xh-y?sqUxiv~(o`m?ejyo}(e9a7< z0HITb-MOgn5)T1Jf(}W&oMmIa3_1 zSrFa&6}JM83g3D`*5KTB!WUcDV|9?2rtbt0tOZ@$*4hY)e6L?miQ@lko)z zxbtRB-qlL9osV6x0n#^-3FXhqG|Qe_yDkKWB0poC@SV!A0q8vyW`JgE+lk@Zi{?fh zxsVWd5i1=A7;-gyKWHtBqJkLtgBL~*o~cwmU=?dmR^m(G6g`U){V8Tm`Mzr{v9hvK z51$>I^w{(Ldk};0A3z+RN5-^|&l04+g%JMpFU1%vcAF=5eO ziscQ{S<&u6laY7jjkfwQJnv3YsL~Ji)O^buDiy30y zzoX3QryWvfKHltRNJ^e9633q+av`Ji#AzI~BPqK;n^+!TY2A=UM6lorz^b>tRJUvo zgBTZe{MufLh`JhbkJemFgY7b*6N1tGb>Td?Dda(l`w#BWFXN8smfl8dUQkdq*|3b#J4dgI$W!i-6LRrWBDbQlv-`q&Mlkmr#R>q7;EprFW3t2|X%O z1EGf!NJL8LNC^Q#xf|u2_x*k2-Z8#!+&}NlUt3A`UVE*%)|~U1b3RkHQMHZlNQEAq4W4iGW zAAp|$tzNROdd%BaKYy|-a2Eo&VI7Hl2Gu?|6@x}6<+IvIN|F2C00i)oPygjvo}1(3 zuM~OEvj@n`kM42k>A9@_~g%+U*5HT=GVSk3KZb4RxG`I{_+Ws zJoQh@;G0iA{O^}B{tI~hFIjLvAIzTEOP*Frn~32OOE}QvGXKV6%EW)rGg1Y#X7$TG zuVkU1SfDNC-e{P(%T1Cl9AqFs=f(@xy3E^#K5I88>-!)5gVM$Podxub5fl>Y*LC_s zHH7$d)g88?qfzhbLh;$ve@yglyHG%V_wGv+aoD$3e7Lbs^1p3T?p}HfG<*yM*pR9N z4?O0s4=jD%ie96oy1Oxup3+;$2fj8+W3rkYGYd3EDy-4?Irpc7<^S?@Cf^@_aqASv z0~k!lf3=LB0YA3N7tg<=K!2)Lo~8t}8SPIIW4)vPNOnGi>~}p`H>*OF4Ei}dk>icU z_p6YAjay=K#1O&;1)V5j?f%*{_x46J3*gRsAjDXJG#;Qs@+94xOE=`1f{o|1Gb*6AX-#;-a}%XE-?W-m zNkadKkjU#!tUzOYXB%j8m3e7v1xaN;p*i2nbCx&+bgS%B4*?1$hW%durv;T#PYy8A z8Q?>EVBLinRHeXaI4*-4!4TBH3 z06^@sC#3-l4KZ=eRi)uP62*KVreoPYz(z52UIu!QcFUY{YZb>^K7Rc&a%o~{lKG=- zNZKy=)WoW#i85Ux;&wo3w{4~v0Rnzcs{TpYrZI^b>^6N?t^dkWx0p@P=~MS96vX7F z`vQB)(Idcd!1SAIckQ`T92>+_^g3{Neg=NzMytKhIqO0Q zAqxjeABXG9gpN)KEW!IBN-%Jg_(pDY-!tlsz^N4*thz1CUOy)rqH3o}Zq>|h4g2fD z+2>2$Q~68m4qx9~S~#0Q0If>lo{Te+?b)vY+oKOCcE&3Dns>OLKLNUH^u4-LwnQV~ za#_;O8fGFP^m6d=lP(@0Id-#7q3d0a`D=uP_&k4Z5Fu?#!spX1YM>fuZaPa5KL0&J zApAv{!)0DCRNX=-jk8n^d?)8_3@8nO$HP+pA~UoZ<-)d#a$T!7Nvg*D9@m8*Q*1xq z#2y{&s8(5YSiN~jbM>*yCHD3d3`=tXb*zatZy|NPDc_79Tvp9Rn0??W&+%rf?PbX& zXPzmGZxQIIoP>h~c>mQ9pTpugsEcec_M1|>Q zE8h49dD7pP((K#cr`}?s=BM;q;Rd?Zx+NAu$?b1MLD7PjfU&D4JtPVRz~r9Pabnt9 zg>W!pC>P<@5ahGd4>a79*ld+m9$aslIKKSn4aPZcQN_>0lQeo0csN%@q^>FfFYpxK zpOBwh^45ExzNldSz%Jj6uz6LX%TMl-f@{xEc;K^0I&)gFVYgn>;eG9dREK6VT z8#0RY@I92|c=Js06fMLR~rr1xP>^d*9 zLf^!GZ(rV)3-r(@<4Aw_XKLFVfkn@}J=JK>1(2niop_YX+*}`#C+zsnk=W|Xy>_2V z!}GgjGRqF?^3vb4!?5uC8-+7G=JF2+Pppcn=x2?7lwQ?j7kp2ACZHsuUVO+M?TkbD zkf=N{if@n&Luo`H3fm2l`A;s{rUj_H-FqH8;$yM`Ci*?hSB}viuBjLTrPcs2EhzUj zEY(iDvf*lVDn7zR$a*rA$lTdZjjW4^?1SXtXS!cmvLEX0276nl+FFH7T`zIJSeo|L zk-Go_*>x36VZ%U8p|mBsVk4d`tnJHO$bi&vY44{JsXDc+FBl8U6pV*Q(v2Kz<#h16 z37aq)pzQl834>QU&R+0r#Cr~7X$t}F5|!RYeRi5TcR!TJff%uU?gcypl5rw#J@(D~ z-v8NI-!HP$z<0Ul=GP{LY`7kY4?J^3yHS=u(Elmw4B78bJaj;7h;Bw8x0b&X@zay2 z)Ybt_-p)Q>66TA^RO($P**xbsRe|Db55aZ$J9VXq8p~CVn)sdMxJr}o7j(2prgoZ? zSUqZI@9LlcUpc~~uotzMo9jx{e{Syn`*A z8iG0IZ^@2kjCzqG?9LQM{O31r?2RiSq9j<V!XQhwJ&1yg%G)78deA zk840rJ=={*;}vvH*`YgJfjYLA6?#XmpKWbU3vEChT+B=hDPd>xf;e<-G1@BwJkT5SWQXLh8!?%3EK&{LKfT654p!N-x$H(jvo9D_NkG4d`Ra0^5J5l*C`~m~_52SW7$I`t%5U);V)dShG9sX`Z>9t^P8yzY*6l zQ;%{588+Kp__@6q9C5TDz;fyO2k#Rx!kQ?|{Trh*Rh$g0<^-VL@&=vj5T8#9)o0tvV z$FCE5W7U z!jyg+Vvu-BTYV!vfU1F_m`fH}W1)cEgJ?b>dNpWnB4bitpXN5y+ZJU6MMQRoW)V2e zq#-C5im(XJ#F_H+WFUoapk>Pq;7gckP6ejP43WM{oGG=+tCxfa9K%QXx@DyVJuG2a zqo!VX2q|>-Y64=;dNG#g`@B*0`z7{wWho1Cy3W#ene2^J@w*kSWBMacBhqT0FXgtacPMbI~c1t7^^Pyjb+;Z9z7T3nXz>7n#z&5 z=h&}o10kWCp*Ln^rs(D2dc{_Xz71Q7kmgMvLv%yX=cT4rT;h)D7Anbk>JgzGKHr1D zHXOGs1%fs94vsemoO3}i<8B{o(6c{7zXJ;Ulr;|oMe6TmxOU%O58C+~7`nVNywlCj zHa#0UQ0uh(!q?l*PNLf!?E;akY6 zGXSIsn*ElXjcbVdHoDxVK;1jB)eb@jA(7o1NETARLAB1j(0$8x5@>JDVb}nz@|9>D zTW>u%EG2ZfnmJf0^{E^Ngqrh;hHeRlR}rS%MPMzp%=8(rHA>e!-%-muZ~^;)k%6==Pdn?ZfwHQriS z%=Uspv97nF>r$ZXAgQz*5ivBF7^*=Y^ew_ir8vE4McnRbK0P_F!Ov&F>l|89J6An# ztOooXYzBQUO!}U@#)q9&<9?y z`C1d%sB^stY)CSey67GwAz9^ zU8OTw(LQ4+f!gX4sG8mT^lUr$TXd^}^nv{Rh(>y0U&CER^(EHVa)gzMD!DwlD}_35 zBILPrK>Mx4RWa0z8Zx3vi0;f*z>zS4zWl9r_ zE(RtF$g@v@RF}%s{;a+m&J!?4?CtHVowg*CCH*TS>83ej40W+Xel@JDbCFhQ^`ql4 z_{MjrW!pzGM}w3tDq)H1&3Mz_s{GGAZi)cbKo~r^hUR<=UwF**oFe|uA1_=mzW0A% zuiT`P?MaR#76bF@Xhj!X)PiS-#hEXjTkjX#8dgGL=enYlzr8W2cJoQ=OMM^`WBAbg z#3=yn#>S);dr1QNPN(pXiF)C&mpkxos*C;cCMf8q>>q1W^^+N*xA`oubpK*!Y@g~) z0YBi?zXP-zRD!UuSayDb{D8;YWoZpa{o{vScm;n9*d|Sb5qDjF+U;z7>v2J=gur6G zQ32ge&WVTg7GtvOYCSvl!)G!9*d*So^56qiNRxNR6oiFG_tUi>5uwVbCs4Zcsc0ZR zGwpkMDwLi9KE-wQqr3>$M954F-+QuHnv_upO^U&adhX#{#z zr^|o|J`lntjo;b>$aN~j$KZW)TZ44*bOwBvOgI~d_*}?f@mQnZSdtmFSkP2*0er|k z8|cGojBc^{k`o+UcqtMD&@x0OMVJJqZvte5ra?I4WSLU~D~z=Emu~`GJuc1hfWfp{ z4mT`Nr4hRI=Y{WF0ge{GW88Q?Fu~iqBO0php(G1~*>W~2dw{+b4CrPMTxgc~Ry@)U zP{e5WkXiqfhrmw8h-t%VK=*b;9L1?lqypr@1S3xU?o2Z_Ze=#9nQI~&ja_~5L{vQC z)BcXgmpL!EAt+ZGu>d5UG z*522y<^)`^Tm9uVCos{{+?3N{e;oE0uYe95A@*GYI;?603lfErv&(1rej}!x)}=57 zPV7!UBaMCc`48IVR?3jhkH8V~-F_%%_G6S;^>@lz{bt>H5RAexSfcC+BJBZ%q|?Nm z;gTe!W*>FfJv@4K+Hl^ZYPDkL04K9Ok|~~9=o=Dy2bG^487eG}0Z4usM8$W`RsThH zz&i(s5Rt_fgIQ3xw)iunMzE1^gM8#@+?)|TYX*3?EqL_1OYHQezC)Tn5fc5czLL;K zF^RB#i;4LWKXO4Te?frnVHI~z(!kV_7MX`}{$@gHfcoX|g_v3yKXJ))^ znW3_xVkljz5MZ24Sn1box`j8s?|2VF-vV033;nojtVvE!Yqx+w>3zsy{CfGC%CwO~ z6G)V`T9)V?U{hRE_F^KTGbUy7>a_ndt6-G5wAX07cDNcyAMQIu&1a|>Le{|$4=CnF zJDzxt1q5v7Tm#&>4QK#K+^F_2)YVLgGHu#_rGJ{8ABja1(1ZgfQt&{ zQcy?L1vV<>tFV;lNwhbe){W>p>zuFW&9}gbZeypBO(Q$~edv$(l25M@TQhltmmT3Q z$dmDx2-jWFS+9{h08+$9UI^r?M#S?u0!G%=28}3pfIn^ia5bG&H6$zhD-l_m(fd>v z50|j#UIyX_lv!8~V=me)#QTSuZSfTLJ-Y2D|6GKCyu(;;QyWe534OfX=h;x^VtgW# zLgD-RK46Cn?{aI+PYdhFQTDU+%*GE2^8jusq zr1v7`WqaBl2Sd6P9pUdl6Zf?N-V4qJz@z);DE)A={1C9L)YW61jUM(C;2h7K=!_D; z_J&9YeJg<$7?YG5C)e%hpAS{(&b@zK@QTGIrb$<0ubvPSU}sb@qS`=EcI(2@>`@Wdeow$@9pmro3Y_r=D9*iI9D+I z%z!|$adH9{m*P?0nS&}MmFia#eWa_-hQH?64`F=PGd&A=dd5yBZ^2ywbL#j9ap<%`N5?g<1Y4>slCrCOXd#r-HG~3b z(|K;@Lgza(27acKfJ#&4o>+G4t)Cq>&+Zp!w7sQfpX^KZFtiG)whs3r+4@1JJ@{Tj z`WE1d5z_)dr)lsEv3_2D*o#swj{zwb(9FWyzl3a4!l>!vUwpnJ=n&sM7cH@n@=%0= z2w<958p>e-P2b&~_GCFGq+X*|R%RKH<{5@o(Eav3qooCjsNsoW@q9c$s{(|$of~jm zCfnJejnC?)KhZ)sGu(O&v0(sXE=xt*Jr95a&vy2X#@8=n=%ucrZ zbwi~Z#m`z?gqK1{Ean8+xNZdi@usb5 z2W2LdAC%+Lw#@=(dCiC*r}$HOhf%izTdNPd%X9Gtm0#%aleUOoD`SYt?R6*sNJd05 zU@zY72p6*^WfH!`yNv~PN*z^pN6U!+^?}s!OAe|Ch0c91RIbG?6!%U714NZyhldJ^ z&cu5&$c4nF)$??>UHbE8SAS!~um8R@50Ks`)jOm9V8qIOkAst<_5Yz3b*snvJ}ieZeCMUD`7oL}f$ zj-#Hfak@BSb#L9~$wL4y^(J_koSuM$UVhdaFvz$}!%x4z^oS;Xvy-9!)$Fc)VPx}1 zbQT-n#Z5xbuBXBSn$n-8&)K4lq`Jd z3>E?KOxG(#CMaUs%Ov77@my2AvOK%=l=`VWGDkloXFC++-o~;6_7&`S{m4lF$xtF) zlaq{VvzpbOllGuyWtNayNTg;WKb7Ai9RQ$hObY^5nC-FgZqV|u&6mNpR!_&sq=rYf zC%I44&C@GhaARAOQeCB?XG31{S*UeA-;LjHJ(pE(3p_YWORv_I$o>ru_XODV=9n*h zTtIk^Cx)FV&|hyxp0r1x7k`qd7%w8I>?gJ6@h}>{brGTACvq3pH(BE|Pp6m1StV+% z-(9{GL7jYS@;FqR#ITv?BhHMb=9lp{UyYmHXB z|Dz614dtu!v36z*{QmIBdSADY*wz3Aj?j5t=D;5>mP~}@2Q?oPuDTbQ&d||V(NJ;! zyhu6~-mZWhX?tQkO-&DQ-?dLc!|{;sMMVfN*SUFzG8lq4$GDcut3To`aYlITS4GYI8> z92)$Is`k$XSyTOP=rZ%xK%J8&C3taTEt!^RnzFf(?59@`t?0@o-pfgdkF^Qa8v88m zruACAN0g-J`EU7D(}yRHM`^b%`B*{uXhJTtyI?qIYCeKd3*=t^%#kmE>Bg?7iuR`M z_jm{&d`PQy*p?lw79WV>W9ZjH*dl*AZyR~2gB4lmf`}{k zK>rvC+-J_$!@!BgpoqSYnY>jLikGzDE0T9Va+K3QbS7`yX!=#}V0~Tjky#*Qha&KN zd-8MY1^o*R|AM{wzeSAiaGmdr3-@pS>(0^gt7-}ccM4C!Y-w2jd@STk=oB9vRmV7m z#P06KwQZo=bRJfief;p}Lp$UUI-BJD8El|bFWXZ!n%kBko)C5ZM_-+#fBtms{B}R< z*ku2?&VgiIy!GcZq@?-g{6}_dT(a_iKEmYjcg}xDe@*4)`FB5FWIzAv2gNt!=U= zO)X&XLpn3ql zTIZaK6ntQF`}YP=tmul)@9n7-7E6g`lYlxWest99IUIvOxISvx=!v3Uo!~%q=||Tw z?d-;Jvg7Qk*Q%rN1prG@dkPiprIo5_YerY8`en`$L^JYu{xI6K-Q-lP2QTO4{{TZa z>omM{gYKc-g+Gs~>9WRTq*pr)-JWc-4!Nx|E%20-@%!%~kL2ILP65yHHk`U9R_Pke zYh=f`skUl`*S{=B$P{AR)f;++BOy;Y|on+dBjhPdXFd@n<+AD{)Kg56~QpiFC`gdg=L;yr)l4o9!vl)EMTO`5_`9&2jGfI46Urk|tVIh3Fqa^QH|HIhI!1T0y>od({`sUEGXHy`C zHfW^^nI)e(OA#**ljZ3IW3MY_{*MQVa1DVKsRfK!J9l%DLbg}J6S1wDA zfm>p!ci%^|sg@v!@+Y3E&DY^&;wy=T6!1VViH|Kbb z9^PfNwnXxA~ZX&iP@JGx*3l|AmvfqWnlw33%sP_U2iWdFFk`8o?Lx8z3|W&^f;Yw>Y> zeekdv6FA#4=$Dh7S@?TNMY!l5ITQ!V-0wVjdQU`TY;V=2J1jPzh35~sYAiR2YsGZiChL+Xune;j6EceX;q2%H=Y47pY>+TT`9|K zGdNXg_Qa}-dHs5v=ze;G0>7CAG!QyezoB#bcv)0>n8fhGo`ha`t$)dfYTE(+WHHas zNJdelPiN#ZvawoWcqEE|0I~CFx3y5-)13wxveEutQWmvi#6**K2~dv35dpBS#NNtP z?2RV6ypNzk+(@_m5_4D3Zb=hkuNNVUc<;98ps7{zTyL_$`h8wro>hwPj~J(!HY1X4jmY_Mc@FN3)@QgO3iAqVAIC)6yH409ZxJAA^?eX%xCcnaJjLN6jKiS2u)7M2xHZEZs2CAHyKCtWT#l84)(znF<4fp1IztE_AgPYoKyt`++p| zq`!!+^zIy(L0A-2yCw2fR41=9!>=IgjCa>TR@3`iK8<(AH^tI6Noh!oS{JIDJI8A1 zuo^N2Wg>mM1N1@M367(Ex1)qZGRVMKt-K-(9iJf-@>(v`%KhuNOFpV>&a!ifB=1Ep zkfdXsI_0M6 zkAkkU;1{^IdJ3e>P4U1VU+&ru9tIF}Z@01+>U6!1jgODF&R}fbn_z4^47v==x`p~m zbbfq1>#fzWxu^Xanvh6sB-!Y-2ixvr#ff8q@aB34l&&fFi-|sa^W$!*STA`d-!(HN z*%~r=ft;LLHAH}3WebE(VCe`~J|2L@NHv21eLt6Mz1#Jxsm?KPgAc zS<*T@sA1YZ2)r)~^gK%!D`n&0FvNbncre?=yk2RETN&H)O)sZnVuU*2di&f}jE&Qt z)s4da1O^bk(?j9i{>@OJ+UMeQVF}2Y>Es09>9`5?VU= z0peFkIZPjFb~Te|*ey0652u1#hk5rmSz`r^x!{QcNn?)4?yS3Vd8c3gI%@;g++xJx zl|j)Jt?Cq*b_DSH>wvQ&8D&g;)+RE~YmRe!PpP2&xb&=(T^J+fGLveB31ij|1p5`VH$7MI6Tu5v#?r%3oPr zKe0b5HG}E8H1Sms(>;`|F=k%v^3f~#c3Z}>;8~`Kssr*^{^X$Bvf2?#8qidKHCf!c zcpAtXE3($f;n0YP3yUl4PEt$hQ5+G>*KPIJs`zTVXM0;bhKuZY^%^%0BUl}fq|>8P zeGj}?1Ds{0tboG1uKe>PXgEiW!4b1rE1@@PpDo*y$d|f+iej`vq)k>&`olarR1V5* zHNrB*MlzndnwXh?bVL>>w4NqR>gG4;H2YLzrXAv(7%9s3BX3I?lCp73H65GpCtIvb zlf=coD^qhtikM~M&aR8E3cvoV!!8=lY zMH;$Sw>R|J+Or8fww-WK8BN$o(vUuW_RwXDmq)ie)^=wBT49dg0Zs+)!&>_)k8vV0 z)h>P$YedU|pnspf-*z%K-w&=EYg3lW|DKvXN#hu?9 z+B)AQ8)7xqn^4VmSGH|^lp8L{I&-GeP7m>^`6_kAz+rL(^=A2q{dW zbo~^X-Itd7lJUN?b*z_ZV;#do)mEFCKp7&yQh30L>(cVrp8cS8%5oHxiQaaAjIX=R zcZ^>*Oc;Ib@o>)g3b=4sWB*J{dRob(SY!^yT%C4~*2Eh(4rRIU?JF zFT38XuAwZ=f|uN_3>>M76WUs{e|p2Yslr;AtLStRD`>rKbpY=_jR9B>Q6nSjc~d*h zncin-Mv<)Y>0d-^*MFr=dX@&wJgBLN9n6|XA=0hpLO<@Ecmr%;)kKl3Pf1bMwI1n} z!m?BKt^z`3y`HM8#*YG*UfO)hPAKZpE}SaU0%*is?#~sf?^L_D`_nn-6t4~DIqEqc z7h+Xp?jyp7QAO)>O-`vXu%fXsk}#ZPdu zpm#W+r1@(~L+RgE0WWy*dr9 z0naq?!Xv%T?5@)Nt*&Y}blNnKJn2o(rClK4_5?z71>w1V*spa^L#l<_QLhddFqL?5 zjn8xbmpR>ii|So^+BpKCdW4U_3*DFXz(<@h9sed*C6)g=~eF`@ZZDg zw>|GYlHO^mcgTgJDIa()x6Tz>4dry~nWd&eo$Ql_I8Icl1roKa;l0`dEo@}cd#-+8 ziY1Q;%eL#i35f>PRuB{Wk;EI^E`Z^It#KGDv+d8+0H2^Rlfjd^UXvTUO3$Oc*{-l3 zqKkVb)K)~k4;mS@$o90g2CVfuz2{HQ@7FU^cAOh$6xUxk?1@^(rgZXHmWJ;osipQb zk*uzpgHpuZx2@=CDB`S*eZ-ITdCrle9ky-S4+q-`cTV zPGGKfZ689a86rqjrU%0QORu*QcEwyT%G!S4O;zm5(}pr;JbUuInYJlB)a9xV$9$CMr2$PWKNG4ftA!PoqGBH z&Qnt6>dhKr+(Z@S88YjHY16K~@@sJU3MpIf$uVJ}eafsSDm~!8Ht6=P5|5j*4mp!#-9Lw_JMC;cg}I)d zB+G`?)7oRQL)Or9(^?eaC<}Qx;$t3I{T}T@i?_$)R_iwgjf#e%Xe~LORT@Ps&DqRs zfmh73yiJ4tj;Mbz6h87wmM9+tYZLUI5}vG9$oxj`7k&NQ0M7HDX|XbC&hL$fMDBWt zS*{1HcN<9PO66-@s(WN(5+Yk}rc*J{gk!Keh}_@zB~Uj81>hHbKf2De^rW1;;x%iV z4t%2PWwt6PWEAf!%^6GVFa0*-DdaO_{O&p+T06xfM{mem(KF`WtMz4j_z*KqkMc{C z1|=D-a@0Wj$4tOp{E%gr6@u9+8R8PNb2rU<6=;IXfLv79%sG7-JXxOUb!uMgJ;Vt= z2_me${O)}eKdH4m;<~UN-p}T<_>Ff&_|6bhVruOsCkDr0iCC`~N9`jqn*HY#>@!D3C+xfAu$Fkh} zovE>2^D($)2k5k~k!iBHcgg`9#QnkCT5pnWsh3z0aa>96Ok3nyJn(0hmLW!c08fuCqxQ!L<%?Pkf3y|h5IG!EyZ zK*IoIm{15g2~TGvGiN%CpkD8avVlGts-D7r@ag<)jmQw=3&5`@>|ww>*REaLd$j)a zW%ppVj~`yBpi@1LxpZLEt)ufTFSy(crhbS!9oiUu&de$fPykN01*NuV^z+r zb>($xW@KGK*~5D;itg={;op;T7*Y*d5vnkZ;-Kn~;ydFN#p8t_{g0{AGo&iDn3S|~ z_twyou~O%x=!l3q*RCOig{Uik0pmb(3ru-jkmbxIq_hYAg{i%9O-1vUV*77jDCo^P<$?bW{)_(ZBFsv0tcpkI`?$S_$#RSEo7obO%i<;CoA14 z03Vwmh3`2=dR%Wg33ZyTL}ZvArN^otupO7|(Me4BEnzgK_zWMqPQ^5)@s$1cOLw1F zU;yXdI!#xr$$%dlL`XCD6!Ti2c)Z$$nGZ6 zNe$q7IH$xFFpiDKVPV4~DH_<*LlkLjrI-Dp?b{}2!o$f*^AH^anWZAh^pD+c-yQ!P z-1%cN7b^^E6no<i%pY1$$2>>gPOVKF~R9YmTqHyv`53szmO zo=H9>+GCH>M*gB3p({NRnc%lQ?i^0MXgiPrsjU?*ABs+>tsb8$ZY7Lk0~od%lqq6Whddk1TA%6FS*a!l;ziU)dEvbd3>dnLRQx zyen{usBcJzHKFpm6;<+NwX3b<;b7k&^vhGA=dW=4W zMTcmkPFE&a_(u?3_CoI0KMqE?V>xRpQ|ovkqp@%KEC49@jn`Pm21#+IGuQ^}?mJFr zxU^;vcvV+hTlotj!J&w_#=finc$a&iQ12N40BS@Ymj{hWz%Y^egOc7JZ9bu_jKsJ;;ZXmv4VANu?{57N=eeW)*bZt=XP{$ z@H6bKMH4uJhO>!$Nj{%pD&&2Pf6%~4>ECb$IRHumU{Jl|h#`j>ZLl9O=chv+nUm`g zBK@w?XV6!KPB3p{aBIhsZnTJOPo3rOjr?ZFc5vtGe_&ar-;p*`7hKoytlX7f^qSFi zm)ZRR`zHtUdv-464*cGkZ-;Ep)adUJ94(GNSyVepUnVE|_OtJHNbyepQtUuv|8Nnv zV#UAU?1#leyu_6j2fBX|+TWqP2PmKDb$m0(^z1RXBGJYZ-GidH9U5OFlKD^H^rlvh7(pz7oj1u|TjgJF(np7G$_70@vu zoyb%>HmZADVdnt+;R*jD;ci-gs&FEtJ@n?;N?7csq{XuZ_-w{^c-nUV6dSRgP4kUp z!F^h(8nZ0zI^3^%A?C4^Rcg}s0$E|m&!8ueMB=FXlG2V@6^<)OQr@1G+vC&y6LDC2 zxchqr3i8uq*jA@!33m@$J^>MjdWRw7&W}vWzr$BUCAw)ioc_vlv4-G^waGdYpUsKT z=UGg@BPTPKWe&FG;(_~*bJ#X!_y~JzzXMZf7x$n0jh1O~#-~Fi7~H1R-YgK#5N|!Z zxzEa|G5{Va{JD9>!y4)FuZ8H;C_UJey;(|-(}2Vx^3ics;BZR0^{*AZ=zNpEwk6PF z-8kltHlD{=HVoNwY<{53+o>wKE6HM(;{Qf>EwJj0R(qw%{|N${h=W-E4X3?m_FCrk zT+00?u)OujYkk;Y5s|F+ZsGbqI518@3?n27AgLXHt#K=)u{Q5AOHKC zi4t2fgKFPf{~5;SW`Ody;fw#d2~af+-?oOcMW09EFBs45uA`pnmh0d3o2ql<0SJ>4 z6Zs64c5Kx0mFGCNt#d<%e?<%|)LbT+A34P3o3=J-L0uS&vU z;OTp!)4w>m4;NFlP=_Qxp=oJ>u+f0Fxsd%)=53`qGN$+`s*J=H>l1d7C!_A(rS^u3 z@I$^ii@-erzdZjURQ2b%qu$V+^`EXlZt~{)I;TbttpX>ygC8MU*n=a?wm$6OUXMft zn(x4MfR#O>m5mwvVz$M4$#y@eezjwRK&DEQK-Q}l_vGD<>ex!15T8+#@8Z6Sj$`jS zUgoZAVhM+}OtC{rI~Wc}fFfG&4a_7wMF-8vXf-1^kv`)PX!v3hcB7A#Inhs}_(cg$ zhQK=xI5bibU~B#{nv6z`Ufs_er-9WUrjLE^HqOnC&nFVh5>q$)o|m6?32j4DH)8;r zJo$~b9nzj8fy`;3uL+QUMh3TxmfC2R6qy#o#ld9w*zb+Th9{J$W}qT$4&$0tl`6=QY#S2G|1g6>Fp(}V39Q3Bz&i0xx(|YuN^c5gc`$|w!T->|sG_ew!ja~JRzrJa+ezQN? z>KBMtD-dy1UU1OKGwcw}Q#_z4^*0j>V1ijM%;%TZU=AQ7VgR2+5A(F-lbUP&T*4)l zLJ@{_Ys7Lj#Qnm;sKH3N8q$7AVZtD>e9g^%d0~cSRAF?fL95EP6S`LUp_9JADzY3h zR-)V6y8ySE9KP60L=nV`IP&wf-bfP@3_$WGi+C3FeR^P0pb?$4zyByKA|hd8!d{Ti zr}$awL!AQkbl#By^&T%EhJ6N6n6D4#*HH3mekuMGe1{plpB>L~#T|J>H9X%6>+L4JWWCXFNG_3A{GF_f!#%M| zuB123JyU<=x^hiyaLsQKbUNH~WpmMNv zco5|*amsakxl1X|=hzxxtB5ZiRZ`JA(Yl9pX3sAXP0(T%-8M1x@v+mFH`Hk{@DI{f zYZ4B4+63ih#RO|B80z%$U;<4wE#?YN2c+TN$jAxa!-U2csiI}v?Oh!zo3e9x*jH7< zHK+Q^XxG}naZr3@cvyn^%%Mu%ib!68&R^yFRXL-dpSKrljwS$%7Ki*ABp~%x&89Ho zsC0oXA)B7=$zO<3~dFGrJV_L!UEhkE%0j?GK9rM+zW;rzR3~BKw4$!~`R3 z{8Wnp$+Dzx?6dGpNXO(2YNgDRW9y^uMF0q;l`Iqhkurr7_&7h{VJD3(FLF{OwJTeW z2zA@r7DLhO!CSq_E0s}tJ5&`M(J*g!?1)#1DH6z?HVWF*$f!8tj&luDWTjI3b-`EM17!#9!!&M98OyV+c0k)WOInHP`!j zg_{TepXGG2-n&F9?deFY4v*dxewJ5F-(nyyy50Tn*hys4+skYt)}c6AfATIP=G(Sq zk4ww@C>=X^3!h~;N%&QJo2zY$pQQplC&8;<2T&YtTEU5=jUtg!> zx|6S(R9^@%%&C|LZn6Is#n&>2QDiH92PN|*D*`If{vi0Yd!6;vmuNPm76xAmik5Qj ze52F0V_IsKxL<}7Td1CB`0g4A53O;n*bwwu%Bu8n`Bm&y76c<+-JbP}TE-`T@9;{X z)AsM)$DJ5y=88aa6%sR$^i;b2!R_}b4!6T=r25}H^PbGCv(wgHYuYO;RZ7nv&wPiqGiu;=#lmqS#dmYo zPNjv=nY`_!#E(rqG8iYeQ{P7Q0=!ZCo-n-F49rQv@$8q0`pr6z&xdL^9M+?^Q-g#| zI6C`du2l(@;uDdCun~*0CbVW4BWkER{aK160xcsRjj4X)TeZ3d0?&IPUe@~YdkzwR n>u3GnleM830FlORpZ;Yi*Zk&V`qJi^epXgcmoI@f_Q zRSYqM@w4#^u5Dgh&I%4C4NZc(3XUVpZl&{!vx}PPZdYluT20P}pfr9L8A-`NximgC z_sauX{9OzCgB^^1ekYl;^Ru_1+5f(ZYgX)j?0?yQd2BpaZ2?M3DiXkOrL3vq`Z%zs zMsK?ETZtFgo>6+tlOAY_|4I%zs0?E!ILJfon!}CFzYKjTs!2XUp{Q z*$nwXSXAS^sD;mx*%o6^aj^Vdw${}2J+_3>Kd&kCc4%K*C`FUbJ11qUhx$rTQZ!SO z%cq1g1*kt}SZIRY3=EgIU^9H+DVl1r!kw-An7~Q?-#ALD@~;J=Kj}6B%4$k9w6OlX z#~c5Zh{?HDMlk+Q@qcfugdG<<*UBktdDyqapv}!s`R{5#{s6r{k+dX_rJBdj{Nu2U zWIJJzX^P07W5^Z9weT+9ivD@5xi~Iu_u+r8G#Vqz`aKfc#mTy2pZ=3~&BF8OyXjq= zGID>oq6ZOU$*6DnzfUQCD6Z+g4f?gTg=bdh`4QulDl`q(zCjzL+=~=GZCCCMdCtUc z54#0|_}<4on(!lHm)wf8|8^z4DtHWsFG{!)U)ZYM|s=$W{15v|4_z6E{o{ zblf4Rnwk3(QL5QQ+VuiGIHA4oZ5m`w0hUB`ckT=H6>Zch+^VJ<;`|7pOgGdz{ zS0KYOeTwMW56Y-QLp<2vZ}lxp#P(B{+^GR2Y4^J{UwqgJ2K2Nu)mOSuF zLvwnId*S8N<(?1B`N0$ta~o0W4ED6kV_97h$H>$6?TYIL8n~0=VXC*6LtniN9=l46 z5-@JEw2;)!p*PjGpT5HPP5xTwBy-_2Sa^!BykV8Prc~4)7G^?Zwbp4Xyl5cNt-wO1 z;V52v@*zUsOFnOGKj*I{vamn)M=RQ1TU1DbGA*nriSHL2X8#ajb&}~sB3xYart>dJ zS8Y%`)^}+_+lOT{T=*cp{B1fJ)x9Cw@B@P;9#KY8t>{t1v+qiE(nLYL%=4(RFCy3E z9eBFkjk3R1$Ofq6^%z07wwX+5i<;kXzH~pS@34@gD1fh%J`7EF#l7U*#HMzFyKdme zpvj@1cfag${AybYD|>!#1`U7#`Sk)TIA7ea_)}qZDV$(KL4M{^}gY z!jQ#UK`|~53Ab?dp49V08CF{m3UTT9((`)*QZgf{k&5$dWXz9v=f-JT&-Q0uchQl_ z1xcroVs?u@X2d&dUuNl_-^E7?+#Mc&2Sz9-TjvB=T}UoIBSLyD#i0QTl;@ zscNB#n!39Bw^FE>jU!l;$>T-^O$K)MPQED8zqGxOr%3U5X@F%kPV+&syUF6aS(SPF z0ts;_>UV3CYx7Hv@^H>qYvT*6K3#Vw?y$~ z@((rI?N|g^6g19`>+hFVn$eTP3;`>a||SnXY(tiDrYCx4K{I~vV;;nV;fnj*DBr{$L9Yo@_knBCX7EnB1 zO24sFVAM0&-(Jn-w%uEwDSEc@uHCVsN#8>WHiZAEgey?A>T^0l0sZZ0J!{S+f_W9^Qb>B;4S{wHm~LKxxgkTh!pPZ4KRIW zZfDTs5l)=uV6YZD$G^ZalC40AG*pn#^^w6#D~fcAKup0j1!flYX9LKcug1?gskOEpO*E^CqayZ_ZhquM(Hm2FoKwGf_;k`|Y?*8WG4^pa2*mlnVU3y|3dy9tp4%g*>8DJJ zh)~Al4-eOQ2_`)kwB#1@olr`zS#UnJwnW}3%iW_MQrSH2ecMgQV#acLxT(z9grJt} zo}7^w$Z7`+7MP|8Pm&?zB5?ws)>|!AFkgLlUDe?{%IYOS8=h}_p4oG#+hcFL<_3$cC@p}0?9*%2r()cF%Z?1tkY*J0l^2^Zj*%JzSEyx$Ok6S6ZmtNm^GJh3tn@G}P zu~lbbe1>z6V=fXN z)zxN7hh1;&+Cw`{LdN5WPj1;*m&Ih?YTnjgMt-6whGt_Y`W>i+SC9v_jTLobu)4Bv z+H9NjH|{C?V4{9zGx1vuvWrv9e`^dj&iB9nP@7OiB98hC9(HG=`**RW{jvyeiyMnx z&%vtvvK@cF-u!dPU!UVHisN=4-u?SE1m6r6$0cq3A7D8(Ag6u&zsL0c0h_;XM7R65 z5&Cskh||i|C`0~x>VF?GQ7;RR*#B*){yOrOF6sYs&8$if;(v2hQe`WNM;C)9|N8p} z6-IYAMFCRi=FfgHUe{catBv%0(SN@MCMzmM1D19vc-^zRuvsBP|9d4Trg-QNY+k!! z04gbS>|@Y7c?=Z}z(2#HE9bm@%Mc%P`_fpPJ7+eUF}+!H)Q zl=a8d2|}LePzEt7MD2Gz_!99bi~KuzfW-Qwy8kmZVw-GK>@e35QGt)gb0FoM;|h(u zKw;sfs!$`ey2>0;zaRmNlsWL9N2VoIcx?O-I-oYvQ`+rXQ}Kkx0N+{3$W}Y){oal3 zDKNXY!DqegjwhRan}~*1wN0kBf6z5!?921zgZ;FGD+sm=lv}5 zJWi*p@)CEnA>1{u?psQ-#AneARof(vb7c6=6V7S~M?1w9nmVvoi&JGd%OkDLDmq<} zNVTsKnScWB9SFq1dgGKw`QTLDo|N8b)Lg1rC$4TSL`7dm9McTdBFN%Jk;q}`m#&rR zjq@4@y!Gf-#c}_YVK$ZtiCY2qMkdC@Zm;VL*V-|aCTOegwyfGPn_-bd&v{e9k!~f% z^x9A=D4)j%oG%!Z&xdnmVSm#_(9gWUT!0h>P#UwV@%h4*G13Y{gfS$Z4g0McGCkKxrv$C%J8)CWt4x_H+q1hA==|oC@8afdOtnnPI4=*QfcHU*G}X8<-fF`CH- zUqSuza7-f5XC+D?*B^2bg7nh%hZ8*Fd7k+diZag=loYPy^jD-@^A5#agS7xKJJ-imAa^c6A)OS$=qI2r+WpfHkK zW;^~e?_J5=c&nPA6pO@O0DO=1zJe_{g_bbyw9nciu}VTHy;&X+Np=wWK?Bi5@lj%e zEV^4$Y9qi2-`cE$@&D!?PIXuULU{yO#G=;dED#( zUW1NVUdPXyuybyM*jP0dEvWIfXUxPc2A8=&;en3uxvKRa@2!2IYkyQls3E6XTCv&Z zN*DCogTcC+XEfSi{F#=#C!?F$*PVGIu~SvsqtYd;P;Nti7^F2t!&dC!GknoCrS|&s z+|PTa6mSVmNZ;o$quY@KX!2YSo0J8Pq(J<8m=?>JN>rD*#`N12Yyh#TN;SdT>~-xk zvjJEO0s9gTnOO?vsKI!Q5@>lFsMisUG!n%fk+cO0xcyDWbpx`r+If{+fWXTHNx@I_ zrjc(iiy12}3Tc>KD(KT_mgy!`pfN~siZqK7`okw>sc2lX=aPY~CdFjhF57fi8w-4- zeQ_f$(Ra^;3}{@W7!=|6^I?l|hEU(TtYXa_zW%qLUCOBb;uZ(9$^vzf*xRJy5iQoR zA)#b$8i>7VZ`gK;G-r<8Z>!yYz@H{lt|EhaC~F7yjcW()h!!=17-`` zXOkj`>T^Z`y);At^|V-m!HZVld;h(GT&J17)M{`kwPZ(}d&8jpwAaoJpNp)~(mD=Q zwq!M{{c=!eT=P~iiOY6meCMTfhSv<7IQ$KWjiMcz#?MTe{ z2MZvyC#@`AQ{c%Hf4V%q+DffATHl{aTTh`MM1ph$E^7W9jq{S-aH!V!tmB)_u;=XX zgAfXc#A!25$jRi#LW+sro$2N$1r;;i{j_N3kkX%b)pPO1=0Gxgn5{nfifg1~(tPP` zpOh{5ujZH&WW1iu!2i|_F?#`4+MqRiY+0FFYVgvjlT4ID=4CwhD%33N0h`6lgTfSA z2&sQAhFoOq(Ai!vgJ$ccxxwt~6X}&}nokMTlw2h{7=zorWNvxehvRIU&Pg%ed4sCN z-?awA5XbaQsP6=%o3tX+1{~{(Eq%>6&AFgEJ2^zEMWI?IKE?~on0W^CLR}_SNhVQB zDPoV4{o_cLePescl0N%l^Ux9bL}RZW?~sO?I6LB&hO8x;`$7(AG?g(I*l6(aa1CF z1g?NN1Et5C(sLPF6}Iwpiw7|hsaP0dm9n#?Pu>tzBw^_JMt+dRW)n1W3~kb*k>5H} zI8s+5-m?A4B;5AQKBd!R1^j`B-(>xr$PN`n4*g+h!9iRLRS(w)NW_o-r}Hi186Xse zg#Qn%B8WX1%mF!V%o6iKB@%E#@mP8KnCyNq^4128JmAg*`^c#Knv45AE{hUmko$O+ zgzj!3pKXlN2BWO1i%*$>9 z&!f6>4twvm$-H#OG|w=GendPhnLT>;g#K8RHW2N;H~GZ$qjG-fvk#loBeSU%w!GSI zrasSIjok8yM@=bjIRfT~*85k4VP<^E>$&>Lmzhnbr=}BUq+!t&Bv>6B&qoN>>zH z09N|Mp@R;z0i`){Y&)JcMI5;o`cxdzf(F7QXUknZVVFHsjpyt=N6b5a&>bI5nl=}Y zXU{lyF{uSH`fcDy-7MJVkT`90W?h;_vWYXPjS;hRQ|zrPC&f9(nHo|;#qkAAo`h zmcmLw*p_Xt|DlgZ6|fF@tkY(nkko~B)z(fgwVrlkV5;+$1~vFAX~||^7b*BCwoT@m z*&uu};mYuiO!S#D7}1yaY#^Ay0t9-)j5<$2OMf*mrV=o2ty9}9K(x-THrWSe0i?Fc zr(Wp!Q;jy~r#Js>D0fV@FBp_8C?p*1?R!J#ZoJh4HZj}V9KNyIaP&Pwe?5<9$X^DN zO1X&q_V_|bvvSh{N1CnEI5ZX>gF%DO=b6G`ydp+7h`QJf46a-_JiGL0Q&*z$9zsv4 z7R`R;|I5$j$ziClNR1u?1du_CaM1aES1m8!b(w`GLNdO82N6^v$gt9x_$cF|N48UN z2;Te_;Q&m>B`uacWLm9RKqyjV13fegG*sXhF+!?+DE&%NxUvssr}api)!#i6->{wP zB+YHQ@1D?70CfCwC;0R3$&}232#MFigDlSO5^ctfpY*2O=mViGxcJX`EmTRVd~XRN zI4A*@vYkgx8);9MrsH$;vG~4)(Mge`ihZRVb)=x`k`2eztRK!@at6XX-da#?NN+s! z6_XlPM+UxD;#2LUn7|I=BNyoEQu~{1yuAH~# zOGEj0H3lb}*lj{x;u?nW4=9<9IWMkELx;tGkUqV>Mps^-HFqE{&SwgSxZ z^cf3~1}uTGu+d!esK}qE;jns^x?s~fDHK0EFH~DWkcJ<>i>pE!-y3K+w1f~W8rXnU zri?ZM>})9ol77$wWl5X@iB7ho=ZHWa<$V4tCl;&lao_4J8D6Wqw>J7qZ_I(_7XDp2 z38Xd0e1N+PT>{W{M3Hu!5|lB_W7y6X`+Qc5+ke^+>z|nXlPr53ink0I{edY24`B>O zW7zhB5`wVhXccVt%SYc~`-_*s$=l?oGF#p0)7qpw%Z_U9C!{S$*98`Yh9XMO>I_`K zziqa|%*GZ6{vg|Bj__DwLTQ?bLkn7BikW#59Nh-I!lyFN{(ksFBq=F0^27Q^V5wG( zO#V<}j$EK=7&qWpls(t#-nq&`$peS8%GL4R<@LpC9?0c$d8z=5)Y+9B(3ro6-fajS zk6e`TMu+Vqhl&<^ZOC5k<()<=Dyy^s(16%GK*5N=*&)aAu;KmIAQB*jYDq!e;@?O! z1dSD+YFl=!OQg}SQW(?`Qp@4H#p%mDm#K-`V|cTzoca{dq)ZvwB?S{QUXLVBtxFZt zv_Ng6HJLD-mNJk+$t6QMwM%EhDrEG2lx$Z?Tklv!QIfGmbSqz*R1>=db6W|H3xHNCUqhS7{xToaP@Sw_V!0C8FuK8t(GEa6q`|zPp z7dA4ccx0ieUM__^;;3-{#D{;L{!}LcX`{o&h600$t=U~?u>&j zpVH7&`S7SRS{)+>X!vxeh%1u+utcq020-l28$6eMY-TUz(6rmo1VQEiV{bfj1$NZH zJ_(SmCTcc7m8$<&957(2-0TzGWS`(?tXzLZOpE8CP~>Ja$FgH~TR8N*5rILNXD73= z&&jpknV?)XRp3LIw*mOR`nVaVR|g)PB!tlqtHj3^Z5NomiL!SdN0b>1>&T7)H58N$ z{yd>c!pV0}#*Az8xd|mZH6oxY)*Nc3wZlqYUpUiZHU*itlQlFdF;7(lb3bmO ztZ(+4>C-xFjEp#fu8b3kwuD#!qg{dj!Pijoe0WSLH;S*e1mhVczLz`oJbnnJ zPNjJj%z`k%91=>=Uht*&>3!(-bix`IuQGB>W<4Mj5nXg#m2sY{LW4$1p9=cE^^I*_ z?f{sw4x!b+m{ZuZSV<mgSd)9zzsojpU2Cib%T0}+>F~f0IKXsf>>(>UMi{_@W>bwRK8DF zMlMrQ2*}abb55X?bX)R|I@j@gKXG>BLe>bVr8py?ykg9u_O+Cs@d`VF{ zF3{WvD-S_l+|XN!xnQHOz_7R#L#L{Wg*0{RK)w%bOl`OM77|BEyETiS%TlfoK+zps|mWsRFQk%K4OQeC}|;Lc*imA?%Q1sf035aXfZ~ z?e3t?#>Q$cc2*yJS0U0PKB(n$AM z-^6SVhYftoZ#z+DzagIQf&BozPHqm#iH8ksd=8*yr+?j5PHtS#hk^pvUAhGw5B|24 z0se(~hvpgL-RRM7#^=Iw{>Qdc08%;V{JLz6j5%n3BM|bly~WTF&feRMhSrR!qY_OR7vb?~6Tk5VhO3xtH!j6!o86=76Xi2G#Jg-AC!M zahk=Xm+qK$_v3(jIYg=FAJX?&Q+D*o(SE1f(slsVkR}q(sh(TimE4$%NafTyWyIdE zQDjPE^y?!D50!1P6>X)=87^HK*5fc4w9x3yF<-BcF-(~=2W5v=3rfDJ2zbKB)K{N zj>3VKvYN=CyvC4-cdSCH`wuw+m=BDxYpN@yOk%T-(`vjt=Apyi*BG6W1<(_&pNot+729JB6bUA4*zazrx)O=r z*unuvPbf1P;x{S6z&IH9R$@ZKbhr;>+tJ&rUBL5ME~ciHWb0+tivu@?GbrhgZwgFW zFPg1At{G=fPvNQ#54pu+p9a_IhAJm7k90P3AUuV$lTNgzcT-^n8Qxk)&F2a(g_i6l zedxDa+-JO-D9XnoM`4gp=*o2!FITvf4^hkjzo;a{DI6lHB z-id!n{l1YQmJPt=LYH@~jhb+kQ%8R;Mo8qHCy#F9UHVfyk%ipdWbz_)MZ?-4e8(Fw zU%Nh4+ug7mrrMutfmbKgC2Q5xh?2&-)6g|AKAm7O8P{KKnkL1PRBs zJzZ9emuN7%SiX3jO7BccwWmXc1C0V2?{XiB+~!2D*p{dl_#Iv=n+;e1v3U`%#Qd1Y z>#>jZ-%6-3$4wY}9}N0$aV%}7k={_TWJ^`^Vp98wQ>QY6+giou>WXQIwWc&MsLFKx zbE*8g2+6A?^zbXyykKuSrRO&SRyD_|63ZRGr=t7kek68l@F2F}T;ob=6&kfD&QQ>% z7h_j`R@};8q@Yo-nemu=+|N)yG>gTf+VSWzjQh?vr~Q*SYuAe>gWSHW>nEC5aFFa?>ChhcV07~V;~sZ@h0l%N zEW2erzsp+*35hEA%e~C^EaKg+qnV=Q>!ND($Z0}ebB}7gu>~R|w8vv~bf$|(Dg_Gt zI2tfep0cX2Yqpwk?2irI zqFW;w%oVM7#e5$doe%f&4chTX$%;r+ue@Ny*#UGEQB#8UWpsMboKs|D+my%WU(uBj zV(UWWX=UgRy7_wnV_o_@=j49kQqh^l(?|=Z?Jp1jDE#Zt@|LN~#Us&3xvm{in|(X_ z;2+HUbPl;)WNz9^a?)yEz)T+u$LZp5NS2nFLMnBc8FiULcWVP+IV|nAh<~jtlUXGH zE_cccR1Yg#D5|VYa9v0l(YQ?RWwrx(V#k+!QiuU;-FzMIrwbYCKBI)tr?j3B!FP{}GUw-?^R^%7Vmo<)z#GVcq6O;rk@I z!JKil7&s;LVE3D=x;DmN%CUJqBAj5vZH@esj?DXPw#~-}huoJ#_~|q%4-&{$*)9v2 z5*Y|5v7taQub%0B_)yrB=JRw*M*wZX=X`*ttW-IKG;d6^W)`AiPH*?u6_TT zJv@ttjhSP+O5HHBaaK6fhPqqcpqA?bfLnMxobY~_^K!e;{nK?Da}X-AawkCx^U!@{ z1i)=@Xh>~6-E9k>6Zx~?6U2_G&zGGZ#Q?UYa;?bb+)O*21+vrH^e8qFz_MN69ecv@ zS%q;Qmf)B}%IL;! z>Td`%k)>QjpuZ!=Z7;%AVRf2WFojLFenDFvK_WCX4F+pNI! zUV8(RPBR08kksCE550*x!5|O}EpIz{EP|DaZ}Ac(jL?&p`EUNaPuqac2UIG1uu0vJOAx~Ia8>|H4c|Jd<(NmosB z2;IWgcScc2$+i1VtH$yTA@`?Pr~PSZ$j+P0l0?R(nUNvQXG>FqY)Ng+# z&qwaOZl3LVQdCq-h!VO3q|p#^S$|d$hrRv5m+pBct=V9o#}?0`Q=uoQw$o#$-|Sjh zoSL+4S8aS;M?qpkdc*7Tm`e+ucMt}5bBQ-GJ?hBbL{RIpgnd5BUh`UXnt652JIJQW zx9oW?UlV?Ca3B)3pb((APSfTc6?Ui@`2ou}KV6}ap7&$yImI$lZlrFQ&D zL(RS9n!`6ku}Tvatk-#Uxj&8JDQ}=S=jso1P>o~uQUZsfsnd#d1g|?5ZRc#^+{8IALH3^ zyJnT;J!_5kw{1Xcb#gi-Guj9Q5r7Bed@C_oq~#zv+L8H9oJ6XYJ?YL*HJjkdI&f;l z&4KL?v_eL8f(2@1~d3g~jis z!qq3PPzaSQ=l16J`a$IO=UfX!Iv+f$cTpZf40ieQIlsDp z*Rg#i3dCLu&tU$rAFT$%4Aox<^`_|MN?|b*dY|uK*Sl5}nD;ImJKuGC{3U?#oM>Bj z-{gd^i4U~#f|9GNIciA6Uwx&F7x2E_OrR@%aoh0-4VxX(pqQOT`okIJCTS$_&{dqz zrxT-}OnUM=NBp)J!}ZH~cKG#E0eoTa=7D<+eXmW0)4E^cx7~_z?%C6f<93KC2FQzn z_wOqDo}+ zJ8iV3?e*t6u}Cv1DN)`$91lil&`}y6@gH55@OU3f9E=ZQ=)Q>B)I~nf*VLc#Ue#yqkq!yaCs!nX89;>8f@+W zBX5DOq>RJC`lt%{Et7H2jk;`4kEPa6$3AhWk%Tne3hi%f_tk0n?8IuD?<_wP#NO?^ zt9sf6)!j$`5>aSHENmkn3Hf+ZQqrCa&uW=G&o}GuBE^vY11_7n!exNN^`cA3a`NGP zU5Tk;hR`B0){8iw@EKd+AXG($=bAi$w<4tLW5}2ivi~X9kUoJTr>E?E1<2`Js9k7rh)#eslE7W#P+&9(MEL?1De&E2NFcm^puc-Gx{tm^kY=6?i;rPv|=`Z(y4{i ztKs7gwi0-su+%n*H_QSYqUJus(kkcrH&FAM>dW|m5^@r3h|6Q+ zw_}u~=~vB*O)e#d-+%1WWTbG}C|gjpbVE4Er(3!yF!0>mqEZcd{$2dcB3-asQhh9g zXpZ?|bw;lI;dY6&M%{?qHHEdS=4hX?m(Q{uYZJ_25#}Z+RXr|tL@q!GX9vAxn)RQv z_}{#UxA=4)r=eGL$KCE0V#dRh?n-DmPs(L|kBSq#{B%`!^!`OEQXkr|v2gFxw6-my z8io|%^#^3k|7-f%W||Lq60cEF=-wBO8n&$pi0H%ru=RcVTYDXI_jR^5F{WNU+iV@!@==$yoN}2n+q_;YD=0FW`Lk z#6iG%yCMMqE1kO;x9u6W*?1M9cVi=!Iz z0@nYarWImm z{Jz<3sL5vMRj!g&LkPzkbhdG+G*30ENzZ4)XanTf)hvjG43TnLH_#kTud0IQIKan$ zXi58)Z;h$p?uEa}=(k&PSm~{BGdg#Zoq_e@3=BLf+1f37m25hAB}Nu0_o1aXC>UQ<4K@ygc^eqsXG$r8j=^cA~xqhg0vIMjM-l<&|9r^*@?y zvI5YV!u?RhT#a9WC0{Oh5(aVge5mziH_;Z>;Snb9F#Co5asm$hK4y5;s=gE=s%_qX z?;wOX{*u6^y0jF^>rC9=t*D@Xnw)%*D5v2rNjPln5ZVo#_&8E z85AY_^uZ}HXx$dt4Rxj&rWfm0Y*kPhi_noe?4prf^vf-J@~7WI!jvq!w*AvMsqi6j zCH4FdpBV3eaihyrOFr(jd!1ZI9Au6&2=VmA-@I42f59{IPrFHns4# z0b;;00rDs!T-uKL<|*?$%Fq&dL}JT{s1R_Jv^S1uJjRfbvhC_5O<%sgMU=M^I{yeP zjMOCd;&Tc*5GFXX^70i3*Y$XxPD)@NpR;vo47@00JiaKS&q>JI1)CNxM{DjvcnE$c zBSpnB!J=zd!r3`eW+t$FA9b7P^^-8wB+S*J$)n7EIW5d@9ybP>P8v4-mKS29M63Z7 za|Q(Au?x2j3k}&SB>pg&#vETwxvi$;WJShWttAzd_SErN9T5hd@uKqXk|(=Ww7Q$JCGN$NTFb)vqmI!Tfxw zoiotru*m3Fkpp3*{xD>6gmFlYFvX(!Psx25A)2 zk-g_`(R^GYG`~yA!@0hT+N7L&R}k=xl;h5oLLV=xE_nxgL?HcbY>qy*<6Cp* z@laFf4Fp(dA^vc;_oLmma%gqJ%|EYnIs9S7`LA|dUb*$UL63?fyz;V@5SSA>aX7k@ z#sW7Ko_^P9`3q5jew&rll${s$W7!hLXi&|kt&nuWyN@GntJV;*=Eo?|6$!^af3zkX~fF3a~-X4SZZ>5uhh8{$!dfhxj(E=E-z0pIyj8IC7F%!OWS?sCze!QcD z`G8dp3>3pg%)W6XMO1KY+IW=9s{4WTuz^L~MDB?fz^uA-I*paJ`XWFCImgJ?D^qyY zoKj~=Y2_XBb75DO7w4R14sd&hZ1(^gs`7|L(7oPR9OpA@xoiT{Q6lDO?yCS1Yxa)$ z(?G2GEZ;F-w-&Z@|K_>z#C-pZ^$@^o&n1j|H6KfCj5on@W0`(o9c=Nwp#)3T6uR|~ ztKGH74|r5$u-nB2qo8qk@osKRm z-_y-QzgWG9JI#2yDA-Hp9FZ5kjo7UJ8SFSH`b%hr4clN5f`27S&K;J=R@~d|ssyn* zFleI+S=Mi|VpLEf$&c;N3!?Eo=g%og zC3z(Nm?TWQ`^~ANVR%_zW@N-l7H*2w>Id?vF)&jR!*P%RBQ8jU$$4W-wsVz5{eiQx zoyNxy#Bro1bvN9i=hYAE1ya@U&%MsX7xjl3ql8qq^6qX$woA>&{qnigL|JS`OJT)G zFa$v@=ZR{X^HK4b$!j93a-c$31QCan17_j4sq5t;p=YEruTo#@<8cKA(rTa98amAK z7q6L55az_0GP;5SQ3#7=FpCJN?i#7oG9)7iibl#YF>PIa$iD*F-Ydyl$1<-vbL=H! zcNb+yx9^bl?IQRx&-ZnOnK&Qs3+DF`_Z*+V_7mn*ZLT1EcbEV%CkJ2#+cPsT#IMq$ z4ajz0XjnT-gpd_&7nXAzC@E?omoTeLKh$VR0)IfnA9M(AD%^=)I!7%r;|!}!1&M+q z)eyW|P5`4s7VKQINA7mzxwdm}QocJtZv~xmC!^ayy^;r2;Zbo0h~vbbbib~)=$WsC zWUU&FrPU11w}#FxXY@<9ICpq3ZMjbKF>Lvyr@r7MZoJ{<){x-LynVIQiuKpiq5BlR zMZ8qQ-u}fQ9%|G(xz**c3n@gv{Z$pp<1q)qgh#TiI%jJ&Z)nouIn?%jJvTWHUY4$4 zwuBSjp3gGXwK!&#%2Z(7lieS-1#44|=h_XZyBU7S57+Tx`6h6wXfWS__2IgG{dU}O zLH%+wMUoQZH)#S=DcmFI0f<0|FXgSD{Fk41RTbSwnx(hs{KdKR7yH)Gn3mkbX^hzZhm`>Op_~a>(n^N19^b#uLbD`yNNUb`Sjk~f)nyNVSE0j>O z?Fv?qwYE;p4_utlXM~P4C4_f<>~rn+Z|1fMQ@Cx9^5ulqYHUCFO6#3W(d#5Ya+*c} z2PCnzSI5j>(bsUdc}q#V10bxCttn-mD+W_oEN&qPs-#VHnPd;BmNP*O{ z5kf#`Xi3(cqE(_9 z*2!fYqRcrOrpA;p-58nuG+ax$U zILj)OKUqdBm7AMMWLa85DQw^jBs#(6tv^7YT`pLRo7$@s7%dIn5*C-oEUT z3}ZdhKySA&1_b!Fwtf?A^oc2s7T_Nx6G5NedD5N_&t_4#*FqUD_MD?FGi}A)6+!K$1!;DmU zVt~gYh@4~vh~H8vqzX0?zdsolplZkN?*LY=rE__yq|yXlOc(1W&>`0n!%C+N zJ8UMyVr_&eHJSELKV*t3jqP!!#c%nlY`mC$yqA2Y}{U}`2HaC_&f&!m%2#!HxjFQDZUqP>k0#NO^ z5gbV%>Og=;F@FvFKTS~~@Yy=)p2p>S9x(Dcfee*|1g1E0=#Mk8Gk9b9@Lv@^wDiD# zg~#=5ktC!vn*;>2Rx#@*{Xs59$HYmbyuE!T>c6oWU$W7UhRMtkGv?FMK@(VG5S-Y7 z>kNs2QzjFKENZuj;?nNV;{I!hBJaYTBbu#yP7LjFy)UUbd)h`Sss2Ud8^imv5i9YF zsNa)RWEw+kj#OmO&S=(8m>7xY%bZj}DKCUADIP<=6fnI1|{GUUXJgeDK14cksdCWgHtLXLDFcR7@2yOY# zwNqvs{l$B0V$J@eGp?w8ld zgmeu$opss$_3tNscLavYnEn5E2;%3szzhYlfC z5>Rz382xT9{JW(v`E3fW5jP-p@}K6$Uu}$>N?gocmXLCB$Y0Hpf1PWYZLJ4+{6DS+ z$^3QRYiaSJ)c?oYTR>IWwe6yGr+{=LAfR-Y^g`)SkQOQFk`#eOBi-F;0U{tFDcwl7 zG)OMGJagfz@AvQh?{WS!&K|=t)CKE#X5RCT>$>J=HIbL(Z11OWVvjR|nB=L0g|^5T zh*}fW7m@KFr|=TlH>=}d_Lpoq=Y89@D-BWSk4`f0j@AFC zD#$msG}2pfQvRKU$&6f_b{@smVzaDr*RZM10F0jN=~;u(TI1L^RN{T9EdS9q!1jbm zze7`$QZLuGd<=zunYJ=jo&MZ*8;>m(rO(#J<3E}e7A6O}Vi!#;!mAVDBSkRew~m*V z5`!^&r1ko9KYkj1*(#=|^{_om*X5!D)vzZv?Xj5dKBm{u}L()Zo`)W!hKjE=8WsnmuI7yHCtcCKj( zRs|WgDy0sF*&E(@_$xt|^ENKtTa~8O-s{4((=3wva`P4nG{vboG3(TW(%NmJza1`p z(Y+cN-$l32NJd#m{*0)XVAq>;eoBR+L7X|6c@if!&qHq0-YgYS1iu_(h&oR*ij7)A z+frHczi?20(1X>$4>`aNdk@4qplfJ>E&$RYLZ`%wOQ;#bQ{D$#$Ci;92Kl9Xs)K1n zGPt+IufHOSLYFLTC31pZ?_D#0IUP|tbDBd;C=A!!xb7oOTT!63iKVQwjnft`@`uTo zKW}TRG<9-o%y3!_PK&3ZNoN$rCk@CqsyWJ^nSNcQKgb~S^{CP1OSVVQ%o2`ieM-On z&Km$bv9}QJxm-%pb#g6w-)v^#I{(5kyaobqH9wWC$Y!+22TOR-s@ zcb#sn{se<24S6hMiPytnOOts?0X_w4+Wf117IG(#yXaCEQ|Ib!Cu2&TOEu%PTAxPGHH7^qx($0 zx%X_x>)7?Po&(pWM3<-mN8=R(yU>nBe{KI+@$)*9@9VCAo2}k;@Ap)uw+RIDLg@Iy z{pg=j&=8!n-+d%ns=V0=T}MNnzu}4zIsfPh|Gdj&v*3&wx*Iyq7 zvl5aClKmRUoF1(qI{U`^^>2l9Yd#K>N0c1_4#@I+m=a zvA154#`}&F2V9iScc-d-tdoMpm0SB!czrQvlPu6y%hJ3Nslqe=!ni98DPpWQ#Jt-^(-y(+dbILiIjuie=X#17 z)H~AZrucRe{kilM_h?aAO5$IO^U!Xxp5U#N)22(g_7Ki345MuBKKy&hET_*RY*Q!i zzI{?C^{j{JFvu!eX_}TQ>MQTz>8P*=5ab#5oLNyZ(=U=M%JTFM4*b%*kB`Gqa+;?Y zLeuDSQL=C<(op?&mD%%jh+eqag&^pta)>gr)N$1*X`5B|!{elH$Ct8k^Ov3@o-nn{ z?a4A0&ga*VZee|b*xOh6$y+=mwplJlo{*zEc_Wt1*;4V*g5rP4bB8=aIhxt=7WA^lXFKx0Yi^XSV0gQ_rDzdgMug)AD& zQkZ7mK;&ExephB5>afL^A&X#MYOqE>1Au$Z{z0;M^I2|}y-$Nxg9q#GaWZTvoG;|o zP|sYZ9-UF2C~HLfj&^GYENtajb>pq|(S5HwTMJf`9>ouWK8lu#n8?*aE|%9z7_1~r z8*OINXsN1OQ;bgBob*0E_>S{;;N*T!HqX4G4d5xFNPi+9!P&=BXI?y~<~E~XsuGj3 zS7POcNF-ND3eE9B#&-KPmk3OFD|28*axx$?u6K*uJxB<<-YT(zDRsv4! z7hM)DSo|l6sr>WW2Oaq#L4#KGe9B*n=2gs4nq-^i>&|!LJ#<-QhUIu!=)7N{X3dOd zL`j#EF%2DTvEchuuSu91wRR3Jk=l!wjA6GT!LlwCgY>6fhSv+di0Ym5W_V7{{xV(T zEU+Bec_}@Lce(b1o|&h7moT|q7L}+r2WQk=UgQRn;{O_e z*q$GOI=HvIUyc^S|7vLGlT#9(W&I`+b+ex|0bd*bp6h}Io#c^|@RSQj zj)(E$)PK7#r;R#UA00r>v?y`*Hc;Xr!>b1Qa5P1m()CLpku~2itf4J$oAvwFB7YCx zuu?X#Gl0g6yJ3trC<~L(`iNK^jo^n`MNb^WTk@0X47)s^{h$dyzezIY$60=AZ=EZK zD|x&;y+7$%)07(d*D&OkhJo{D20VkN@%=1)8-`yCIrGrh=bDpfnE$p=o&V8|=d}PU z5jf+9e5h+w2tmvZERJ=^sCrg!s|yg269@k(p+*u8o~jSHXy)#L*ASOf2wK<|j(ZeL zf5(nk?wjV#BdNr-pKCKgQH%<>aq;ij6H)LrOU*ORxmfy}Zbh~6LH>8Q34ml3YgBYi z0+Kg!3w{BA>`_6%Z;`6sPuCJNgGqPWMz>KA=91k10kHlcZffRhcK@%hm-+pAVcq8m zeRNVp6eA0RSmQ+@cd4m=C~*BZug%v-!(@=%n`W0>4N0{1(VX%H?)AUtpO)B-|JP;0 zQm*`d8bxBn+9!c|s1!Ca{vyMk|7z-gcNU-VKx$oRnH#?}y#LHQDvLqL24+BtoQYZl zw|q79-x*?MyZw7Vugk`0kC^u+ok(CEQX(z2{8ZwflU@!HP{@2$9*jdJHu>MaV^GDS zX<@&cNs0-xH(VBb6*9il`u^-cz5X!mo1s`13AG*0VKrsxjg2%2>J08fFYebQqCXV& zcg3OQzE^`-CH8N3!Td4Yc=C~=y#MJuk3RU_);;(CqQMsdJktL?uFyb|w4<4P-MUVc?pGaerluXqB2R{Nsk?kf5`4hnhFp=D}=MM>5Iu$_V|7#+`$`FTa?I(sqO!=OY4$_5THfF@fr-> zohTbKf}3Y(u?bIqGP~+7a9UsRxy!-f(g8S6C7}xvQ5RQ1qdgfHzaG1B(39Zz&;}61 z>Q8jj-L#~HZ(;7eGZqUfD(VgyXl56}&PB~H-q-*wG>+E<(vX8^iBdOR(6FL=M{=JA zHNhf*fJr9mKTXkj0DpYY0HOx@UOglDEogw2@c-yT zjTM~OzqsxYtvjg#wrr$^?l}P0{0P9A+289`C*G8Hzn`kdpvKe71a$)Vjj~&@=WwY+Q5h6daVY zBltdh$Zq|6vYvQlR2t4HqHcC273~3-M9X#aS06a^s+1yn1_qL17!|rNb{lv5NhEJ_ z19#)3oQC8`6`wv$wVA0=Z;zm4d3_3aDR*`OM}aLOX{1QCu8|G>w$jfwCw(>JO8}Mo ziFYT_om7p%)`Ab3|88xkM6+l)jQxXJLXaOaIvelv;e16Nn+Yz#z|P(j-j(;&bFl!c z-?i&=A__cCSf5~z*KR`*RtPH>z%jio)foJ2{X`HIqzWJ_n~VY-zTK?E{?a~Vi~(beXIABQ~6@@6ryT1 zkq>w0jT#ITD8%%)zL(@-*%iBZVw;<<3}YW$56a+o1=ISlHlBXt)Pk@a#Kpxe6%wTI zn1z+rWCD=aK_2ZDhMwM%8awT;{&ogN*J%0V*}>z5p& z0o2NUi{E;TgCA&sW|hL07Mh_igeWjbs>qoH?7pnrK3kLgwSkwAZrztAn3Q$7%)%Bh z>-AGzxao_Z*oN)y_Y%#{ste(dnDnwl_nlA%&QY;%7hf>#u$+GlxhPg4iU4?w9(wJM zR7ga>&bK~3S;2p|75nJE`qmI#qtHucWHcPLErghE3V{t~0qqjF(a1k1B88&Zx9pdJ z?VL){j*N*K1=jqNNNUl-fEIMN2zRo)$p?R+psT%VkL{aLNOZI;_iszEvp*uKy#^TI zV6nrgYWt64msWlgXa)5i_mGT`7|J1li| zgb=Z&T7uyVfM@Qi{(5w8vdjdU#!!~-+%K%~2e?`K+_c89!Lu~lcUuPlH$8pRdX7Ie z2yjt*JgA%T_eT=;xvZHouf!ZjmCP@vCY9WhHNivWCbFkx3W;qxxH#G>3R2gRS|n0d z=Ce$MI-{bNIJC9WmD{edWO?Ue*+J3qA5Vu$Ge(Av1IS-E3}Mac;lr?pP#E?accssj zC%@h4-r~X4?rWn!WaOVG=(OHyhTi*fW>4v)QEZEq;(=w#ecqZ^pG zPh702scE(`T(EhX(m|8cIq`VexhL`7W^x{ayRbRfWQc4$6H}6eeEDW)WMvyeaUD$@^m&Qv1 zHkH&e1-Lk(&Ni~GWHj|PF~%1HW@9| z-lVU#7@$d5opByW0MOLdjHjtO*vzblDcP7PY}qkQ5?s}|J7}tbthw*+iu(a)GxxGk57O=)M_2=zJ|Jvj$)4fK#Bsj7og7`0;%KgHQ)>Z0;?b1 z`ydlbt2ce4PivU}b$#M=H2Ngzslc9fbhcX5RhZd}Xp2L2fB#A}RC5E}rgMkL zW&$3M&5|PNs@b5EdgcHEf)bg>@q|3QytDBPdewH?12Vi|WVyk}VydOA5Xg2Li@Ygy zU>c@F)Xw?6`ZbxMDELxZ@8~Y&r;T9u5NW!>>aP|hHU=h%ZToOzWR@FI&9Tk>D)Adf za<*@v)nY(3g*$SrlYq3`C`{tv!S6`+*?WodP{3MK zA*$E)4>ifHriHKE#isLKK|_k=_sVGko7LODM-dw<8-u;QKH2d050hy3YUk-HTQ=2o zg#Nba-ZlZwCOri*ss*E8=SfonWO#0~u1OTZ7%>A97fM%XtTqhwR2f}X1m=-q>jVx! zC$MWUM`qL`2!jl?DJX)Jij&GqOQqMeWMk~T-an=T1*j4_><$jOBD@OBX4D2F1W@EU zXuxi~rSdPvB_ndz^-UraiG{>jLi5@FEVbv+>n->#XST{{dur`z&O`=M{McL%B$M5C z=iUeP`G@*oN0xOutd|*3-!Wkv^A8o=tu^*1FmPB&cv9*^XFibmK9!wrH-wpM?bAK< z1e;wM!(i`clRi~~R64X+Xoh=_d-x#r9tU+Qje)o8<%YK#!0F)OW@7`k>%Lu)0470@ zwLjisJ;1RU`i9{40xD-R?b|0GAKQN?NbvuIP3r`EQDOAt%t4zYXu*8aidv0~A- z5u7zR27Hz=3Ih|yr_Csfb*Jw|GmtSB<;bi(u$h1N_c+g|4rTB*dC!>rXB&8*B?E6$ znG%QX$zz*TS>kU;yxfyvz_~@mm`om$%H;uoyRXQZl4&n61MgS%wO2`>MmVkywBoGD zzh(r)sY|DpbYbW3`(>YBM?R1CzTEF9C6d_HQ^h@?`Vfj6DSklmJ~Uz;Zg}`&M63cs z-xL$hpqns5WnT_&etXGjAFahYBZ2u9!voA-CnnN^ zMb05CX4Zggp0I&WKm6Xbp!h7A=+F6M?g1-aCZXj|nD|z7vyGCO_;$2vM*B0wa_81x8WJxA3T$Crc7q`!MWwDW=3V<5>zTh z%M8aC8LQF#KxRg%BC$W)`-V{Yx?LDWB~z?~jgX+FwcPu1EQ10`-(E-JxKRarqq^b& zt=BU465+N#-che<6c*ySoRq>RbC&ly0()MDMA9Lcb5l#!EbfutaxLMn<_Kmgr1H0* z5ot51w2ayOg;O~3dEAh4xEJ%4<)U!F3L>AN+!qEKG)3*Zz^TV5xz;L{@(&il!aSv- zq*->KUR;BDu9r|}mC4Yf;fsk8@1Kbhxv&Na>au2M{|FYuWUWCYmgjoY&4q>Njt~rA zI}16H-7M1H`!7-K9~djObUa8l1sW_jC`8fhkf{e+zu6it z2nTzE&q~S!=YrUz(H$D%td5+A4D&QwMfdnzz&*ey*q!*9MH$+P!>dgKZ!GbY2h8e#;KR?UzN7e&Gum9a3BhshZf z1WF0qkiF0Nhbey5BKP#FK=9QFlu}|Q>ODSr1Ljhx2d;>%TpRY>0XKm=1H?1S$H z+{8xt-DFG{v~uf`5^0{m;dkTM$Hu*N3)9;Q{VI!2i(+vbLs_2E8_ZgmR=czB3PB4z z>WXGFjV8!7K@0?;6y?966z(mJ732TzL!Abf9H3BHD+7y*5&!BnbLr6{>hbZX)G>nA-u-`7JkJ z{Zq_@Xqdob(m(Q;^h_mX@W%Z4b%I!}|Mx&WzZZxUv^?A3S}L0v@~PGzeOAOtZ(F3R z@V)-eYcS{&{Q9u(ev(4UTM8O*%J;dLb+SNzEj_iB;laiEH_V5a8H1#fP4svBYP~M= z09g%j3mVy2Eta5n!VL45MyaC9S=B7taoBp#A@AKr^0)n=I}-=6!#x8hi+SoDHJf;g zIa#&Q1DQWO^;OfG$7OfD=|jLQTD#beD?0zF*~tAdqV`o)kF9uholH(s_NmJ;qFeDZ zP#NZN>%lyeAK^>MdZmGv!4_Zdf6C8(kct(B-xuR`e!)x5))S49)REbP_ia>+;ZNss z!4LvIF?b7YgTPN;DiK{KJ$&L$WP=l?Kv1Lj6(!l_5d7njGSA;(jSg!Ebvh2U;yI5MEGR6B=b#n4{LYpeMgKjG(U*65$2GydC zgPs9_M{$l`%a(>QqN6G~yyVKJ{q~tTi5E>x8N-h1c6Xsn!xgkN>3ON6$y2W?9~9b% z+!u74XI^_1OE2k^_)5FTCMBMxwvqcPVc%;e2LbspH3%@0nLC^rUnV_$GJYrcJ0GPX zG`~}r@18kzw9%`tx?g8G41BgPue^5cuVoDPSa6gMdVgi@61P~q-JX**emXn>~xsAJOJ8SOmZO@lO^Nmd1=Yz zfrLKi#>K%(9{I(J-mgbVxLyY{#O0Js*U`rwc0zh z1ZNX}wRD4~$g~pVCk$mb0zJr`iv2d*vhMlJ=iF#Dy7ic{mLAX9FI`qyV(7U3s>; z>tf`%PKE?%@zwheJ^w&k&+B2twL3BAEZH9|$PDToKKph_w9qzH9|WA(N7HtW!ALky zU&)f|{aMBkK9EiRf)lIvbT`)G>l{n43QB4sLNb%J+j5-@;$z#Mq}Q$!dBZY5ys?S* zZk?8v%=X-LLXKZCn)0gOrLiVn8o|Da>2%J>3lR|={*C8+<@bISdN$%M@B!;#WVask4v|}~OiK#??sGIv`G-Zb$GB~dYygFNq6y-Qy0jB6iDLUaq)xX(F$n)RhN-5EUxmix*W%jH5Rvd~!)S<3}mL1=) zB`D+$#v@VZ<*H&5xgdpgb6yAKZN$GiNaGZEJC7>&tyH%YI}flu8rPWxW@qqZ`8lH| ziTEIw_ouC+sl*5^Z}YLgWfDQ%l3~d=H=}JdVNNz~1mQuZGA4CofUvz->cr{n2M3Qg z{F1)^>ggQ74Xs(U{)v@J2+hvdC&$$Hv@(i)cqFR7yS#Tm!xRzw_C$?9&MyR2HY6LS z>U{^zNKML&nl5Z<_U^^=p{lqF7{qV$*V5E|J#CaQw2Y9tmUs0-|j(y zRj3xPfVK~|VHi0}v6Ngyk(NdRY-`5JCt*Jo^^12H^LN>N(lROwclzS`!;v#8&*5F; zc;xf%5q1~<_v+w({}9`#(!+7VXNwQsBi}Q>>pN;G^50JJjm*2Yh>n9~6MhQmXQx_c zH)4DDEf?M6)s~|qu_sDqKV9ZXgyM;D+BeF*q7b-Mys)$-#C504`_K`|w22S$pwetQ zb)xC#14-)jJmjJKA#a+hQWIXiqaC7;4Baz3HfQ27z3|yX1{ zNV~hg!HRjXE;t!oGd@Y;*Lsab{+D-YG5oJd``8NKtKh2|lOrdKMmyJzL~x)E(uMN6 zT>V??XpF&0{%W_PahH|Vp(EPe_bT*?0$kq#FFZH~e4zc))dj&=M9ztr$hNE*SM~K`qpi-4j^!Li<y z>cHQ}t|fb*n)Ol>mFtA?139m4b*8vk$ftLx*reSDG&SIOE z?Gsr;RWtF&p?r`Zj_I0!AU|Z_COk)GaZ`YTDBqz$3)OA(t##SGj~t8> z^LcB$?rum@K0kylP6szOUOn&m=;r9An=2bm>RBXdG30oCw1*n2?Aa)rHo|;zB6Bc4 zZ6dX(TX$14RNLg~Cl8pp?rv}0NJ^0Z*_7*+mY>(y&q+J1m`Ud~He^)uZh8yhVzH)t zou&Ly!I| zK1Qut8YUGR0?TJp)6!bLE~Ns)!a$u=VMnP1(NBV=qdT7$DsMiWskKe3UFGxnz}*`h zc15+8D(e|*Mr9*BnF(Ur@7gI)d9t1QRIRu@NmD-))`J=I$EFA)20h9$S;;oMnkLes zVSV14@`8+xvlD$4HF@4kOjT%$T+lK14F#`!!O%OsMnh#mM%nt_R9*$SaD<<{HoFxo zJ)>o4fzC5pHui-k`V&}3h7OFQJk73oPaJ0vx# z#=nxYe*{(f?0phXmgpj6d(|mgh27CJa9zP)zVDn7IK2uv`D(d_T1HK}Teaxj4o`EI z@FCcrJ2qHy;ckts&V?ngk6xX}J2za=&+rF16R-JWe$ckT?@zFdKN4f#;$<1$M;MB9 zt#QUa6@Gj!)>e~gHy48@&s}I;I^|f!J^W=n?ZoR9hvTk%W*L6NI5U<<&C4T4&FLL6 z#$MZ~mp1UqUKq0>TIhQpKc^+>HII*>T0S9Kk+WY-6?#`tB+b+%R~BT|tunPySy5qE(kiX_5wv1LABwYmV0~w;mfZrLKpUx}@)M%RrPc-G)`Qp(Iy zyqxB-S9edRpU*<_r!(6gD~hT6&(rUJ3aWA0j5-slgyDeJ@pjgsMYSFC4g7;dNri{|1IU?Q2`^hixF|D%2CC zh@|G*UfW=ALYU#d6zbd?J}=!chxcy0tuBhefLE{LleUBd5zyWAhVpa7{wZx=opM=ssRc1Y+4Bv z8**)?Lm9mG9Mcjm!<}EQ;MLN z+s3aK7sI#k)+ia!&HFQ{57hWE-6Bj3wcM4Yf0c}v(`2`!kd8mWQd5Rlg*|j1QupfOC4YaLY7UJ& zfgJ|d(|Y$4^?YkILHuQ8!8?*2i0UY(?=zlK3NaUJJr@;Jph7=1HNLeoweRI)GM}=ac5s`!HuiglWfeTS^9w%B~RfJ2+_i zUbVY4N|fSu>F2o77GwoNyAHOkYC+B6)kkw?$kY5mE)!_>#CWOWCiOlK6mmJ%bcHS@ zoNboMuPr+$0;Yd84V1Uorw5yhfQk(tX6dkFmEYC!tNU~V?wSRCWVEf~7%34E4xv6fny^vRo4g#{9wq$?{}$99Ws;vsAxL$Usr zt=@g^>E=3iJz}6t;QTZPi6-u_i8uy#N9|rA7CH?oZN%?DQ-z9{RhbKa_!M(%>{PsD zzY3RyYbx9-8XXqF{D59rv!)IlLsw|jbCt+>>Dre`cSxh3JR1P z0BR@@1Jy|h3aY1!Opj=d;($(ZsU>DZx?DJEou;gT!vqT}pA4%@9#UBS#QkTIfu6~D zLM*PD_z||Wa<%4rV?<{UvY33LkQtto;mt&=aSyjgp>@ax4{j7w6HR|6>Nzu;{#7IY zdFkiLW#mi4^Dj?8xgxYm9K!4KL-t5-$qhbPk{#m%0zf)RPMEU=ZdMV+^j&%R`CME1 z!^4VI!aEU1sAT1|wasx&Z;jus`gYqj2?AvrgW<%lrI=Fgo;<~?{&hve?xGX1LjPkB z)P(Zcs4Y0%rvqQ%n$Dtl$V>HN@9fqH3^jlL!pZNBmOQ<)c9T6IQLM+gB{rh=*w>hk z?wpv@VgHFKJM!thj`pW2AzHVO^aLI+~`CnIo^aUoDvd?MmaN0|q5I9oCluy7#?Fy=p1 z)1iho7p%klai7h?yV2Rm!H75Eq6^;DqAOGFdw3@jb54S>0)0H@gsRFwv^?F2tG2$W zb2!L<9>z4~v9V_8x7~KlFE!n+gA;2xZQ29i6=ri$msCcQ(^NZi=nNU8K2g|BY2M4o zi&4eIOF7t!w#e-!6z2;=*-@00!u&(_w``aVYOHba&kqF3LyyFeS3FiH+AkUsTen{~ zc05c&so_jrpjqdKfc-GGAo0eZY6ofDqo>Koitj#>KxcV`qZ}=nMJzmv3OF0kb$3^vMO^MT(Wf~Mi+s$WNeqLJl(76 z<^>K3`tw$69J&F^%(V!}C-p?wyO{y19{0v^r<$wX6CL_2A$-SQe9cMlJV=6TI-*2F zPDC$y+Ei(}Yw!8Y zW#h}nj8?^F@mSL{Dl4W>^LoM-K17(nQtZpHXKJb`md-@T@ZkkHpdy;ClbHc-z% zH&xU=^MwZDCnQuCp+uc34?JUubR!5fGoH}Z_sX)$t>fU(W9GHmLOAE?_N<|ASw*u< zaIMUpfN6yq^Iig>cY-q(K6%vWS_Fe#w?sb38IgA~D&6LF=@(<}BogTh6b{@%3AmZQ ziAKxEfoJ9SgqU?CLRQ2;u|~f32|~S9N8jxW%mu0@58`mCTUK7a_ja2Gt`Fv-a;c37 zY}0HzIHx=admM)rDE8*u%CoreU@68|6QmP(Hl%Z7t?RzRMYQR_#hQ-7EBK+H%6nI#i{d5d?o!hDhT*0n(BWe7g>VgC31`Pbr3vsv^jT_jg$P#t zq@;*p*>oYUTY{EzSfN>$6Dm~pcZ^7jW2zlag-g^^1m`zjTrTIloBmP6fJHFs*MIEb zm~9+}B_=VC-JNQ$&(!%=@EJ-Ug&N^#0w%C%{#8yi8TE{+4H+=RH9xC&3(~TP)TBZO zZ#oIII6nI!shKo=DXi)dHgfP;$mMV5I{2BO+l7Fk*ebM3sYhT(n=+2GDo5ks!}t)v zxH98-mw2&l~sa9^8szd*h=b(EOW+bu_z4r_3gr5%MDjaL1Y-TkQF$x@y1hvD! ze0ss>GrI+P6;l78CX{iYGl@3W7QgIfQ_fuwhPZh1$5c(eyv)rYEvF*IrK!-7=oQ1I z$s`N5G93Jlh|gw2nmR&X(IUoqWoWvtPj-Pa?YAv;hwNes-%Na<8`g^{NFyscR(09# z`A(}~{tKGJueNLHI-?onzN@FBk-P^l9+}dxWUN8PnX3$5t&V?@Hx^h*UZmigPd^i` z0(%T12V%H3@1P&Tp=+$0D(2#>H=e-l(|atZC3(Jot?Kh8Q*8-?S17Oyki;k{1;;FX zxeQ2&>^W<+ri}CSOkW=lh;rC5DeiynLZB)k+;%Ekv_L;syi(HK#5X_FHRJr*uE>q%m<@fz&KF}b#M zBY!jvxkV${5ywQjE=3EyPH|B|-( z=RHlE1=vs{vp;DhX})6i54u%@)L4ONiqCSSG%!JKD^3PoMw0f?pG;ErhY*oGTneU8 z@GEy5gif+;bQ<)rBJ5TFWO@|s)D&b5_V;q2JIQJj6!ea=VKXH%=|)^ylE zmrj1VG){~9{j;}_oAJE)(w&C?+2_$OtfK4p+bNxZAZ<7M$NFa(DmMsll5p_-kGbD? z2u=_|b$7)m29#zUxgk%hY=As%A&u8hZmaLbF>r__Pz|-p`1tPOA+#Jorkb}W0Bnyx zVr(f&5`+;d5Yx1z-_4_!oLGId$`d^Xpn>^DjXnpPZ$EYeiADxo3chH7czFz$h6G5r zPyIy7F$YpN*Qy@C`xIS6$pnNMi(_em=74nLmiJSylbr-0LZw$U8Gv4pXzS_m&y|(2 zk9^Adl6uYPEa6fLKf*bKY?RR1%ImB}>IlyF=O@P=9C>S$ElV|qhCLxI(`BW%n#Sx^lp~=0n6|8L(oeQ zuN!@PdMhG;T*BMkL03nx{p{|RY)`N)ZCmXxeW$!l`=(Vwnx@<* ze+w)a2VF3JTBA>xKy?8SV#L%AW#oFyksgRF-}>+iNQwC4>xvccM}Kpt_CtKHQx51u z=glF(C7IPvJ{mT=ZXh8aKn0r2Ynhc(i_t}g7yJAT1&FQPOA-M?&_xQ>o`x$uGn1yb z1E6f78@<6VU%V)#sD`6J-|U0795uZ66uTig46UF~dw1!hfuP=z>%En0R98 z%I43mYK62)7}|VE+#UzZ3-#s@R%yN?cZ1|T39>$qR;#mv75WWh^L1Bu5_>4hVzQ^0 zv=Tbd_VqEPO}5uNd`Ul}83iTq58qGTantKNyBX~;j^CrL#s_WVqma1VleuYJE8t$4 zO}^I2QW11s?KKv;Y(MYCAeNmp{t6UzI)RRfKMZa@v~SD>ru8!-mGkC+YSPtHcM?1T zw9w$vCm~Z1*~KO;A%Gd?K7=+pul2u}-mkMB|3DAh+Llf(6}|?Dd(9>8+1;vC*v%EW zB)Y#hb+)Cl4Va_b`nMCq9euh*$RgvTAQyCFS8K|GrtLWVcpbM0g#ii;*Xn*jo*(E% z@n#O(dOYL>p(v47;vS>{=TlcV&?zLW(63}1#ed{XeM+2x$N9~3@cPLBK*eV*h?sh4 z67&jhMR+anlm8pQ~RG3dlI0eSCAGUH0Htoy9ib1Qha$h>KYD zb$4r=DKMciaB|V~S=47#N-u&IlHR4Ad!?3ng$^!-lfBBGjLO6V_ouf$=9o^unM4El zBx;dEQP@<`%|dzyt&Dum%t*RJ1^L$1u*JK!TvjPDlSwfA_(fTvg-YQ5ap>-8p6Pwb zb{Qt`x=R{%IthWkc54=vCUxT7(5VntLU75Wx(gx4W*2hMgbohBvdL9rOPupu03-6TRzP`QTYFjFET#navt|J!xwo~6ew8|7aGS@b0YBPCyXjISUe&%x z^+#ua9zI+gmMY|!%V$3)MwWR7Bzg2k$_F37S9Ss;*cWeEwa0m)pKxr!X;CC4@J05* zEV@ajNruEEZC#gYF}})0Te-FmNUKkMwkEx`X%!ZT246L$R5x!g0dKE=rW*|Z1ah+k zdYA>kqrjj2cwpM${~*?OCBeMm4Becb`EZl}|KsyppZ?y827Hc;8@KL{@ALo7ml_RN z>WF-3KIN%fWt`ZXCLx6hzZM(vW3r<0!IY9H2fo`pyWp$LJh+gbRTtWTqK3hLyshL1 zF$swx@B%9w#)v0tJ>sZDopb=wEMbpw>2`l+?l3Va=@;i1cc;Av2(Df zhvz#ZP6saKw|q?X8(!9Fr~2nUbNi5r4W2&x@6#c7Dfleu@Fg$HL}f4c4o6gbGTi1i z@V?<|$&PavF;PC|OltApBg8^QEz02Q(R$jeslC5bwCV7nnaYaKvaw$DAUnMi-`dWlC`sz`xnv{9|Koe2h1GKM&L}W0HOBG%hL|YMEL)J6OdJNR~CL*Ly7j=#9=ti z1I|>^_gG0dSWUC<25{?HEyUf5#L zP;MZc)pP)*y?g*Pv#CCF!mYKi%X)NL>1H%5u(J#5ghxG|Mbcp&EsJaQ%1q*3$MIFR z)3q`rZXd+2PnVnkGpRQ@PTwVZCqF*|Tq-RW)%rcVt?wHnW-a-OJ8DM&9PrNt zc6S-0#E+9p^^Z436vjRnus=%VhyiSlu5iw;>xCFvPu0{FA3chTpcL#LE_hS`ytdNe zU#+y)YT(L+jY1CF8`sX~YRjkxS8#?79iPUfV=B~4+^5(Eih|Z@bbUZ-7Uq3@w#MUi zY@^%Y`5A|T4`SDF^cWCOKBrY|Mq>cLwh%|UIR6hlPfk>or+i)*onf@76M3;8#f~s;asbiFDlisKfsabDTvHT-|bCWrgensn~ z?kDpS>lGj-`XmVJ1MR(JEiPFH@sy>j=n&^yXqdTF$J;<`ExEc@Z4@UKx1?ma^-C2I zA}*-xM@F#W&%{rJYV-wkB*vp=3I@by0FK)cv*47T+sBQZF2Dra`cS8$+?pnCTxbAP zRMX)^Cctz;EmB}eJo5ee>Z02$$$)wOYR7E_elG~0)v#Tr}LsDN*T1uc5jGSomv0hUhp+k861qG`_0^YG_;ZzlEd8I;C_^ca*tfLr?i#B z(KcDvjbxFXQPAStJjg5zX~oy56RJkG)tSt=A;x5YTBwJHN5u-isYAModSV9=I4Ig4 zK;|}8lo;f>MPnb*W2%yVl@zEZ;-!t*VDUgP6e7dw%6u4tvHaLS|v2^3MSU zg9p^9Hx_>ueE^LN<{MtA^DfPz?r!b3`PH9P^3#6&q~)-8ew_H!JZWdv>~Nyo z>8HEmEE!A+cAG=ZV`H@!9f~j{Opzv4!7XlphD*%`fDM{36FpeQ#WN-c8|@J?H&JHc zErU8cgB#3cdBmJ0m42GfF=3&pF7rm3K*^D1uWbgm_2UxP_w&Y*W7^xQ0lfE{Y8;oe?wAE>4x)%`hY&Kx(}O6{ zKE4zskTLPd<>!QYgSNaJdrt`ghXdB7%M{Sp^8qg0p|jH04?%qr<*Ge%7Vp7kWJB`<_2xZX!mi~qnoTbmea zePKxH)34VE>e}1jDuI$zM=Ngutvc&TsHrCw`7o^u_n{T6I?AYtHqF(?m^?Y`kC;pl>e(xYovsI zpp0BA>>!9ArTcOgo*sRUeGuq6=`yBk@uftz59(nH#-8dqkrbh3j-iWk`G*OB`n<4t&xvLo!%i_QWAvPD7_ZE!_DbUPD+C|7%-rQ((FC! zc^q%X{p_0&4cgRF~~H`uaT*<$`*r4fc(;+v*lX!_@{ zu6-1xK|nx2x^4k!ly0PzF6of&PATb-mQLwz0Z9P`>F$zF>CUrme4gihzwdj$Gk)Wo z^T%O09Ll}#wdY!U?K$U~b6(fn)eNPIN!i9)J`lsv)flYqggYV&E0xGN49+F|Vm_g= z+5>=nyYD?G^i{5JcGpcHnJ-W%Qfn(x09L6zTe(Q3F!5vXysVqf%p*1I!YEIxX5K)IQtm(7q04 z3X3_kfGdSxqWrgZ$4A1v?h85wk6a?>emr{fAVream(}r#c;AjlhbRbmnfiV{E_Yvj zZRnYhF@-$tTM75geVLv~pd4hY*rk2j12FfyKbO=eC%`?C~z(G#dMUziV5j+p;Wh6 zDMWeP)-;&=goD}7ER5h~hg;P{^YK#U&*q-NY<%DRKZf!GT2TKm%dY;o+kUR0TCclb zeSL2XTkx5-`#_Rc!@DLq-`&cgwCDyAJ%3Q7Zo2AYWWR`#$nt~!WD~tqD#L=HxOLvp zb*>}}=yQ)Mpj|hVhK+ry#C!SG|F=ZPkEiPdLh3i;8hJk){VOrMCFTduZO*kK!XGB? z6p*<890~1kso;`O#(#HYH}ZqcPZ!Ie3J&6zD8rJx0&zv z9RU~|vrps|b3aMD6Ta=a^@g-8+z^Ec+6Vk(LbDVx>(_1PwbthnZ@j|6c`uTepv$G2 zHb21*00jpJ4R=Cy`bgdCX=2!ftnov?roX#cw$+$WuvL7$+HFv_XJgt}hVNm~siAm? zx?Ike%e)IhkWPwIdk>h4Z!afW;%E`$TY{zC2P8p2i8ru4nyr{rYb8^(Kc+2G0{gLP$MH(1mWli_k?U=$Zo0uy%I3hl!Q~ zn}A`6i{1Ei2qAdE5bQ^RE2LEpB5dqqXJ`>7BvU_Y-NR`XG%Y$Bw6y>7GBlZRo)yYZ zP&o2@7L;a&Jk-nN-q_kxTC#-|CfEt(cHGMUO(UD95WFFR%t6^m9$;?@%fU#P4`~fM zep9^p;rAL!3>-%!(S+oWJ~VQ(jdO_wT}*-y{UDBC%!-dI0|$AOPNf<-9{$Q(epVgp z_a>Azzj$#i$=gw_im7ETfj1zUiLH*B^x&h1%qxbm0@ zaGme43kNUylJnhrmI!he@V>u^9lG(Zmtx9d^xY=Y)Izw)GI8tW!>VEQZET18{zfO} zdA|p4Pn(XnW#_GkcxTWfmJ@IQlhvB2503dw;@!=387(@jfYGQ9TY%?b{y zm0_N-M1Z?yBmcJFu8==^@|1?jR~f2hjo)7Kx>~3QQS=7GmeA z@;TmpG5GBXOS35Pr)1D3O7$$;$uF%ggEulzo1FALY+NzN9-cJ_2coy`=T~yiKM3UT zOr=?TC$h*jj!LW)C*(s2V+F^&24iJa_nA?%Q8o3=Wo{rLCCa5sgZs~djS^>++dAX~ z@|}6Y59$3;`{RV$!W$%+>idab*!ta(tYHluHHyIIaLLc8wVrip$F$P8alpl~(%xeU zu4%EH>?g$55Pf2)aIpW4d+-*gb8aV_Zq{|J_K10}k>;B0_{og%i+7{>3K-=QkQu%Y z>xL(sOhqqE)Sz!i-Qh9zPkd_qGcR*YuC$y29(Ty>VZ|GsI|A62jN} z8A-mY%2QE{X!z(Dd#G6LrSjUhbw5l)pMG{Wn!Inbg489GxbzsSc*klV+#h@jeB2j$ z@j#pPa~pt7c)w;C&gNXt0_or+M3INYZsYmI>VWX1f*(jW} zjWkCLcC;`qMIpJ=%(44%I?Kj<^Di>efeNAw#3+!dYQltx5+qd!x^8b|zfP(39Eb{G z1||15d+Se^o>3>==@Je(w3h`ZS6qF%jD(|@WpgPAbwj-*`cRIR%jM&VgNc^@we$8` zDD{`Pz-1J7y!_oa{tmWqyaE@!LTL=k1l_B~42#g+hc=qtCE>99KPYAikDKAZ-g`Kd zZ^%R4E}FHJh`0N)Wm-^skw}quT}}qkCHI|bOGc{)a*uZuhMZWdHE%<2w?LZl8Z&^k z>w37TWu#XHb$IJ#JwLg24yThsbJz(n7I;)azs_&MZd~zUomqQ&%TgE$c%3GD;2HND zl$-2Qjt1&}Gl9imN~%Z!9=SGaRwCAEpXB=V@VbpMsAg9R62jUG54(1n#X;ZegC&li za~P5CP8|0anqB$Dxtu-p%kNc^>O9G>@87B=QZ>oZG~JGsNW%g9+JO}%5;hD+h$E;> z*1uN7N9Y1<9AhtRgokh-v$L13+(r*2^qpkjetk;fz&R50zF=WcSeKCSP401*HK~xS zLs4OcDk|o{ece_xTnrSYgo6TBdx6f%5>BhNgMCHJld2X=mjObinh+Wi#1z>L)YbXDNS^pAzLly&~zvyczk-YkcbU3+%}d zfuNf7tPdxdq%_BftB5PcH-$N7BvnM_OY4pBo&%UM05m&v+LEpxv|!@qYYy9UN*ER9 zxJ;M4LCJDCHzK(QakB?)c*gdfAIk!}blK)tRY(1E z&AKbZ-JZI68wT@k{YV&*sPAqv@6v33B?cM?N+%|B6d5VG#3f@=!VD!aKR>R*7$D)@ z!;}m*#!ZuFW(8Dzy8H=v+zQy33>z;+mOa>ESMZ|}B`|#9Z@zqvsFC>9sP^jD9qO>)MiqS2Nz@~(1d2)0d_}z$S4!PN3V@;M#)lZE z6F2Eo0uAXej7Qdc`o4}l@i&2O7CW)A_d}}Tr2xy!8G$VmjHBGWycoK=zVniHWlIoC zU$My$CKHJLtZY7>A3FJDjV;htapZf_d_eQpq@yg1=-*-^470NxvXAE2dEKMNr4Wz9 ze@>3C2gc?*1;JS@tl-SH{957~Ik(niEk0vkSai}a>ZnZt?EKf$% zK9z;!N8DJ#u1izUX4V9nsPSNokdaQ}<m(V3MUaD*OBU1 z?z0@EmYm)l+45)fTi6i59C^&;Q_ThfV1f>c)%I9XloA?q!N)|9F>Daw&`YiEXwat= z09%Jr^E@#a97bFwr;vd;h3^JQr0(egB?@l&SDk(?RrxdDT?=~A@w1d^1nZJDfJgGn zM|_(Id0nb{@Uqg$L@74hlJhY^Sjgwyo$;CyPo7Ye+Vo!iC1bCd1dV_Yle*kT+^@em zrCrR8OJMx6$g}&P@5fI%XdG@8zYTr-lfPWmPWs;{R0)l2n3sMP6MdY8fy_E0D1ZfV z8YDk#g}q9Bz3FDCPFac3X8p09)jF>W(yV)z` z-<`L|T5dbxQXu|kwxF*M1vmTQjBR`v(7$Bz#149%RWT|eH5tfJRyB&_GC5d7!B{9G z052jB?|C9QkV1sh9L{bN#lNXGhKA*E?dQYCj|fPURRmHg^*pN+M^j(ELOPk~bLyV{ zEU(O617k_TLL5u4nI2Ru^fI8ozeXQaoNLHq3gzcC$C7*Z2qiVb#p8hPDbYN3}W#Q|8^D4;8CM@$|xCGPF z=Et7;M7dGl+w}z{`alF2SRfxlQ82N#J{t8-Eq0)t$OMA7GEj-XYP)Ubbkhj2qH${u zM==}zRzmp|X3NVX|8S!iDLtBq*CGD}jvUJ?M4^1Rjqs0+N+O0xI3ZwZCY_X=_6xL? z9_^nhwdjqzK&vG?-)#KEu{^BMbf3r8+rKQ~rd58*_$E+Hd3DlfwZB>T2R06aQnU@x z;OCeNv@@HT5GoANO~h5Hu0OM_&zy%U>a-Rug+n!0nr45JHuI-OwoH9ns&=<2wog>0xI9(lt z;};Up$i!#vc`oc53HcH#iQt{j@$>{p#?L@a*92|&T|WmVendZLRt1PS1(koO{vtVlO_1TUTO?c-5;wm_T6FjiVuH165iUCM;`X0!&3jxG%KqLSFwX<%rZN_C zzUC)vMzeL1Orn+!xflQmB;9$cuXWM!wkFdO7HpKHKSYDjJRKIIR19o7PcvxPr<5He z33!iIKo4Q0B+B)4uV#AH@^2uR#r+V+QQ)TG*UKJej(x68VbD?w8^WK>{i> zH7=7{W?Y!8jcfwz^Yat(r)cMp)1)uv063K1oyP4^&!a>@kD6YuU2gN4h~G{moQRKh zN~5;$wt?025OmIIWv6#Ir8_JWpbY2LzqT{x1A)xdZy#VQjpn$yJShe@@6^|w2j-i` z4kJ6Y8k`D@UaOMqSD}H2YKD8y9SE((k{}wS>I9hxr4jt6WX7qEarEld5{Vd0O~(p@ zPd|8qeA1hMt5s4E!wYc>>`yhEyFA%V*w3(QaoJRGrup{DxQWm03?#(XUZs29D6s~v zO&%Ns`j%82= zSAPb%@aUd|R63WUsLe+b4jHx_A-KQ}>)UGr$a!W8GKE5Uie+invglr?%_~;qpz8Z& zBl)*wW0npFy*MvGJdogBhzZ}#@kkCozV%B96c6?aE`lH4f_`w`U%frK8p$r(EVdqE z!%LezKJod9$|`#R`v~5FfXC_D#jtXNU+j7fBemZR4eOY2iA{n^HgyXf-g_uJj4RgY4hHl__SfYm2k*w7h%HllG4nTiq>@Rq`a;yP36Gh4Ha#!x zSPrcI$A-6{>IALXRzLzY4oa*z3_5K)TbQ=tL-Gy}M<Sm{a>OPb#zo$ z2c=dop)#DKJmP*aSLIyx2%lfmoL-6)kvN2JY0StEjb9+f!fUN`xDDOjGACWA5Nzoih+Hh?8eiR#;+fc z!pe`jL+urGEcgWJ5J5VHS1kv`-C&;5Z!p34bQ*hBSgwHqY4uw4T$w`4)N-$S{}8)?eUZSKd*jsDIIu z0(Ic{uS9e(tjP;(RM3&N(%x7%&E}~2u$#{A;0IoVuyQ5p= z^?`V6_hrJZJy0(_5apSQ#-kDPLyj~`45O#u+3`m^Z#esf(IAmGbGWT@r_O(OZU3B+aZ?bt$K}@tM5SZ6 z@w+~2PsK!hH;w9gr{7!s@<}G!u3#zJ7-hk?PJgfBwVeA;qd&#w;^H!^&$V7?wwp4H z?{aRr()!Z$4^QXs8QZPV=gwxxs_`65>O)DBB0aDa3Z*T#ZeP$i!{asWg1H00glQ4( zt6}c93_2i-0i}ei_1mS2Q500ar@zF!?B}FZ?E*3$8MgYsvw*hdDfZ#K7b`K9cB`)w zne}`Kpozd)_oF^DHP!X>yKCdGcehv2uD}(a{b%d3zYyn-Se{G#ap+tT?S~5 zvgy6E;Y!8;8d@Bz;_{sHLacsg_;bBmRSJpgVrm7rE>N~c2d!41*J;*V>rbVFR(S~5 zd;wRPwV)Gm%2ifYj`II$q(0_1gHFh7+|RSe>r5k9%@3M2Jiw%bmVY!}tR(mIQP)f0Z)-NAn5FXI24YED8bTW7<<@Btd$>V$E32E8QM|7Q0yq_!^x*N|b3g zDH-@69FZ@zrC?xEZD{C&F7Vw%oHGUGEnppXM`1wvO(uxTd-WRhyq1?IqXMMkE!)8c zk-ArUhaijm10>g;8dsW)(S8{?;YX<@W(OG1>y-!VNS*^JTqCJ~P&hmD-&BD&I~Tjf zqJtj7%FLnZJ`lD?+XhL15Jk^ z(cxTa!l`DDhSvffmkxk!uPFuUPw3IFg8~bc7HMEJJtwe1V-hQYM6^Rb)~I}o^q?j@LKmo+QF61!#<0}fnI}GfRAG7Z ztqeq>`9Sz$?^3U3!%|vS_H9eWO%&2!tF!jt+mqJHK@pUjj0`4RpzCma|4nr@QX$fI zZ@@f>HAL!DgVyYr>)_$&MP@{o0c;0_R-_Rqn`2?CsK7@B1+>L%uYQCsoB#y_vS`xQ zx|fanZK%6SJb*9Ydad;hsNhwuBK_}o_+vpowH~DIxr<+(?d^p#NCPar$(AouZFX)h zv}k4(6n%f9E3Kn2nRExi^unW2eveC$o$N6q`ix+tqzfp4@af)y^$o?xjsdqe2BsvT zhujVu8J7k^fz?5S-&mf$H2SzTsS+arLOVUMy)PXehu)eF;yt>g{tA?f2gemuV|)I) z%IjYdTy=pa%p+4laFuyMoYW)xlK2C;e2b^Y*otVDKuf6X@KPwpBL9*3OI+P|8Hq^T zIX~R=X#Luu5)4ELJq&n+Pw`gEF?L$!sW4y={Tebq1n^uBq7JA&4tu4T?@>V5VK9gF85BPh5)7)*gH>iJ!&r~EFs$EpBvs^^nb`Y4z+;LG5;fAp^X6I>gu|q48^$oxsJ`}9)j=) zwAq7db!Ya!KLCs>vVbz<(){RECVRzHtu^U={o$V~M5Qlr!F{N~CXti468A-ie?C(t z&bfcXLDl5ne_o@Q-cRG7-xP|OfXL|hK^N_A#PIOH0141xl)@gPeUP*D`t*k|9tHHrKWLKOOvGrQR6sFmxM(yLWU z0L@REnD`*ZpgrIO^ktkg{1IaK!hGC8QW9U&Y+hB=H~Wvg!9{=yS9rJS3{>;QzAYa4 zgUkRclwY~Ime*1f9#Pw^kJLERXe?F9&>jIyC0RiMAp|gus}!p;^1XM8`VorvdT)Nt z02nee-dyMH@gZ(6dfI_oleuRF7fO2pxJTQ*0+$B8PSO@$Q@!y9H7Lq$i#!p5MdmC#XmrW$w^-xi~Z& zztER=MT?F_6kXd3D+b9e&v7s*ACY_L#r-3ut$6JKkp#VJ<}N6>Tp#?lyW3iryOLG( z>d}uBtTG<>l*ZS9AdSWF=xl3DI*w7xB9%!cc7p7~^Uql#*FS5B1bhzU<+s5$h_u6p z6ciLxfW^_+Z_DR+|Z0x5oMhN?|RX*D0jm@lk%evyz20SKq^kfPNjAbTq3@MOnUQP+v_d znVkXhPXiC7O1h{h$N{s&=?Y>s9yiW;s9pjk$NWj=r532fA$Vz&tw_Dg1X{_)g0jsX za3p8sb90EtfBAYVDioI?8ka#`;%s;B88!8(jK;EIB!oK}w7tM;Vg$SQdA!iWKy&b^ zQ0wvFCg3#A9sc(7n~>>z<5vRDwM1jkQ=Wmg56U*(i=IHh%mXs5cX081#{DV3DyxMa z#ysrk0kv=hQfG%fOhqkgoC(-&+LrFR&EOjfC5T=u=mFyD>gp2QBV+(g@Wv+5y6GhD zh0yUnIJhe(0aQYP<@Y*h6V+A97F8#ZtM~2s=iSbH$bA${(z;OF_!45E?)n77*FeWp z@Jg1)i}*K3FXr`+IApJvoCyci>T9Pd_rvG646Y-GU_^CPRTW6}Z}|(&e^{6a=-4ieisGKH{_b8Tk#~ z-3AeUCo6v)$#b6~gwvPTe*KVlQZj}*OSVnZL!MV5+61w1%~%0;8h zehb{hvrS;rzz6>G?nX=B-559o^_ntE>V{wc9{bj_ZnAkp1DF&-YuX`a)e=@hf=`=s z=e<60Wp>1mkO?)_+OMN`6A2Br0^(@_99{2Ahdi0o6<-YgNL+@bBJYQM&A=Bt{DG&V z8jmM=z$#H05lUoNL+m4z=_UNt)!&v)qSw(?dEOByRA>)wB!my_ykyoQ@JWSaKHoJ1 za5LE)z84;)#@U}F=FZq?M={X=#R}k@&WT` z&+Cqd6MZJsP*2AMHt6q1ON$R5qklY0|Ez5Bb06l*B#45W0BxDj!eckU`ljSI@? zctMx%F9g!%*M4FeKW#fdd$hJV`JU8Mqo!AE9X1YJ+nd&lEH)eJ$-+XDtE{n`Z>aem}TJaI9=Yo0ufod5eOZ zNN7({z|tt^!d>otj$S%>l#sr`cIRt^L+qS^H8qxev&9dA{CX|4k!c%7Rijv_J zHWS2dkhX`85@+rU^Pz?-*1~N!E2?|v+rm0O5ok`tERH~958kv$gf`v*!p$36ZA^$) z-J-4QaZcP}Ct;dgduM9M#X%+((3PeO&8b_?aBC8IGQ@_9ZT^L2(xcQvZ4dfPtD*QR z*SniD!`1HCaYIo>UNn%dT6AFTM+mkgR$tYM2MJS1c8m&=hy4b>=pbehyRcXoK~8QK zrAkxESNrusThO=`oq+a$U4m@xIbG7`hApUv_;x+$dK&OTZ^l8qBuM)p&B5dHSpMj9 zyGoS~?%&ol-(YxTFXw0ECjMdX)h%7MY>R+qK<`hR+YW8y@IayHw@*Ri*fF+<{FbNu zYEPb)DU7IqpVt*MX$W28*c6?q zmJ9Bzy1K<&PtVbQ%#g86tSZN{Ni@EqF61WWPHN>8@yjR(o^nQfIH?XA~Ahk2wRlJRhluyHwv41MmVTWhzAMA&Y1SRvL~7utx~ zWj+NU3Jk{3e>ULM@%IMd?6dQ*tuo7cX~RTuIugL+y5#*B`Zips%V>S&ML@0wXO}VS zJdzIa*SZ1jmz#NoJje{AMMF1!7dhjqPh@=!W`e!t;abVMxK&=bUpu&?VcGw^(V`#w zLY_T)29m~rN{J}*jV_}uQ7?kSyO4ojOSSAoV&Oyu9}%*H0T|PjS~@8^F2a?7%?vQc z95=FqZq=lqgM`ZV1mMaWA#zCaJ&^E4H?qaj|H>>D3EEo!dMOP~gsp~uTVBy;$s``; z-qnQ7G9<3{-dP_GgI*Gy6RWwA%)Fp{4j8)>5#xQ9y!RWByiS;JxtF`mBNLMN-q*Yk z8hlRId>g_@B1byjp6~Te)yn~b3+(5Rk7>nCwnSRh&ErQ{?($+7>GU)$LJz0LSCw5S zpuQ0B0mh`+mwo?U&yd%R1dY?O>L)q{qr*2dg*si<*J#(;m`|N@HxDoxhX?9=CBO@g z6>8B#6FPVZbMx^Vk`Q(>$AY`!mvZLLcEJy}?~(7MrAFF5=Cj1Ul^JrIRg21x0ew1b6qCxUP#Q*U1l!(!y4FR!MJ|R;yy=>Pvy_$(3-%Lm$HNQiBzs{u;sVolzAgz#TJd!Yh+ z1O=cvBL+} zQV7e7>Vg+VAqgy)6asS6*tf91PAfptg2EM&$+DywKM=zDZV;KlCySXp_Rl#b8^w2x z-8_!LA@DVLI-qaX;O4)u+Y-vu(SiRpYM9*t0K)%#8#GG&m%tD{R*6ESmqxvjP-hIB zE+L_KOmY6Gc&~vUGFLzWUhO!5-u?OqqYWJ-m|A(744=y``w{+&@5F`N-+6RE{k~!c z(rp^I*iseN7d|`smz3AuJ5|q2YJs@DT}2 zueO{Sr;<%0>b0$R-rFrBRY1;&26bcdw@AUj6}yZd%Xt&8i&phqG50(k+T&)X&Nj;f zG#5Gr@ye?iBP%e8hig0m57CmeZ^)y+d50FXX>Qare?%r9;yW?rc!8q0fcXMdZG6~=~<@Np;DIcOB|xaXFlL}#B#Muai)Tu$B( zFV}lmNj6n&8snuZp&GQoOhBxFm4ij5PUCkh%|bBr8Iyz-Z7DMf&vtl@k+?qVw^Fmn zra@7Xj*1Fy@05-f4n%S(ztJ^I z2V43IzU%U2`({`WUaV$#t`s!-xr(nf=!(`2w4UwoqIiK9!ju9^5Rpv{eO52ve6^-{v|&NP_P4I$N=h4*4m47uZ*@FY zy?R39isW3kOKOX=vmvTU+OxkEOtY3=>69cNn|tO^!As_L=GI?{2;HxWpfZf=lr=pa&Fc~7NbP>5pqoKF!9&UH8{cB|M}c5kQ>zzFPXyEr|3olw( zh=Q)=V%kVL-GXqUY}tnJ{<27HaClnmT6*2jk5fzuEK@24)m(-Wrt@ zI;tz<$a@wrIAP-gE+P|06*d zpLpRj56-(?5nmm3dLgfkhF#Wc!U!&Qt)FSk??rb7`>c#aWnELhxiFXnA)=QK=Xtl+ zR(x&<;7JK8<(@6&Z(NleI@Ru;YL{VHe9LG(9ow0z0TB<~zFEa&45yepsglM4kbgv9 z<964DgMq}Z+&B$J%F-_A)6vrUe7$;f<>1%S_-4Wi99U~~t!|cY7!y&9|p!m(4Sk z&nAZxTXa91T)pw~B4qQfPw@|Z?VAIhMgVvk@rb{vZdd8JWhr%!`OCH^jN#^}F3NRUy038i0N3x$s;!rN@=NP90j919@1rMN0#NdFd1?X*B0dmpU;Gv1U7DHUhh#ZUJX`S zHzTi6y)WxKCP`fJNH}XSuqz}Rin;iF=C`rVWxsyWGmHbdl*_^pqGeNZa{txDSsi}Z zcj%EN+vX!8o7V7=BzC*NPcn7FYp@x^ADdcso@3#PIS)^h^JcYItyLWchPCm2VSFIm z3L>OynW8@Y7Lw6tZ2*3$)I6{y96KL>a$AIM>bVN;ZTj;_-CUF_84NBfdtBMTPvcXSb^+X_*)q^&WtZxx@~CsE1g<_TB2` zbj{m6{@4*p@pw3M}N*`SEPz zy{(d_5QSo{r}kH=EXJ5-yi%Q(&vraFM8lQ)+?nwwQD^*`+PmRh<5!0uNOqn6>>4I_ zgGoKpQR!Fql`h`Rw_HpX05e+a-oCWBcJb0?Und({53#udFd7wfQ4%gUhgCyAQm3ri zvl(FTTwgW~;Kpu??h?X~-97VY8GA2(uFVV(-d<_*)NQVMwo!ea#3~SV37G^7u6P#hhIABV-*5v$Zx?w#|oRxqzz7DRQ@zEugyL`w1m}AA?}1XP9}3a}lvTt;r4E z{#5ctmwK_0pVae7uPy72>d_a?%I6ZV@%9=@E*mi~jV=zmFYMR)PuBfswk}`kFkR)^ zHEryh!U*2Jx%8yt{w}%)@1`^Qeqhpe`PwuV%dqvm4f|E<;(M8`_Xg2tdW+BpNiQ*4 zw`r5SLC&Ekgmu#snU)XOtS_00Z6r#xgFcs&1$!pY;$Nx7ZbzF4Wl}L*z8iY{%%l8u zUtDFSt~m@0feLg|bw1w(zEiohC?DfxSV+L+_a7(kpVF*Gwd6`}t!~*1 z)>#dccmCM_fC&?+C6xJ!pBK$fMuw!BgI=wE?|Z@2j&nzoIF&0YF4SmD`G}CFwc&lj z!i8MsCYC=MQu`o~&Ob-0PS{)y1dc+EImKc10^~1ua-?4zr)8-9;bF+$8n`iqhu}gs zqc7Q7B&HAiHgu{ zLN~j?@R{CU5+BEUEc%l0AqJ~+G`X~42-~ms9qBw)FDpgC8j{P8XzCe6WGX2mZ{QXP zS%Za%qyuJMJO>xzX1%@XK{otYGqsMFm*cIH^ruWf?`dmTs5c1oPG&FTHDq4E={K-vE!npKkNY(xkm3EOrn1^*NeZK{Ryvb)I?rQCLu` zDcA?R3`PT6(oGw?fBWuyH|fZAeuj+pCc+%G%w&{|9|RY z{J-#Zn%$TrhD$&u`>&&A^cy|6KOQQL+uxqr#$P_PcP7-Cg8qbP&*MVgpYBu)!`oZD z>$0K4XD5@d-d%#Xzp=b`>tSBGYDDI5B(Ql@Du0Scv@gtaG?qzTu_nyhJ7zhZEw`!| zvb!HiPW7;cEK%;oENyo!qGFdRVHhMh3(H`vmhWBGD%xjR(kfvLG%8*UJ_D!il2$m{ zgSz{#nPs-3MgwvnK)fv;*gL9}Ynkjh*>x!!Jb*oa7!au*2~fXNMP&%s`G2|R`N0TG z5>^erYY1W?C(4lyge>2a98C44=T9@e6o*T*t;^Rw#HvS<@%?dC1*KO+gcM?;vzt+( zr=PO-?j)J_%J5I^OzQWQ*0t@#$olv{V7N=aL#BGT*JACo64mNwd}dWeozBX$`lL!f zoTXu%q$-^FCF#4YqyFaO=qU^@6Q}bRa;|9$+{d`3}!j2ic0O`^JeT8{db-2 zmoG%T{|RI&4!RQVTuW!{nMV;|xos~`9Vz+N`q`2N7IyscdeM1$ElziI;bo(JvPDsA ztL7VOr)Dd!ffktE=?$cx$!P{@SIghp9~6 zQz~tzJ3ftXuDTlwhb5V#!Bq0_>Bb93F9NnhZOV~81PrGUj=2>QQmwOW$x`>P+oigc z5j7)*Y}Qsa=J8W1dpXXX#76O2HDySzJv0j&ba_>q{X%fG ztT&>>d(pYFw;VT5^J!2_8>;#X>r=%Nh}_Gk^3v-!HJ-e<%tNM zV-}W@0r>Ur@(AMXT;WviA9#=j_vIRVmNw}?2b0jXZ70J@lPC=)Mgm7n1$_H zGPswN(Yy$>+`El06OgJcHoGJ8_fNM635T49hRDga#IH_w*MWq`o~DqG-D%rp&Ob(r z)vWlTVrICkC<#SZ6GdIpN;n`%r>}cHPG*56K^vc4$cazVdSBvv@yzH=TtbPqrPFfQ zt~=r9^c#yikk&P7^I6$0o9k~tHx3@I9siC4(JP2{12GHc4JU(4VZG}4!OyK9AF(dI zT1OkQG@a#4z>ivCksHEH#U-Y2n0IXz5v&!jEh_GnHs^_bSs=sx+(Rh3??&NyW}A56 z#YXeyH3L*W@1>}h@RIDSL8EVl6ch>B>rxMfvV8GLh#9klB{L1ZJw5kZbty80^%O8F zHfN`#13izDFL}O;tslvC4Q5L+QwEfZ!~2K@ZbUvw#eiH^Kl6BGrWm<#>qq%oWSxD8 zyFIW0`tXiDbKKVOtLz8RsGxU+5hbTaiMHs$EGY9aSn*REFJ7LUG;!soeJH~>1|WrR zAtvjZ0!1Q0aSynI3uPqgbw4EE_P!6Q9wFtEERwM(w9XnLr^sx#R7wI1GhF|LM-rrE z*}&g{=(|K@+z$%H6gN&!_H<4LpK1m6BWL#D5#wfU?mJQa5%2Q2gx`Lx?kL^ldk6}l zR(du+#~@E(MB zqyH}6PI*f3Ap?_WIx!;d0WDgZOWRJ_ZFfnYz3qmEStfk&_)u+OKIf1tuY=X2Sj6OUWJ6B0k!|+Lv+h+q-)TNw)=A`E<&+_fb1ZJy@-HD(jmSF3S6HQLphLx_K}XY$ z93AzGUR059=1X`jGb43e6E;%!gN%?IA4KGX9!m~hzo!pIUQh-qQG%!5(LFcii_#xt4nMC*tDST z?LXJRWH9~{t@y_zlKd6f`Tv34_q-NM} z%41H_vC^p6Q%y~F7w=2%wV3mN#CE#LAO0^AWD8bTqutjcT0BG^oFj73_MBo!Z{p!6)h9I9>T%64)AfaF96WO+Yc7m39o3(S**Pr*^~mPKIx?`CH|6M?Y8efyd9DXNtw9RdHzgEKXueX_PsUGm|AxOm68suBKc&f`lEG zd2y`BLChc#JzF--YX#k5-bUAsbK!|qkf-9)jY`B{~T zNZ@PKSSqHb6|q!Eg-H}0@=~Te9(mYk-68^qPA#=^^i@vgazBr6uwV~p$q_XCnX8_J z*o|_UaqdFYf%x}KLwXjx-#C4^LGZD!2D-$TD? z>Fh_QWmcsMV`N_IL>kz)m>vGLWfh@Y_7f%a&6BpA{N*&;9#hnv#YLy1vLwIkOuZAK zR8gATxP+WQO2+2OruqNcQ6s;=LUco|v&gfbhmIOhH<3v*s+@|mszb?ej@ZISi`pLXL|XjBf<-CSg~-bVmp#+RotJgzI}+R zh40FAt+HIJmQ;&{sK1^0)SpfaGCFtiN5%3-5&9#g#w*)8a^`=IXWp8TweXUzu3s5a z&SC`!L;`yu!Q*4>)9ee%XO6W@%}1sc z^BWoeSOJ)=hyN#b>^~U0RZF2t`!nrrX1l7n!%MSf+H5%2HS~|Z`APIrg{e2=kfdwBTf4PYgS;xBjb=-5vqvWm%cooO( zGUHya-P88tI=Ld@yL>T{=4{28F@^rP2!f}F_0X?L9=a@?)Rk($34*w~s*2zFS zpc*+UrW)CJo;js+6FVg@o;YV0I(4|(iUK&Ed$rxVb^--hd9H1$?E6{FM>;I5R~jqE zN{)(j5{Vt;#u`uJG~(1FAzz#I+IvgCcI>Rg^o8?{zqcr3zFLXV)zkeC2hKAl3AbbB zh7F1;$_Q4TD~qa5l9wQeuDUU#pKa-=1`d!lOiUQK_d9J+kRY4v0u=sP04hd}4p7p` zqsqed~>JgRo)3$5GL{Kc`=7ud8l*g zbQ{6bzteoxyVlE+{Nl&XVNV}pgM|+HP+{!Uka)7=wo#4EXSI07QVG`+^S~nTarjM4kzYTlXqFZDyVw<|@E5jl;{JUU!w7XL#_gs>ZTUhPT2@D6>rj{&ktQ zRqDa=+|9%?*6;wWS(@i`adGdnOQL&AKKPix0!}hFI=kLZM9J>@I-+Q&lw)FY*sm7B7P`{Vf*LD zif`dkY<#Aw1`tqTp(jLZiD*ZNB#81=4u8Ge@|ZDw(tXg`5Je+@Hs4T2HiU$&Wi(N0 z#m%Z8&~RryUT4ZDww|`lhz;Et-kL+Y@WI)1lF^?iGozQM->OWt@0EU$o;m$(Sp-S^ z>MqW_+N)aAe*3kZz{}&A;{Re5!6IANKTimfvmI=1n&McftJM7-q$?yCSY>issN6Lk z1pyYUXSjv?$y)TA-hSSZo$8TUni#RL*+avg*-QP-`c1h!nJbiN(a>3L+aVLIA$^Cd z-@C^5o7Ya%7Z-xXAQ>G86>+PE?&ex!!rJm?e6BURFu?OmA1NDFX%Sej|9ULcafFvt zW&KEmPmkp1|6^9XQJ?&kqg*ktz( zezxKcC&>!9r8T%0LV?Xih=NwE^0~Okvrbu!vf|#B=9`idx|z4p8`ZdToz@jzAB(Z6 z6onKBr!OLSz8jtI*YkZ}i*G4*D~*z?37oW;{AIPUF=VCga2_hNMu3aINY0+h)37;c z?O9buHua@vCp)G$iPd(*_HT7a15S2Ha$N^8Ds)9Fw~PzZ(+mxk>$^?k?*=<=;sU#r zB0r`u_ar$I5fU;)WToEiLrS<8H%AM0AvxR*qK5v}R=;JErkmbhODe_`XB~Y=ag1w7 zbKE|SeNjMdTC!WI2kPrasCJu;{nO9OVwvi;ooieP00b%MJ{*mRc|BJo%4r+Q^_lMoZt{@grkR~7s0&c2?CQ>7U z(xuyobd)M!Kv6_QiXdG;dIu#yfKaxgBAw6zgn&p(2uYAC0g~TJz_R!DoOAD;dEc2k zbN$l^$+xcOc|L7%fK_p3?#0JR@IPA}n2XEZvrj=;YS2sUis6OMzQ?z|&YUi$=Xm$# zEH}Ke!$^IBtQUogC&6OfUsdx45;`y)z6EL$kP*TtUq6(L>AqL>KwM#OV`BpfN*S|9 z1I(xu$2yU`$>ZL}RJMNuyxVkfeyRo#$R&n95Xd_=e^>rIR@yK4RZ6q%IQ8Q9z2Y?(6;>H`CxU-OG~C z_#7VHZUK#)ko@*rk=Dnl0b?!JyAfOiLK2#9EQX!ql_LdlHh$9cZYy08?WFrEDdz3J zq?j+&oiFpE>Xadsp4%tUq)Kd9Av&KVB?!L{u)n2w3LUve7DRZi7Zkas% zfi*<6ZUAAaKzO9L^pin3lkEWRrUvBe7{<7MJ`GYe;fSYyL}@h6^dK&dZsg_sPRyN-D3F)wN5 zz8#T4Td=Lg#k$WLR*H~1*rh%U(W_t4r7&1b>!3>HiT#2y?a^W;SxEqaM(6PLzw`(v z_0?c#g+!!LOpRP_i?P!Xsm@w+=iUQ+A+=AAar*iAMx;CCWljbYKb&jQW)+T*K z0l5{TUc~j-v&Xe=(*c&P{~?vn+x8{y%Rrv}+-yWBZEH)%DN6l$cqrd?<(D2aN)L)~ zCD?MxphG>(Wr?1)G?pUAEEjcn9aH@)$m-gvrt)^yKHHtW)AKGwFL2;v?+$pX`YvH= z3C$v_tj*|!6e5Lea*Eq($^2R^V!;OG)KFAI+bS0tw;i+mxmk3z?!?sn1#wDLZ<7vc zjTP9I&ExW`#E!Yr+az83=^e4cl{L_m z*r!8I-Y!?;=?NWM2lLHP*aX69lRQ+)gNQewG#9+0N3tZotQ9 zXI$r#Up4wBFVHfUB1|4S4HWYM*8SiTO%;#{`^}DN@o+j~e0dk2`N|i6R=%##d&6>E zL7mrdkxbNBWfiPj>!Lmp(4y|54GwkTpN40f#VTJY(}iabIMhUia-xIS*2bMb6gc(f z5Ro(wJD;m|mGpZ#x5CS4LH$BR&wg7M+)b2cz1#|K))3%{r)m{u7LkjO`aP)HQE#HP7n%hF66;VV&ppm3Qt;Sc9{))fOKt>JAWW zv{CGGxlcblMTdvl!hNT`Xe&c&mW!c#r>1MX2(@bzYuPV!Ix$?FdLni5sD&`W_e#}L z%FWY^w?T{fHmrfZXqAb|y> znYlt*Py&}VPEPXUyVqPm`Bx6tTQ3B&?74((nH^KA3h4R_BQ0R7%o-#yXibu|Q4wr1Rpf;^b5w^mn z1G@_S)U3fTiPth7S3qt;B}6S@w<$s+o>aQO!#gWFs{-0XdavLKtrBmE^Ki!EaCV2x zenHmVq@L85tedj_B6IeP z{2JPuQ&$u+udz9N)r&GOPdwMjeJj*3xF{X3+MO>T>$~J%=C2rjoA+>fb3Hcg=}u@b z+I{09Jfg{nKMAYHz&l=kK0nV<3SOQol0Jv3oYB{c2r5Hc&DU(4W+Qjd@8@XTd_X)} z!+4cn9S=A1X!q`1fo?lKsJ3%&=&7y`k5DyqdFcUQaIsf$y)i@AfbSQq{4x}|6DbNN zg_;v<`KnhYiiZfii=R?%z%W++W4STI>=teQwI&O$0~#!^8YgN_F02KC3UR#qVuXTp zUy(DaGn{5$;+HRf#Zmo-kPF7-QH1Y(!UBL|=9e)TCKWq$D}+)Cd?`^U58KAPhelGDA->$btp zy-A6J1b*47T?fQ-=N9PcYOLEEdzNA>DlE|ff&`mZ^O`PyO2>A03chq8rTC4=os^22 zbky@1@7;5qAK?I_9NGyAa>Uu)n;TW{ld=m{ z+(X)w;mZS*JLqcpDJKz!ohq;#$Lt?--ncpY4LYJ-YCF z8Ly685OHd9pqhgrO%CK_6j)FkLNw&NI7{;OxtAX-y;V1U#ntZj;H(8q6@I(C$D7l; zqgaB*rXILZPi8#bn$QbfnZGCMp=lAsL3rR%^&}`@U$5Q4VxOs2+KJK-#_^sU)f|Y|aO8A+$nG9d5gZaD?UM-6nku$3;aArPEdhI5Bd}eIM zRX-&N2s>EO5IbC*uRJ?vUJ;uX!gxAIa{p=ODwwfy*9=^dG3FHka0xA?hmIVf2NHJd zCEX=*RnF5RHQvg2ORQm5r|t+*>x}25fn%xx|2CC+bUz$nJpcSaCH3pmUU@>XNW^1a zrnM?m1Tv3cNqk&;0spqpNBm8ib`Um4di&13nOPLj;+)k=eHbv7`g-virC!~B{+9_% zO>d?p`_Zm#ks`xa=kykbuNp*w+tb+VepQboO%%13aX+={kfNk!S9-6u>0*N@Q9&4z z53l0B1%J6C+%EZ}<-><-4~h8tcqKgHeymc2(Nc8KEh7H6H0Knhm(X(SIWDhCDEWRG z>nyeRh!|$;Sm4lBA9$wA)VLZF)LSa2*6vuAX6aCc&aIZX;+t!NNV&vGq32;oyWENl zy_icLSn*}RWlrRypH4XE|c}!u{lXcz36%!XniG(BzazmV~zI<4jm;WnjZ58nEvj zd4!|E{N`ZSW!{(pn|87J622?!RBQ(7#P@dF`k2-?iGu!oDj#DNXa0v4I_TRkZ{t@_ z0dfZBIJwD{``_SLu>fKN5KMvl>%)ncBnQtu+^x005Ow>tg}*uTukT(2^pXgKp@uy$ zKK}aolpPKbP0QH#1bO%qMY#fV$5JNSwrPJ17>kZR+l72QOL}bxR#*Y(r<~tS7o5Tw z0~(4Vi=xvmr?CiC4hHc^q855D{~CSUg836r>Dwxuz%=kNdm4bR2~DL=U99q^^y8ns z-3=n&i##2J5cR}gS}m85d4yvbNf$At2#f}WQvRDJQ{|vyitBt{pC|l z?D24AvUBsThMv|@N$2PNMkbZbT~3uVXT9W^mnKm32B2KEBLNXzd-$_27kXKf0s9S@ zVssbaOYyDo*JN!T_m9UWT6)FyI`<;76OBz>+fKt6LIeU9QvlQ@m2RyQ?Q*^&n-TRaQi+Q4mtLnggDuk# z8llyg7I(z!`?z@>xk^`cjG|qns5J;E>-&&SayM;FOe781l=t`^pL$i;I$GkE-M?Kp zPR3~}MuN%x^nZBje424GFHVjFR$Yfdl?9)RsKK(o*JvKK6<+B!Xic*&NY_6jpsuvm zt%E6LoI$$!pJbf#Ta67c2@7jYxUBKSvD^jI7Fu$9(bOa+r#iQ-#{{*YTIIjNz5F3R z7LmswU($|cklG;mQ=XhxTxVX(_*?WAc_@c-9yr0IHTMSs$uF>;bDwA|0Gbv*vG#Jy zObKj}y45*zAG*(W`a;_Ht)zHv0vH6T;-Q

u-Im-Jf+ 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 diff --git a/examples/platform/nxp/rt/rw61x/doc/images/mcux-sdk-download.PNG b/examples/platform/nxp/rt/rw61x/doc/images/mcux-sdk-download.PNG index c6afd18b6beb0b4de9655ee0d5e920dfb53ea294..f1e236a896e05c05ebf4548e652ec8092f4a3004 100644 GIT binary patch literal 127556 zcmeFYXIPWnwl!=)5UGMn2Ne~O-XS!VCej2Hq>D%|p?4Ay0gC8+RA<1_bPMEIp&ySMZM6~pu5I??ZSl%behjp z4K7@uG{11+qVQEpato#(W=sCK=w+bs^g`t@=LY!)h5ZwqCl@Z%#M7KuUm^ddc7JB# zb>RY&_g}Az3wAvI7cQI^YN|eY>2JA(r1HNp(@xwWEhJ|A?xUOx!ncR2;xBz<+_cE2 zx%n{=pYO+bJ5Z)w#?W=%y;GX{cb}MUj2V;s@X-BUZzz87T3+MR_nIHFltD@(I9)j} zJKr{mwG&B+^L_9+5gbgyLV^iHGABFn;K{?Sx{)UD4JIDYVIJett9L&B<86lr|L0eN zX=L=dpp$RD-NV>`&*>EwP`~b`l#PB}^0|R>@J=sFde#a9_%Lkx z=|7+US5H2jzhi3pG(D-Uh7_5MQ5!Nn(+Vjl5(OU(Sk({T{c|6fKjNM(-u<^{dOyCzGIpKX{NEZs(UUI&+zpNXw|kFWq&8R8AV2VJAEg-7B){Gs6)uEl4p z?qWuao-57Y@-1`?emiaCye;U^^4n5@@KK#iRZY{#SaD#xny=?h-EHJpAfFiVV#Ta+T#9d+<@*aA6OXo2E{sl)` zTkgB=V;_Y!6)G;V3<0DC)2mI_%GfN#i`SEpn zNdvRPH3W`l?}@QLn~w{NNxm5W<4#W+xM;@7rt2MTiMdL8{m4?|cJJVoPX_|;Od~|A>d?4DDkzg$DIu;k z^f}_OMl}8O4(UJaAAgE?_9pYAKelo0B-=XccwTo$pN7ckvR632eT^XFXvvS#4D3zy zndi7*)2H94=*`muzI3dFK26iO_mADYqv{}A>KNEzhDhKUqE?A^On3CUJND8N!<-cX zb9V$n;~FX>r-ND>i+9A|xbnRu3SWm}p6IotSH*9Nl^Z&{>Pa`~6+6|nH?)A+gw8H) zEF4R5OuzM)2P;kn`j=rR&xq6RALbXr=%o01hTs8Tut>XXqqzz#OvuxMFqnZ;P4;;v z%z^1|zw${vo~bFAv}CedJ=(v>S3cr4>84>MGd~veywBoqTlDiBwyYMN+wH z&OX;oFl)@=DbToPes(Npm8H}_1D+DEOj8CQ-pTe8-HmR&78HGHO3HNFfn-@H60c%JhTdIcd6jQm=-x#y!zxma{1$hA)hAKn>vV^ zc6^y;KcWCFj?22efRGj)##StIQOBVi%N&1&L;@3M3CW_ z2HbX>Ju?_^aUeRoq8uG%8tKeU8zP}Ey=o(fQ;rJiyJMYNOf4tx$$Rx9tgxm7xz&c6`@~y6@_HWqPE@4?K^p70Z9GHPxi$;x4w|UY4L~lj`aBy45;*^E!F4T_0C~U5eOdeqg^h zqSGM>I0>*J2c+QuF`ph#UoVAd48b>^a@P(`HwI5354K>rr^M>e0ptB{_O4`g)2As# z)rI=pIUuBGdYr>~B@Sf-_LKsW%1m*Rzc{uQKpV6*L))d-iL z*l76HSrh5AO`nD~+SYG!!e*#Z!Re6&4cSC^77Elcroi}o%pgLV!Jtywv=I?@Xo+a- z-P6enpH4sqaaYDj@3%ri`UDX5ujDEqPtf&5@b4spv>%4b~p zIaLq|(P+|4?2|$@4i&p@KSulk+0K)MGx{3%>pbKi`8aXT_=}I*S>SY93udLA+UFyV zC25_@5tm*1gGe|e9>PULJaF9%0-oZnilk$0rCg%vKl}sW_|w*35k^`5M%^`^1=p{v zHInUR>2gYtYlU<=q&OXta&u^|ok1GRJhVr2#Vq(#Rg^fvT#q_dqzW8~Qf85>uItnF z4YRN+jev-O9ILESDet3`)W+(Q^Mb55yVI5T3>`3(Ns9@2C^%Xkx0z z2r$=}3W4UR53Rxc#3$T8jJ~!S3X}#hq;F(2T16+&4z;;DDt9`zi!>C^0=%~skyZ5U zM3b3m+lDGE(G%EAJQe!HkdSP9>ijr1&Qr?c(>)p4K zigI#aA=-RI$KJB*AX;mXV2DVb$6PCRHv*az;Xyn$!5uu2tZ&5+`$4pac5zcrTnl9O z1?pj2({l&T_0xgv?4<(^JuTM(*RxY; zFlwyHE>DHLC#Q{_xe&=?Mrg?&M_iP=IzA4l_5?^DcIJndw z^Nxg`rjR0p9#NZvu5wGc>my~nkGQR)p9jIz3BEom^3YN)B%_OD@bs-8l0#+5$o!#& zHjyV2w_x(F7$UK#6;+GysES499@R5&HqB?8TOs(!h=K8`LTEbALk@;V#0WqH)UC(;c6? zAZt~Y@G-)5x-n*z?=sm^UD{WNfhA^jfl|}3ja`%-D!S1XRHZuyh4r;G_&psDMmb68 zM28eO24}dA&$qYC3zZE)=W~zFnPby|U{`cTQ0nQE|3=%~lc$$hk{)<(h`Q)&F7B#q zhg@Qr;*rsD;%KBz{bdgxS2_(FuaCm-=hR<&A(;+YF@q$TUAdj^8(;_yHTAVIaqWt` zHSuG>!C^oqXYFhNYBqf8kUTc2Fm&sT<;UvH0P;NHRQSfBqU$6~)F<2oXe~t3~+VIm|L?B6lK5A zTJLivfU7{XFX`+9m1P}`a{64yUCipu@g!#;LOMjILhnYsNCE$ijdj)hcJcHGh+Kd} zqh5Vl)vM7l&x{5OBLo&{j^1;fc|tl^{~dCfMTitgu&#ie7rBIAf%8t-5<$v1^RtHsMtDuj; zx`nMyIA%cF0S$m(>p$CF5q8ib?C<({yY9KM9p8|T_~Kq^C%}&5r~<9CnH}Lvelsa+ zzW#_F56SUm?5KyF+%m;7bK&q6x@VHz^&q_I2f2UXBW*JJF0)+Sg`jq^ysd)+W#n;< zD?VS$mvYfW^%)m8Fn|+B)h2}9iuK6;HqRFI1hYguzAA}5PjrLzm z4w5T$Frbv=5PDM*tLc=A*~5v~c#;T-M9#a(4*F^Z!-LO8RB)2dV~FBw$)w^6!}ij0 zl3${XA8ZAT?@%k4l6F|@_-*HpxxIKDFomzkXbeD5+%|=l1#x=^DZz;oTYpps4x9l? z4gh|qyd&zbPlJBvkDqZZC`&e<2l}x#IUsp%n5tw?4>-*wW3wd5DDof9>AFR8b&jW$ zdw>gk7MxeG*8T~c@>jqPRu3xK?QhNdd?0FvBDR@6Kau}0`1U{fcKn}fDY}*U>dyYV z)&G*+k@KOWAE4J|qJ{h}j+4^fzt9s^X1cr}+5g&~=iU{S!K1iHf7{0c1FsN-5oqGe zf9B=N^b8?dHOg6N0|SU}ce1)p-oP^@fPWOsF-`qM^l#L>anF5Byy~9>fvQ&g-slMo zG;D0I^k;-iA!B8P2im*F0}`?xV95Mm^Z+-%`}K4Ezk2e|hmybjKQ4js{}1A0MeGpQ zT2^xICUA~y@(M#hfL^D^K9!cOeX$%f%>^gLAZSsrQNIU`?N1>$emroYt+PU2rrB#v z+Anbg;wIe&^C0dYU^(WS3ljq{$#$kbsk@;W@LIQp7uQ-s5;A6zmf*WUr2g~v!WjM4 z_Cjs2#!;Kifb@gnqAyAwf(2+^wu~FSG3nxGzAJk3FE+gm(=4;dJwoyOw0FK*c~v+J zK20}}@o7#!E*W?MH`??+I|)j>@$Ore($_>WO>OPC!XKguG%%1#U2sEY#&fR356uJVHzEH<2t*MocVok?LzjS){215%h1&oEgq`Ojvg4QY5{dC$@hwqGQ^1@>}3HGk2GUF=8 zRekyGDX@%N$TBD1S61{%X@RamlZ;jO+Ay09@9miM?;$ z2+s4nifV!TAcluo!{yz>i%LqY-eW6BnT06|+VuD8bUc$Q3r6y1KxqnUKlV!r5pf|> ztoq4bW~E^UIhp(kHKJkq;C@Mpnkhl5WQiO$c(Af%fCqZP~B+H39KDM|Mi-1?%Z2QW#7L(MIop7*Ibzx-z`_q#1Nl|q}sJJ$r zRN^(Kh%z77lj2$R^ow+tr#veKs?xt;B5FUjGCM>0bXW_5BOx4(d3MODQI)#bNk+}W z$sA|%c3+4v+`oZ-6V~@$?NrZVcqn6r;}WOTGkv%LOv`63f;b~N@De4UH5GiGP9!hS z7qS7cdTEqSWXYd9u!1#^Jc;LSQW4K)>TwDG;=w-kYyHiYK_Re$=KQ5;Wp)4(Uy^m= z)DL@(9W=H~8kuP>e9?M5MP2Cx7~$xm*ngB9QTAxk8j?6R11z)Hbh_%1kO4`O!_WVT z*!|(VU*=W1Qku0_(miF6Xe@Q(g`KgMp(O_ZKV`eWj*Crbzez1-t@<&pV`Av5ea@Mj zy=91|Nf994G+R8U_M{J--cp))KG~6Lohu$2SQsv@0DgY0i1PSj1ciix`vsNb{jRRZ zDLd32=@*_>6ti=bIOkHbJI(&U$RsWKDVMD{Cg&O%GT$~|D=SMm+}pgO)DDWOf|7)+ z%t;N)A+}{w6zrQfAJZX>PG?>aBLaU+wUD4}L%!`(jFzSNsg22zO=$y2bxW2TN*|UG zQ7dW8%#WS~J24gx7tt;I5oAO`y&Gh!sigf}`?VMf|U(7<-T}iX2a7 zL~~5z0#`np^6YzcgYtz{PK&9-Z)|$U8dH>Z#poN0|AZcdfM`Tx|rLS$Cm)KnUW%AXP zcS67N-Cv0%+_D!7)TLy$|IW-Loym<1-rnDPv;F=e#UTleU5X2frPKdvohT&4>*}E- zhxzdnDXSAuVY?f_UMpQvHTAu!^llwAGczgayAc1I@h*OCD}|dpA~G?wvDzz)stE|VTOTPolAK2K?IdUZKPQOYhG-#YSluKZfvEgJc z>lXzMNa~*8?S|(8Kf@7xP41Zc3+>JEXq2G+@}9eRQLOz*y+=$|8vHj~>35@Iaem(H zD4VWSSwLoe&Di&tsCUbU4-E_xiOl(l#IN7=KJ&U72yT>meAkP)!v|K7+wXlRIK<~_ zOV_AFlk_W zH}dmEU*R_yug|-nYXR`R013j+v;?hI3oNM%LSQ%k#atA~JkMVxkzLkcKLStBFubQM@KVTgUVgKTpBZZcvgg)9&Q?Ed z-^{+5FOKhaR%b7aoc`MA@hBl>hS5?kJUfi!eq0#lP4M{cCj(3tN!0*?d7c(LO>2M4 zO+~C+NkFy1ef$>8WCf?7C$1dC?EC2;hF5MUTXvHNNY8PzH9y8D7LOG zv$mAPoL~jkEfPLF*%YPi+v6=1Px%@ewz2ENGB_wCDkawQyW^^t1czX`3-thdn1_qV zR#IT<4Rvc1_Q+Qz3dy}S{)zX|uUR84d|4l`b+eio!#|cXr%6hM+oTY^j`5@b;`DXl zc|dO~B%T0|wctENd#-Y&M|MP!Rc0Kdn|CkBXkZH z4nBwF*x0y6M%BnEAK`Nf$_nDsO0OuBHqJ*k?DcH0OCPnIu1=SBzAlW($CN-nve@UX z*=e5ZpR#evUt3#1u1nM^Uwm@9!7X`Jf+6TQj=iH}{SB>!&^pV_pNk)_mIqs`vn;Tj zFJ5dqz{c6Ur%_Ztuz7LvkT$b?{iaf?H9CK^M#i{^qvwq+C`j43g#r8I=^SgHZnkRt z9mTX}Z^~Cx$xHfQKLN265ksHftq#BRZ8sgvL?*CWN?am-X5E7GK2&ac>GdimP+qFJ z1ceV$b+VLw^i)z zze4ZDkDPgCe!jvMBb@W0I9{H19cRDq*Y;{h1RP+XA|FgY)9*w~DJ>Chi)j$G>w~ ze%PkSxxR`CZ_S_qEpQ+D5b{wy?)gr|qWj@G>ny5oUvtN+c_;u+&LkcH%{~Kl?+V1Z zy?uS;5TQ5n*4^aMQYrOd>Q_Az-A8f0$xX8IZ>(*@qg!j~p$n?E517rRLrY21qmEq{ z1|Knt^2hd82&P@mY1i0@P%8(tN7e|G9c{&1?XJ>r9AWv21I~FwJeH=#Kp{FFXtjmb zQ*jnt+c4FP-p=g46`E;`?sN&d9`3CnQ_E*<%SI8;+39soxIwm#k!^Qkep&WK%j|58@4R~g=FiS-%#o>bBDz1*D>lc2T+F7`(|*y|PA|vy zm6c=N=G~i)rZJV8x~&*!+{Mta<55`3ZWinM_|QbSw=KsyX7jD1!?WX$qS|{t)zV}3 zF6N@$RQ$Faeh;dFeh*@zx<*Tw8*jD5@bG+&ZI&}yJ$|Pbb?@;JbZC!`nrAknq*|%z zBWc;~sf{<2fi153@$6z(=*&;;q*1|&@g-N# zsZFv8z;jSk%EIi>_amvEwYUP~^9yZ3$BfCWp=k|r!fwOgS!`c4yRiERI31mY83y|w zFiv?9N@trto=wz=R&s|q+A`TC*|-a6<^pw@6l-nB@h4sj4~5fYtZhe%p#B^+NJLR{ ze99@72nnPZMfS%pCwEW7sTu*mL=wD!E_b z2$7w+;?|@66_qx^$`h9e*`3VDE0m|mjT`f^Ukf)V_Ap^(2?9_kvIgSWv9NGOW2G*u zompC%9v&oym zy8AWuPd?)@WR}=+EfSgy+2zW(I|AWx_KrDXwdiKsKLm%Ib=%|C<&Jcm0KqK>ayjBHwJdR5~L0dgHs32JC+~yalY7n7p%{dI8(F6%Hv2F(tbry&|BpLT}?S z`--1J^e!7lHuyl?xL%u&%-Kh-{!J`-Ri5SkMJ#>eq$0OcY5xtexx>o*+!)iu-$%oq zuWZWHEe@xolN3uwW}AF40Gacaa0E)nX872g$u7>mFWHg8F6K>d+T~O|*1>_C({#F4 znHv8QfE_1d@O{kd2;(^W$3_$@K5B`T!Zt2L6w_EMt<#+~6Zc?_XkFaf4f%dC!PxG5 zBA-j;jrE&D*t&~st9RCgeWBi0S%7{Dif!x2IvEv8y1sq{jhTc^`C!azW96K|{GCZ* zVH@0ZFKZz~f4%4}!U^kVV&$9TUqX8i*;Iy$nwyF#YOwoZZQIk-*UF;FxoYp!udK5W z(js=HAOQ3{#Xjc_pGfStUD_fM!MD?^N2xJ9m6Jl837L%A1Iwa6KJmIvT+(d=Q>N74 zrq5J_hsUE92hCbd>J%bTAny+llvQ6ihse(G6mRA!)@>N<`HjWOsuL%4Wou3{Yjp3m zcf=7>rbPBxsEYq_n(oPNsjh#se}0+G&k^te4JVg*b%V!ZvGS3eUD0ZjZ1Z+q&Kai- zFRu%d>*^;Ob+Ero`P*YcOzX{Q;e+34X*D+J=eCGcYey$__2*2u;YQXCwk14HcW&KZ zFh*x%4UsjaWpDGlkU4JJPFrN8b267k*w;5EHr~cN_y+y7`rfXey}AyyMNSC2J(Yl+ zjz}kb@~WP#Nuio!4C|Z~^B;eJnZ99i*w=WvvSi}QGz;7OAx_b4p3DYuK$=3dW+N*> z9StJ8vtXxdUhzWc&8af;_UUa}mi_j|5QCGeCrg4Dc!T8lrq!yJ>jcf6kN(TSL2enz zaZR3^x|ZXBcCuKCCr*In0Cn1368UQQjK;V=nap`C|Kj^cpq!GQxjM+%7Y)=~Y8}~a zI$mR0T%~jS!7$sjA9S{}@)BoW#cKn4lrWRxxi;Lt1!(vdppnhz>nIh33W`c!se5Q8 zpJMgkAotH706oY*eabAQmUw7+Qr2(tjRRitSu@jr=SnuJHT>AMY}VIPWLj-UcBQvw zdE57qUwOxyt``7}gN}Q(4504g<&_o95!~6{))+HTL?&X7VAIufJyW?gBSed@MpWRN zjR>w!)Rv+z+#@x)r}hxp{SIGQtTCh-7jmW)GSD}Ezh2%V!vE_lv(E)}G{eJ|hoqJ< z3w{LhSJWVX!RvNtax@TYGt))Qxu8f@_VH}5YWUAj7M?sAv8&PP6kD}-Q~lo19M`nV z?yTMswhD|wSp|HRS+MG+DK@UgwBHYQC+IA|Krxr`Lu;padJh~>!=xreY=r0cv-37q zfZ8#$`M0hw)weT|79Q?CUDIRSTJ}MMu2^?>ul~MYY!VzCf?HhG^>zJ|aCSp4FHQwc zHj8A2?7B?9UX?0)}%FiLF2e9g7f$2XT=ghFvm&44%WzQ-pd2!Wu&sSgL- zYa!N;&)rhlHkKS*^?1Nmfrf#?q#wED@hK#rMOO>0{i=V!${|OCT+2`R!_;r*Na8YfMU$h+!;g&0 z4py+5=L2|Bn8|+MjOhS5FWmV8-=AI`avtEeP*k=)7pmq8FT4+jbJ{?&JH$A(sUs zGql=Z4B1(bH#@|#*o$%B?3nMylxCX3Yg2q}%o(3o%}I(+4|Tqqf71wUN@51$S5N$? z{E4mInXteZE2PrUtQp_Y_Cd7Z@2p=nez!aiPU(8|XzxPq(xIrIq!!chzCFG%UgjB< z&N%Y3Ni-syp7-IS)Q-{Hm%24Z?GGPQhSS4R+C8t{%8 z0J#Mj+9Q_YO?9d=|bOSc-*aN@4 zfv7%y`H%c{5d;K*x%r?|)$LR|0|qu52%Y)tKDZUDg=MwlMs>p=D4dE)NZa&`T2tsf z&+Y&IB5+N^CQX8=Aparf?ps;nnqC^rUaHkf@#mb4{DzTq+!_<*`2BJQ^Z>zQ$Gy9I z=(3)k)ybtx>!>OoqvUB?&dH2BV=CJ@H?&8#SOv*M;nCp5Ro7IFgUWF4U#xv6DSkC4 zhqovPQ{QU_^JtSRxI`eIoF7IMKd+SE2JHxfFc5J2EU7Rz@eHGzwf2TeRopq9h$`Ui z#&@(4JMA@GEYBXB%2wWK-=W*mGsgPY3b^;_a!eol_X{9-RFA8$1+( zJD`V9#s0>=;fCEnUoPaxYCvq3Yn|MICSgOw!^uOXWZ@}RdRTLvEC|#4D54A)sEPHN z6e>ke_Q&6GKRQe-DsPGHmnDs*n5O(!C?=PM|5E$LcTOQtW6Cm26O&(G$Mb|*lrz_; zh5X7^{H&CwSj{@a(3)QJ`sMvE^`Kk*BGB#=L&S!J2y@+rjaY^>_vusHv#y6RWDdcw zsIFYI=R4pQB*5`?Vz0GfLX7PzrsIjkrG_Zhse29|*b~0V(t7cZ>M#^JZ@TX#0lcHQ z-dj)JE^i4~T*5u^Q|4(o3LI;1Wr=H?{{7o@tW4v=og*7Do%MK=<7c#lF0qHP9YV_K z4CmYOl&gGVfwpoOOJpQiUmPHII!cFE2?ha%=?@P5ThMrvds!{CJ!t9A>W8e@n~pc- zyrb|@M~PRp`)F+!hQkHtM00AxI5yI6KJ6n!KQt4IUbIzXEu>cwna)Yass($K)P|9O zE;3EFyMR^6Y!67>Y>q0oG9BD(aWN9qw4RxBR4wYa5v`TC;JviYiPM|8!>#%!h8N&P z+jzNcW7%ec@w@-*=&zv{gQUW*MoCZHbS_FoJEy(`P@gZJX_vz7 zJRK?1ZqB}Yxw0hrn}>@>zQx-sw+_aeb>cFLS;`G=EOz!=X=qinIN2K8@p|!l>=jp~ zb{S?Z<_Uu_t%AIm+c%NuTWSVSZ%OWiz~w}q4;K28Nv$dFqyr%`1{|K^kda)9{tM`B zTPTFY7-|p+yBaCYCp&C$6>@z?eh?|C(fUE&zobCBLTimq)cP+8aUP|!^{9o@kQuuJ zkAQ{0WHi*eLZz)iH{W)@`izoyKU#P1UfbQ66p}0SNh1@t*$V$d*peP8*&N%*!Wp7F zlv1zw_UGoX*xk7(t4rM}E5JGcSIx99dTYt7fe+Ytop}F^0c*6r-NTlECGTG}YRVb` zIX6EKCq9d*%}*N9Pkk3|W56Ei7Pp(u^CTsck@n_sxOM8(y*ID9BOL=S0q*N77=KTc zu>JmkQ!JkSV_^yv&TWCh8xvLBnzFmVrDrVD7Ob$dvv21fNNb_enwN7+b!~5H{bt0n z5o^cvUC|(s3T+EdL8hdu$3-7inDt2DNu?s@)xj~BCgsq6y6d6lP zw|lGKw_Tce0?{NJQM=n(3|00EMF0aCk?#ZF-_$~;Jr=`e*u)?g2s&U-Yw6O6He8)JDkm+ROK&{MyI~2Gw>-G^bn0%5$vUFlfrN zB|0*_4=C>!4mMX(vq0~WRo*^zjyyl{zM>1`OarZCIk`5;+qG>tcwlGiFspG%XJtrv zA~0BBQ*3o%j}TyikR(U#Ft6|`Wu=iLnziQg%wn=qWF=!Dy>=LUB1IP3S2c|M{yM&A zHvhDY;5*dZ*0`|>T_{(;&SgQ4cAgOAQOCqnx6`@$JM3|Hbo%3x`xAW;oY;C}(`h|~ zSXp5t;?`WF2WH=8{IzdnzuKLi_|JczqEMhT23p=17E0SRx%pc*-NwdV$K2q?k_;)3Ey7-U;zHf8sH&JuP_9lCm0fEqT7y+fc}JCpg(CM zUXFlZ2*9Fg+D>+=0`?PV+VY{6fGXPa_bn4t{?fdk#LQSkb01}OG|Tl=q zh(>Y5ltP$?hllsprxVLQ~_pwR?xB{8M7Pg=FIV zM8F;EX-Bjrx}9bB*f8bp*_vB=YbDSa{^8E);fsC@dTk-WJNtxwRB?s@G-vhVfk*F# z0@r3(XVa7Q2MpE|WhB|10}mrkB)qFTgH=w3p}&h&P=IOUoA?)9eIzUFc7_+{S{9-| z0CRA0cwVj}NG5IR*5hU9zhyJq?s)x#!4G=(^o9|$emy6vNem_FD>|gCD(7S-Ub`EW?@$={_ z$PlqqY_&mezAu2tgW3b%Vi&(Jp;2@)JbK#YLP@rV%gZZK;js<1oW^aJFI6}Cb->3$ zEl-2c%b4)!I+|?7d3U1)GdbBuE5~C&+P7DNlHD-}LPHfjlaZ^=2%g^f&Y4UTSagbV zOV0|*!fm@Z#azj+DerRSnJsh@fKpk%{g?7)r^$S2{8vkaZX?$I=m6+i-0U5E(5~Bb z#6k3Ou~%I2Q?_?i?1xko!KzxVwJ>oNaq?m8@*SprmQu7ZmN^GFOphw1h`G|Stw`c| zoD4a8&jX)In6r1iZ-lM+seG`z_+b?S(a9b41-RCaAXtS5VH`>&{gkf#(K|7Ej#6#C z9tEykmN{jGy0M)hWoV57fTZrsR^6?+nc}G$9UtQ1>~Z`J_T*A*(@Zs#u=WVOBFonU zzVXl63$nRy`)8-N_O)IVg4$S~>S+Y=zF?C`vXLEyuI?M8R!~|?kdT(1+gb-7eyl(= zjjoDo3BC{8P00W+iOigLB{_pvt7Oi)&MnWa&WeK1lvhrz+^ojh&alVlJ2Mz#$+OBq z!gz08llIlO{XtdLN4=+$u=;4XLd|mj5ao^NM`Q6@k!kUK2aRM15wx_r`Z)z6!p*&z z^ib*W*oRM4G28xhCleC?StJ1cSMqkRZd6Q!bcv|c z)YOSGV;;~!UCz#YUDn1bGPXD;Fyj2|*U0wXcIS9ka?S}X=MgG}&aD!6G6Ozk0RRBx z($iX6u{{68X(A3TEnxsZ`UO6xHMW=8;^cDo_3RvmKVdsyR!BT*A<{0wl88p?MSGY+ zmpXgEPV{*|`dP^FTWh=gMAA9A4yvEL^rP(@7!w<_fr9YBPI?VbmQ(bYIaxoJmdYug z{?fa;z8aksepC4ztlYT(kK_J&Ijse@ly~HpcwRX$C8TU=)uw$~ZexBtFk^mpc$WoC zTtU*eXmMr~N{`#sop%ekt_87jQ3$6;sG*9M zfPuMIry`wx6OD_pWI^Q{1vcX*2f9(SQzOWEE6Uvs1YK+F9UO$g{b^r~?oZ@S&TN(r{ zoSg95Rax{bhj92;#>)$bv@TUn{u-KPHns-_64^h&z9E}8j87!OT+_Jfa6zN;x;06RWBq6d|t<4Bg&!$lq^Bs1iY@y z{-c!a_8lTd*T9l}B*I4py!UwT0ezk){&}Cab{}s?*l@v#=)>sVT*+(bWmZ7(m2v)B zmV{d@I~R-n@xvYq(7GImT#wn-T7`V1Y!Hdj{><}6K7YID{28(~T%ju=z0nGqaG1wK?4#zbSaEz%lzeqY{!`Qv=Kryb_=tLUFaH zuPSPqmPTOZ-6)(=g-hk6tp&ZY8JbrrB|Yuy-+XB8L}bN6KetF*kChZy_kQvi*y|XPO}w92>FPZ2Y2W;k(`-7)bAb-ufm92-t3C+ z`VFGdQDnP*e@e<(+LgVO;@cHwHLQm+1KK?9xx>{E6KwdL(JTV{-DUA+LmaCa1fX zPsc)c_*avHnp9cZ;%^k@#c}=RvXs&JtPz4g?U&Qjw9TysD0-V3reZ8C)Zh^t+U7Dz zQDx4WDA9~4XZuHJJ8{QTm%j3hU#U|QY4yIh(6^Ee8%yKFqNCZQZNv`}x;N6;RQ7jg z69v=ZpSB+#AMExbkQ^cqH*)bZ(o%7%%%IKP+r#dK6K!^9h-jT?Uw#HJ{d2DViqt+F zZ|my@5CGvfRkO9OUjCNy8^lwonZ}v*I+uFq9}n5N+2svfXjN69Sq<*+%AUdWY})tv z8-Pe34Zc!k%7h4;B*~AXiLd98T(1NC<&D&8*2_jR`J;=p`WOL`#Cr{uUkI6hDiDzY z#DnWPi@VZP{+PJqBhe~Q(ACI8ZdSg%ZpjvgF!540Yp8bA^d81rUmCPGx zEiZWi|K-VVii4iF&o1B!D90omZ&dP}<{0hBr+bfpg8GI`pN6&XKH~lQ+E)3~Agj*H)z|9UpYBR3M&(kE(Q>jp ze`d-lW@5vd#-y{JsQR=|gUL!XbP4WvL-x+MFJfYx$>?laN zm(8<0M~->LqIpO*IifHq=%8y&9ejf~b6qFZF=*Se4lS~sABfPHD=j=}0d@nEr+1d4 z?uU|nIEmfza!5&4MrdTg%{Xb@?=jK5cU^8D_)b&U#ujSLiPA>j6|4Nz|NVhUca4*v z@Dy51M7N_hn6}Z@ruq?2G%-ey6aHY)Ao+u1fI_nEj%*^|TYaWzTTRa7fUkdCOa?=3 zNJo1aRy)&F{jXc?IS|8HcR{A~Ipzx81I5V`5*^esd_m@1=G)$cySYIZxVhbA|C?u5@gue)rC{M-hq4A8PCM z{tg!_AstB>9ne;+mA<)6dc=7qTmL@5m-O6a#6*_-A8Jpw4sE~RD}N*k)TN*=N| zNk&Wt@@dy4#Hq8=HG9exR=g;Ai|v`}a$^=Z1tau^tbfVC&Xt~2(TMia6L4mK{wq|Nch1X5RqqaY5cNIMvD9e8O(w4no?Uq=%=iMhsrSRH1 z>dI8rH#@3Ne@IYICzh7z+I%-4E;`w=uLLoqC29T~&#%gG#F%6Oa|Owqmi;nGFtx>b3B%|)3ze`Am(Muk z^`|Fm0gD|g3l+W79tX}sXrAO%;?bS6O``#Pv$QK1jW0&Slx~rMc|M-2$$_76hNBcn z_u4cvJ$>Ljyn<(WH@_y+cKSYlKOng7jhZ4z(%Gz}pLz%nU2Phdw6Yl;_ttjcxFzK@ z#yyswm(9gfM?TP5w&LW-(J)It)CUnVjPrSV!grKHAq)mQpF&ov75cF6^nC01OHqD6 zmp{Q&BRbwcJ;~-h1-jl0>-Ppo2te z%mXZfR~b9KcEx@&6da6FKB&qV`DRRSop7`7PX(b=sV2&m2O41vQXp4_@4YjU?iH}B zTRJpab$i`$e3_*A+Te+=g`5bF$#9ibrXG+-tGq(c-J_--fuIBL=aJJmSy{lFwLcxX z^{)q2TE^u4MR_$e#6X}s1uVujfrLlNm5=QA=b9(E<|B-^Zj)2A_ymm<1DFFq_h7oI zwPmcz-fD{)uc92lsP4$i`dTi^8ePB&xWh^KxxjfeWO8Zk zowX5r)Jp1Y?Sf;pOr;1<2qeSmJfwK_5exaG2YOg(0qXV9>vPIkH3La3WK4@K2K0ng z&z|;)h5Pwyqb?uXn)_wPZS+le zoGIL#jV)eIT(Sw=8Bhi9p#!T4`zkI(0f<|@c<0nV(1j;EL-flqT>Pa)Kf`ycU#!~9Bm&_OM_ZBg5!uw~ z9ft|mf3EzLiUCGFL27gEFqq{jhn8>U6V&{t;lL4Vhd0mGgtJ6N z$GfLz!a5DVjp}i~w{~JQ^cC*BCvYPn=BuDmb+`{c`@4oI1=UEF-BOfmBCI+Y%c3jh z=5|LU_*goeD!J|;DMVH;Di^nZIv5f_B?3C(K4`w~a=LAMx39e)&)RFR>$=|i>v_`Fh~ej7XhqeEs>i+J7F;P^UHykOG=*)I zuLUegeL9e;;n&`o34DJV8pMTl=qnrse#@6xiQKM?%;qr^3TMf}v)wIWo}9M1=fCtm zBx{@K03wXE@M?^_V&_JwH4nO=&VSpJF6OS!uB(F5!*;rR{8n3>!=0Or*SBkM*F~!( z44JT`IYol2LkhS`qhy=uGk?Ak3LYDRF|z@lSm?&p1~QazB>j6o1(FhdZ?hn=5+UKP zuFfnwB&oZfdBQ4B&9xol(&}o;c@7x@O0=XgWF#jvSd(OFpzP--g1_^<=^MerE*R>W zkD&>exqW*-T|C}w3+&|1U+^&Hv{5}_gs5O#c?-nXwS;~rOA{rYqNo^>^staUH&Ayl zh&0{T0z|8-@Y3y=Z3wcUu02k)Y)gYv@S&sq?_88I3lDE!TmmT&KNxsY+r1f1Ud7RG z$5_H*X6R?MO%uhU&O0$%eRuhrPnI_Zt`3nQ{8XQ>Q45Mqb#{m#ROYp>_J_7jcE%-E zwRYakLEmIEw4!rn^>|r@-+UG(Ups2p(IlruqGY%bC@E}f20NRrJek66#&-yZ7Cz-I zo)^6EL&58_n~tp1V#0m==gEKj|13_8oA4mH2#UUYaicgRa=O8tNbphwAslQg)_%K5 zg`&3*NF8<@Q)y3cIJ^I-O7Rj^i=jF=4$hzrJko|dfG<#F5?-7Ur% zN9#=&bwYA2JeL0aJH}kR4Hy==>f`Osz=aDa+!@|YN!+esQt&n7Um%uy91_2hOOe_k zzYC6kzl)4!2RsRu7}vv;&D}3S6-n%5Eb>v5tGIHO-m+#4TWOTRh}sjg-xlj!r4-fs zU}+8@V}94m-SP>nQe`GT1TQ61G`;(j5A?tiPuTYjp_VG$)m^KHcC`7?fG2%T&QR@w z(Cz5Qo*H1Q z4&N46wURWGM3lDw6RWp&it{~!xWoSf>BINU)cD&R(|k(#dt~M3cBj6a;8U2dApOk; zcOQQj?x1lNEU6mam!0R#ZA2lHLISDPcXSiO7?Mnu^aHx@_E@K`N~u+CH5oVHO;kq0 z$$_w>{^I_xe~z4JEtM?3xZr`e35WCr5+2KB%s80k{GsZ8s~=q&WG6LQDk9H4_cB>* zeYgLiz;OW2mM_4Y7T|SLVe^hI=)}(D^L~yzJSm8sE8|XiCz%%_MiMc`o&-aY!@54U zm^^cwX4^5sBn~=1(+%0?eN1>7v^h+-Pa1(Mt?5?!TAw;RSzK#QN-X^doCRi_fA`w7 z6h)gYtX@?LH3l6SDsl~ztIWi_!q#qC?t(>AAWa*o2EyVQl_-OPZXX}|&h;Pe4U53n z_s^lS`f|;s6U8zp1yv-`P#lz`(9oq$G^oYB#g;y=f}S!Pl4bFNx>BP$bN?{L>G@$* z;poKSk5m{tD|`2ioye~6EtSIxJ_FS!D9O zSnny@CNSv?URSVi+BL9Z?&_gr)vdz5NQVd$lb+9STWk8_1Z?+yv~~8Gq8vLgj(qW(UP3@l3;oI)QTzT9+x(Rpy6n<4Mp^DBh3&s zC!&FEc?I2}R8_@+Cf@Atlk!-xxTc@J7+Y6NrV6kJ3Gp!UG}1=zf$%TKJbbEGiU~v$gI!IAr%AbRNXAZ zvX#sQ~?nnin zdi8KRen5>^Jd>IoXf}%u^?93#yracBhF+-nwnxl$t;R~3a$XK#|K##T^|}M^B}wPj zgmRe20f<5ha%iCTqs&-I=78PMf(%lqpDb5e#!lUCFWV(hr59v}+P)^H^4^|zHX&%< zb%Q=YL&L+~K%)4hv}FBG#wAPiB~@d#(1vJ=0JOweZW7`}uBo4*-sXpMcwG&-+1M>J z6it$H$TmIb&{IL4s!WMDHn{fYv+=q|-hVZ{=8#SOjOys`l@*C-ca$>v>?eba!C91y z`3`qNo|IAZogu%JY+}&IZ5lI3#$Do!vSo=heyeEX7+Z&3$ZAJas#-=2ufZ@5Wi-G<=S& z-gpih_EPyWC~}JE1{tBU7w3t0Mh>>~Uu;@F?q=X^N+=zww5EH)9d1t`lp^=smYkWp zk4(nr#|6vO>XF{j?XxGYdW|PX*0MJ1lY$*(l}+9I>>}Od!u{kWDQn)ya)uFiJ?q>d zwEUVm{VxD7Qye&K;KImtIPN97b~M%f@n1i-?pF7)G5u#80nhJT@4esWS!1cC|J`xN zOZ|c^cY-Dz{ez=I(_6Y=#s{(N(20bA+!zn;q*D!^*jrYu1>`@ul6-hf75%(Ex#?c% zTZPOQN9RR0dFoBA;&_ZZpFL1QgmXEX&)i$?9+Ju7{;6w+BvVb+vAkb8?H3n?++(TW zH{-lVIjS#H2G{>qKPPR5%*?kgnwHi?c2$crGKS9U>OxNUd%m+^>|x)4PoN=}WP~4N z?Kpap%J!0_^Nq8DosPdKRS=MI>kFNES#jbc9azI}EQcRBx76dqXMTX(gP>+ntO=kp z%IK3q6EyY_CNC#^wPDPU2D*|ti&$6i|c3R zDY2Ag^ahL|46TC}_c2;~6ONvmY^4!Ells3k~VUK;GsEdLrQ$OzAlQ!GjB0qlmDrdL;<-o7ie&qfHE z8)6EKSx!!}Zd&Yc+shC~L_*2<#4sOzd}>Q#ApDeFYMY}5=f{E3DlVv4^$2dut|wB3 zK;(ftD@EPZW`fDp>(Dt#`2buU+{GYA^pI8w` zD^6sJzT$+c>Y}k&*P?=})y`ujTMpMC-Fy0Hk)6p3UgC0w(GpE);~ivmHbAk0`#A*3 zDZsK{?ivlheOrvE{y8x6kBGDWKy0+xr&#-zdk-#J2dremP@YOwf-_WI^o7NZgG{$e`prQ-zqo5vXR{~5Q`%; z_LDv<_w2YBm4ANRU2|LyVINtkgI19mZB;plZ@^T?wha04J14(jT}aKJFY=FeH>R$2 z3P-xH&PN+X(V&$DO{ii^bMgQ>1DUagH_v`4uFs3qaF_EFPW65qsYnz~W?&wsj+XQk zl~wS(hk^HgqsIvf!A^yYwi9fzc@-b94Y4rkc*nv(?9c&WCd!5mE^Fh3(bcug zceVUUmB*MES;>++_d=KI;xxAiUD2ddNhXQF+`A$Vt!TL#=9gKM%vjLrWBj15-nRlo z7nz@wF61^ev%mT4(pI-l=vV}sCJ+Yr`W4o9yaq-VfJ(z*pYN)tm$F^62&8Y)oo-VY zCz`z|i*?`4@5+EvXhO`@=ouA!qrdiYsYw+;KVsQH_A~K+B?}mt+$?c^J&3%h^aZ1f z79YryyE!QX2<~K81)tuWwb{9>>>_}1`YdSg^d(Cf~H%upJ z93mOj^j6fLe_*NWh4LT<8<9Y`raqsYH73Uits4?>T+>gPiLYHd0mDxy+5Y_hPV*G* z_FRItZ{zX;=E*5GOzekidwlnn3MjN@wW8o;u_}v>%Wzr3*21WtGIyzo(h$A0V6Y7B z4!pu%nO1g2zC~5Jr^p)Ex4TVPu4H3C8JWjz&{+?wWhKjkqbilhJHlg`%)t0IH&x~j z&yCY!-QmFtudp(N(~LB?_W50D-HL_)DRKqcf>2Gl=JTu5wN2b^A=sRMThv)_PwI9U z_&aH+pCA3bIR+q^X&Lb{F(6MWhHuCa`p>L!x()(ecsuJvINA7E(GN7)TEUzny zMtqijj3>yJ^ex{c(59D6xJ&}w9kF|mq+#G;$%8qk%~Q!s(C&mXK_LKm`T^eCjp@y$ zfAa*@xg-+ahC%_C@cg9ut^IE1Sc0OHIOp^59PRhhQNU2-{^jRg!e{n!lij>y`Z?|T z>!%b)ty#rxb(Rt?bz~$lPdyv#i6v`2pWi`FBF(k^^9{H57(HZx7pLRqO_;IJ$B7-- zC1>h4hw1liAvlGDnM&q{iTlG_Tv|p*C%0+_M?HX9;S2B&sES_ld<5 z@n3CWWMvI*lHTC8wJB)0|Me6CScHH)j2MH;WeIDJn1&SD(pqd;6J`-VI|LK^@71r5 zR(ggAIcwe5RIX1JhE5Dc=8NWAokiN+PfZ46?2gn|71z&E{=hs(8&6!?zsB`7B!=od z4c~l7B@u7^1>um zM{8wfz+Wc3(n++#dC@@beQ8=R!i3lQ&^Xwu^;*3ZYu!-34=hbLH+L>#81P=KWR!p$ zTiZFR`bzxY{adt1X%ocn@RI*pE0v6VvhmPaM^At2&@G(NwC=^65yLBI3TJVGEoyVSjbJy zb7f5=lal`tYkIxxb6PPgL1g?VFYTC}^KIwl+cl|7iH3-x_SxK=X~NFBr#)^f<~9vk zlTUb|Dw=XXDPtB@iF<@lp{V0PdoAtB0$5;jQN$PS38^-p)aZJe641NRK0}=#&wdPIQ`oZc zJLVX*YKLzpC>mIcDP6&2ID8AtTmBsuVjafns_LApIxJqNG#$+roPWxtM$BnYPGf}{*nI`a=&_pyroN_Cj-UXh| zBH9tk*RJKfnIu-Z(h0Uai z!km2H1eMr#1Tr6QCjaWLqWHWafI~EEI)x+Q<$fYbu%sFJ(uC%*K4EYA z`_>-OB;oJ7=YeE5j|S)0Rk_Xsj@fX^fp7O87*DFF4A@QMNJq>2mH;5}^ko>I*^H+1 z9|2D1y^v#DkF9^kkj?~6CtlgIzS?wx;tn*3+9clco>plyi^f3N#wSlo3r~E(TXAZ(I&}{0(|Lnih z@VLk-@F+pwOr{EK4Z&Eg78lfSUpG7c78Kps`-VwGpVF?%(Yb!GZy`X!pDkdR@HFxw z^RM3UAAh9RYr)y>@BN*FvGd81%yBd2tyU*hK=KB1Sa=qDb_~^w&azQdanilBlW6WN zYfyeV{8Dv+fuvtcez*J=hv{E@O40Jj(@T#2bw)!6D#S6M`yZT zl&*FMG&*GWRsrQ%BuG~3Q7@;F9-WIcQhF{|W3iA5o1taFXd@m*kfR?pTG3f5>lK^7 z6FT3Z+j!C2m!0X-SQUg5+6VS}fRMZ2ae&{!xe{)IJ75$tKK_`|V#70v$%Mx=&0Ex~ z0)NxjjpzcC<|A)MrgLSnZ4+Jjt3T(wDwxirCF!1wp!7J_G!lPc)x@+BydI4lS!{n< zaVcpl+7M}2bG*^N#PrKh0A=YCztZoJx%y6Ckc7eUHmIRRiy@)Qi6dclwD91U-cEN% z8f5$>jzj9eR5I_jmi0rC;S#$dK6)Kb4O^q+Lk#p$lq|jO*LX5l9I$}S91P` zmEXwYRn~|JLX%ZE3AD$u7VBjLx;jXyhVSk|rP5p+$%1Sc>r5U@8EevI$1hpTDoahD z&vxs8(3#2%N#%~6#tnXRFcFQR;@yERaLGUpciY>oOsTgFh_7$1e@n_xe_tdvO|=%` zZba{GwAS}MWFbB5sy$J5N_zo!L~%3GHHOhq?~PXLJC7Y%uq`;Mq?r_%K@`-}g;?4} zN1xM~nu7y6tFW_Ass$I9;KUpfs5FMZ^P4ux*>)Es9H`n=vNx7P8ajMTD6SfHE4?dR zw~q9?9{*78a{4b>|If}`qe!XYfL~Trz|Llqaa{pG^8X%9{ui<=t;_gd95u~b+q*TM ziwOAQ8e?T^4#|sje)gXN`C&u*(7)lz19X4sBVIjqx^ z50~qWj*W;fIFWmANbagS5d4(@S;&CH!`zaBu(Z~9IJ3#Adg(l@?m#pQ6uz;~bu21{ zHl9F3R#UFZC^^;6BpqE*L-14tjp8rp8~!Yzfe6LZKfUcW+*T@N$W3X3iwxBv zuC4j_3}mzgRV(%W5Dcxo|Jp_zJG=ZrsYc7!Pw8cZO>x8S zO3M7#`?n}0W)tR{cn0ww#+!M;1C`c~J}V%;aXKI{+(^s^Ku!VYgm{Ba$~JwrbfU-dXrzfH4(68XESUBNPSC7uCvVIo}Joz8!_2t8+D{?gdVuk}`qHy$BlI ztMc?MuttUAE=N@R83*u-Cbjb06*>Ez< zG+hj8#G}4v%=2QcUi!n1Ilc&I%6e;B>TVb~o3zaKuOPx%AB(q{L}SOn#~44%-u^X@ z@rwM&B`{mU`}>go?uI5Pxg&_}muM|PL%FoMVUiW`faX~$TTV2T?$=UXa5*;S{6z)i z78$#2cB4IMV6w5V*)ir%COqwi#WWlDa&iVjI8V)fKLW9!1x}nXeHF~I&h0{n@j70~ zF$4MB=_eZbb=*9CE5n%HA|kfHs@ghrhj$jytz~55Myt2?9uP|#L=Lj;oj)DxBa(o- ztG;Q@q#Rb%T?@R=1cUr1oAVWx6x79@*XA+EXiga>A}Fb*)n!A`^H(wQ-j-KwFt4hd z5!3WltCO`k1_{-q5xx@W>OeR=1~FUy4PpDQ(Bgwp!0-xQq3d|QHGWfRUQ}`g zc6_~S%+U(}7cVOC_!VXP9r<9H5<%`N$=3Pww}S7HsOp3g_}qXs^)cIf=)VSjyd zP{Oq8P7zNoqtp>H2taR2lH=a3cvIHA%rATFvEK5Nv402EG2a-Wle5cD#8AMucfOwdZ6)93;?kQr=JZ*{`gl$!#&a-VFmyzk8Ny6h| z2rW!EIJj{rcDs669^)Y0%}jZE4x;os#iCH5>aP&Y)-7q?5Od~M;T!rrGQ5+7Yt&yv zA9`EHa^z#h_AC?UQ5f@ca+wI1lM^_CtnpfPaed}Qns?6523c8u&aV66lbH@(aoy?n zJ$Ls6yZ*j0JPw&#k70{JiEP~>V?uo2S{-8=cIj=A70k5KOp|S@?;Bm5P~|fqjFZy| z>|Q}`FbW+1eWAb#WA?NpRFjEw$mu=Oy7jY5d;R=GJ+!P(-^!1o&TaSjn?;vafFk7e z1d+=!Hunif^VOd_k&KslJ3TTGq4c|*lP{@F1wXfu4*-}WEC)x=j?p;ELmdm9yj+D4 zvobcHX6_G?ngSH3?nfQ?|7(N1lSOffbI=penNiCx@Uv$$`|(H~3zITDBLy>v5w@Pr z4_(!msvV6TsAYz7c!FK3q6#$B1XoDR;_BV_`$%>O1qBl7iKlcuZ84upRCf+~ zz~#nxjZ-Ba<~; z)hfL$yvZ9E7ileD$w?LpxPDPec29o3hhdrjS%1cgcum{&HpB?!esgIhQf6uPe67!5 zd>B`7bt$p?2xacloJ78D|w^RMS+l@d>Ti`=j@Iqix)1_@pkEBDuNXOqH-XTW>$EQ)g-a%;-D~qVk zR#0+rsNj6smhEX^K;1hOje-d(o^L8-QWL}2Fd$P@uNWK4VjI%`6yWX0JH%E(C`6aS z_aNYSfRLg@!j**dlk0AR=5#Oa@#_yP|2$SJQ+nPp_cWZ>awyV6=3Z(Y84iUQcfun% zY3MDj_~awud)gq3!%1l+C4?P`{j-J{KP9;5QaQGt+wC8Vt(Q)tuW4&(v++ZQT5$fns{#g5L!|Z!d~gg? z!<;-(Ly6;1M^wzG=c#ngLIlc)hcqO*9FjjWmf8 zW|c}z`h3c9YIa+f)zbBS_q7M}YJfF1GC(xVvz8S4`lqL4L(f!F89@_D$4F!?_Cy@=I;uNcCVe#<)|ZCz%20@5i9XUuCj9?Q?)Dt~xa8 zrF1T}pH9)=OQ@gP<-;*3;e~#&DTq~(P>YD@6=g>qE~f{wZ8>p3)n<9fV}Ce~L@7Ca zb%OOzVqrQv14l;qHX=h>$+D^KZ|};%Q7p!a|M@&mKjh-co~0>gGttyyn%#VV=c(g% z^YF2F2IZW=@ooc|d;_(3U5J#EsKEN+Z%|f70WQ8IY#MY?m&#}7HxM%mT0>K>a~Jqq zmnpd~)xT4neJB)-7UU3h%9kAS7Tc|)^19+lNg~>3t6?=~2MdHiUoaus78;&{; zKxV34x6LZkhx$Xzu`rK@_kur#4u&CDNkJ+h+=en(NWsZv2t!Ihgb1lDY&(mhX%?Ji zW{<{`-u&x7I+FMit&3?*#v_oSau=|-6B^&{`eJF-8$aVL098xX)d+}2)1}eJ&VubKrs3cIcesziR^ipKKmGBC^ zq?3>1_G0cF#Oj571*laF^Sy)a+_4^@+!2!~h_SOK>}3-7w3s)f`TO8Pw3Pd}=w3sv zg!nzdQ48&OeVKT)b6VgG3YFb_JlUFTg~@VquMzlrW9*W|V-t1!qHw8ZR_c`lX#=*c z1s^W~zgJEpLM_>W2+Tjfpk{Z64`f1|xH+C=E-Y9@_v?DsozBp_nFKD**L|YS^jTA( z5IwvI@9i&-#nC11urJry%ZOFNc0O>Nl{}C*<4~v7Q>fiY45|yJ#7T4X9M}7RnY&@veu+7 zL>L2xadZBSb4QCb0|@wnHEl@Nk+W9+a%Sc<2#ahlwy}ZzqN!?gSIrN(MhPD!lXQUt zMtKO)AX<1KDzhIe+2Y4Jp+9r!U$!qh@^^HE39N}KZ;vtvPBS*FPpdR_)F$C_b`Mcl zUTZW$K%sD#qK8?h+vB-;7c<;3niaW#dY=?H2>GuD>&E4J{?jFC1-v(R+S`>iNhF3E zHPv@Sne0^R|9T8*EF9yI6_B8$;Wrv?ZyQo|Bf@Vy+~1OZhCs!f4}vt#cZ zX0R+yb$SArW z!c-M%71(Ns&n@ENk|TUTzw-dr53od+tDN3#TE^BRcVOSyJPL14LFb=Menh%M=Gwa7 z{0GjCdQ}Sf2uBzyhlCD6izu4)IycaQ`2~_MQ_BUL>`$~`Rk`&NG%>Z1o`JTyL}29} z2ETXF2t89;bQCVZ8RTLjiVW1bJkJNQ#1?qNbjsao@+}Y_ip`4STfJD@TIuQ#P}B2z ztB&i5C6A(4kQAC|S0@4#5>jrbZ3Bho>Rhh}1?Y9%!EpUc!YRp%vtsZwW);6<+p?By4^(GLiC0u*co4nnH+g>kq(&Y8H zlEegoHy23Z@S>M5ARc1zdH~LK*uiX@BrI$eGSTjWMS**q>vutJQLP^i`;~nQ!`fNV z#MC94&gx@GC=I8IF?XSfN_gOMvEhHZR<;=LG6<#OC>YE8;6(*57(4-b9jA|sQ+Won z5A`sx5#5wa8zh=r(ijBD8!G5`w{wCmJA}oP_NzPtS z-AK18w9gE(0n>Z0ibby#zP5Jbu!Qe;{$|-ZF_2%3XZ0G(iZYT1lpPk=JzR>i*^T2ckK7pLYsv- zrKT`4PxXV<@TAT|b#GH*&5z>W%u=nu&-*fTJ(WCt!D8DKmfU7;98x>GR@BSY8iH@K zkeQ)+w;9bNkmJxYO7ATq{3*tZff|)s^DRpL znrhS4av`Oa`rw~%`CAJ)e14YrAAx64LrP}qqo}>y31`eu%Oj|DQ_kveod7P6_Z6kv z>DsT$bSw*dgAZR6LC)E>_~U9wO_>N*kiC6j#bb_PwkaWFuRdC}&! zP18vSrco$ToBT1KETSxj6(8nnoylHR6hnmE@Th|x#+gh}SLyD-{FM|86_y%ZUl}8t zs&lRXDnxyitGoTfSd1v&gc)ZQmsSTBjd2zjcmIP3dPWJm1WSKa(cb`opa%9Vo1gc*)(Eedn1;ck>K;e$;SftVl_hn1%u);AHNp4ub z#X&9!3rr6QX0w)Kjqgw^*$6;2+27=K2^bI9+o7gT83L~=4(jGV$M&>wll9dLQetij z`re^fYq94#g0Tyj7zqf)ppU&QwjO>$A+1~UNWP)PkwM@0^UsK&ZCA!vX-?mNxx#r! zx15UKYyJ?msMIv6kFJbz#zH+O1#E)1y_Bo>>&p2%+h5PMIqMGB$=zrS$~`%Te^9aQ zX$XhbZcg+E;}+JMi9!Zq%r56&2L_^zOK@fvI8a$$wY@r8ZBH)}A*plgqbyEgOb`}} zT|aZ8tYF`D(G{kPx>`_6Q{H{S0Z`pjetbxh_KlZ&ERl(NHf41(R$)3DChPa-3+k^a zPPp6t3Tz17$Rv$+P~oK}1J5H-L>W}0b&(Eg9cHXy#><28SFS)$r}p~lzO)uFiRaS6 zzL*^)6Gz9u<_zCwhitjAACAje%V&-^{je#j!Dl}ugHI!=nn{UOsgeg!J>-IfWvMHw zF|4V6tH>8w{1!vSUia77Ccpo@55=YG50C2ukP-a)_S{93QlimWfirdVQ7hXsAcT$d z&jgM5V6hR94ipmH>_{R|BBqOtKP0!M(n z;7IbWc^%!Y-zKpUb?=OQuZm`Uwf?u8A~i4a<%?EigQKoC>V)2(j_B8H%q(RXNuiH> z-{N`UQc1t>HhSY91;lbXckP)(yeP4!_P99-(ylr2xH*W{cbA~-6m!~Jp<0S47}uoX zGA0Rv=!%|%IL))wAl(Rp(=o6g%;XK3jtHBB7+B%49#r0*h+|59kB2R2HL`(=Jz^lA zp1J`_uT&8a;)v;~6(p!w{%DakKX5LVF_dmNdf=Op)@jDkpp5{_K2dUGPU^N;(v{il zjEixSb+Y8`DN_g7%hsNgE6p9nxeQT6NJ;X76o>x+qe{MR(H7U}{k$(2n_^=kE9v+c zN*evn+KqKWg9((+`l`F0zD1TK$<8PGCFHc>3hXh zVl2|LL#Hq3=)#F^Ec$-8hhy*Z(nQ0Y`hjuqs}q=Idm{*hxgW6+yxQN9A-4yk%=gn) z+O*W{_tR2-+->YH+ZZP0`S^E5J*pvSNTeeQ=6#^+VM@pNYGZy!j^`$1cftKyX7%p@ zGA`j6kT%Fa&c_p;+6qHeMfB?~3>@^G-gLHPrHLeOBoPF5MicK`U#IHnZ19ybb;0ko zDD{UkQdZ4RdaiUVLTi=E{;noG2vv`WnCl8DB2UWh^A)t{*Jje=CbChK`5mXKYh6a4 zXLq>m$VL-)rx`TzeOEm!A~V8 zhgh12CABs(I;sVc*eJ5u61nj=2`-A5nA-Mh8~f$Cb`$?6#a#5ifC#AR6?#N-HO9N6 zSxb~6Zm7O1)v0N^o9lbfs%WZE;Wiy|3b2Ef+w(pjl_*CwBKsQ#$5JVOj6_K%saA4N=A)0I zLu#mGsYw8Li{p*$DEC2BP7(V-3x(i4i--$4kJ~(Hyv>Oq z%>;Si$GqpC1_^k_gp`mU1^RK8Ur=x1VuoqFL)1iN-9Qul?s?`W+z54M|PGr`g^9)1L2B4haQQ zPefJ^t?&jY&daq*KUIhK4_mLROFgD&L}(a6#4{AGX|%!mcs0rV6nt7Ela9@op9h&5<^YHj})^4pgIR&Tg>bH?ab*k#(qE(c>!LK)bj{BrP$H74W zYSCb@WDpsjDc;ll-6x|A#r>Vu)SamOlW7uuJ6#yMv0-mH7up5f!-q9t-$)2&7;5&I zWJe*+iF?ue+J{LFY;O(CszN@%CPsBC-Y0|fTMhIQh}cjlubWy}kB}PsRJ;ED?+XiQ zGwLc2z+B$wKO-IAU&df#W6R7ktgEkm`}H#ywMrA#3B}LSTtAQYrFcq#cyY0g*MDw) z&JLhC^EfL-v;Vm~j2U|G-xhpbc-Ydjw-d6?<;q*UJ)x7r^r_Vy8;K*XJ%Wje3OK!W zWpW3Zt6^UkVMHzQMz$H+q;)&+QNzxV{_i{uqG%^8(R=Ild=EFDw^>^cQw)Ot^dU^Zn{*6p$)n{u1o5@reBizlL$#bz`& zCE6MYOUNYZEsaA7FH;%hlYsWl@TBU*NohU-wP+T5X0>q$KBay$^aJo%?lxGOiY{&S zrRoWiKmD@bi@IRC&oT&H8>4?z7kA3h!gFp-Pvx}6c=(aoKUNZdr74z8j6r0pue7pX zHLpY->MxH}&1&*J_LUTFs(O(r_k=~%sB+VZiYNx~3h#Bce`MyBv*7b#gBj6I?c+}^ zm&h6YijagZH^#CI%dKR5-TKDu|0yy88 zNT}ec0;^M6>r4z9Y$D|LrS;o?H=j-cv9k+lZ6)J}iM z4BK5B{>N3ZzX16%^uL@U;yaF)zzYF(ha$ zOUR~GE%mLL?>f2d@{J>Gb`r~*;>sFTd-?IP7VBV`r=SfY=*7iP7_?`U80;#~>!;{E zfdB+($}-O)Wl#lNvo$$~hPydVaH?3mnk&q#CRJ9;m(Q&ge~(1QR0p~1_M`WLO#oDI z-gz$(coj*=A50-8vYi+1M#v^|v=uLev^8M^E%-;O;hW2gMX5@Zd7e65 z_qOrxidYKA*e#`PndyRFo?STh{aF~*(E(_DPk=_(Q;(hnBv$4O!d+cIe63 zt;*m!Y(UF42_Tq`O`U*0ss#i~qsJ!_42Cb$i%v<+UvK{Fq#KEw@@6p2^xhr}?*caM7@Hr%})~7VNl2 zjYTt!uF=UM9Yr3PP(}Q_B3f5hFVkG{&ccFU@V?EIWN%P|&UZg49))`lPuT#8EOQ%w8%P-|}`yOz1#}NJ$B{sNKXd zF&houG2ofvYtzf$XQ(b0U>{p74#T#udCXyR&hY>T%U+Y9>JG!?9z}T!Q3(d(pq4J= z3D|F<@Oo&tJPk>d{NBRlb$K?pHbq6}YbZ)d^=o*zFtSekTIw;iBR4bTYN|9#?fpuz z4044|Yb}YtvlX_P%n!9skW~|GkFpDEHqGJ1D_F+6oC;NM_9tv5)~#Hvi|fF~EY;)5 zNo?fd`h_tdDiXYyfE?E7vd^wGKryFsDNfJ9-3f^B8y<{@mT$ph-F#y=WQD>n;s5gB&VM@P@Py0X5h zem0n+Iz|Qlyc--Bz|sA1%uw}E_5Z$ShvXD){1=3k?o*Ae+DfaloO8&qB9rQ$WsR{v z!+$-=eSHZ>5S8PH`9bz6yW_`;WLcwQrj-ui*(#(iZF`eNik89>6*XzIzs3nv|DNce zUFX_-`6mWLs?<;<_MlCV(KZt*X+{4mgn@jsVCY-G)1ClFCd47r^zP(}((8f9s3_t# z6o($fmYq8|HWOF5!3}6@9eDJ>{L^t7j_nkUUs_q9@&*I4I;lFpUuINw} zD2aZBzUVBY;s3^ma$oEB>n#pPsiZ?9Zrc_LD4j@pbVtV4|3WYcvt+8opT@_x^K}G9 zU(XbFj$ap6EQQQke;m-LA*wNBx7Kq?$vQH{cUFsk~@mwDkwxUPuy zgixKpm@`R9Cz426#Ka-;qDXN0GtZ5~l6(w42^Kr_%Pm>#8>1YGU- z_U&A!O*P`@@-#qS1l<;BM>MO$-`)!wTBX*OLyy9Cx%)lo@Z+TWKc@BYu>Xf<{?Ru^ zaf=;hcjO{&oC5G6Kqv-FZ@b zVlAA$=(xG6>n2JlPx}Nbm>2WnX26PkGf(y)n?Z;kL4<=J!L!r@=*unMz)sP5Z$`;4 z;IPG^O&CCjFXBZU-5!bX_wjnHeAfE9H&oy3KjL2YdKZY43mEo#T_|~|4j`Dp(x$vP zMjV2zf*U4g!B5U@E$QUo#a5mtpU$*Yyq@!wiljT=eQrG#CUa&B!ob(r8HzKQhh^TL z0>a5W5yMG&G!wpi#>=9)O zR2d4|qg*2GjOg1h=ThIH(GrVWpFE2ii5TS1qrn*pa!Ml#RZ+mZM=GU?*gI)wk}k-@ zJ&wFZ=}7)}%^Qp-W#l-eP58H+JfG;y7(sgyiCiui4JS%)4!VtgQ7TUuA0vY$qFmW} z?V=)arJ$Q%a<(o`Vu(X;hUO| z#9@0{F}az`1GHJEST6vi!5fJQ!vM*a5hjOpy!IuN$wGU5unjXmm_|2j@ zx(^4A2<+mAL`NVZ%E{Fk*0cJ8LZ8C+_{j0AV}H*mwVa6la^?|ec5YjM#<%ppKm9zV zk%M|D2l5KIwUd=WG^c8b6gHF$__Aut{^oSHf2-g8c-!s2x*LISyG#WD08+sK%jK1S ze=JRcd&rho7{2`Z2gAvIMVY^Ch%saRkMD!ghd`_Vvx_z5>$0P}%&sX-!{)YAhBHwvG<)Rp{5k6Gj(e3mZudqr{rlg(+Q$#;m&dr zv*?Nr?v1X`*}dQ?#eb^?1P7bxx57YJk; zt8v1So2ix~A->U9r|t%XH4k-Ve8E@>!Nz zUh@k7+Y+?NNy25JC%TBhDAf)oVDlwea<^xE%bpqUsSI4~h)VJn;BI?|!@dxekc@gs z5f3U0H=Vc;;bMNXGjwBYdKJ)kJ7dXJMoShs@cA-*Q_W6QI*PevdhwVXk9O^V;E0h>m=Y12ffLuKPM--or zDlts7w5Y0_0{I2K^!SfgH8j}wE5xXhc>mw;#vEZlq$4M-1?(Iv9j`8@%7)b7g=bukbk@G9=%*qUtCWc!jAK}X*QH2^ZsHUJ zD6aObEakVUOs0gJrAmCKZ1A71zVn^(5iez0&>S^jEGh*Wx_utw_M_f!!+14=+qvRI zf0TzTu-w1AE}`rSrU{Mf7o&lsULPdF9XHmW-- zcfE`6l8%wa!k_a=;mY`=d|0tELb#F)ZvkkMSXBIXAuUAQ93fp7@3@I7zP>0yCWXP_ z`Ih1tU$|~5g+X`!Hc95z_jYL(cnX?=|riAVKL&jgMgbpGZrH&!Buk(T;GqZ((SB9X6!6XYa^ ziNGF5og9)McPS$)^O2G9)Lid3Dy{TSP1X3`p)$JFPD1XL)!!j;EJI-VR9>z8!s@Wv z4N**8Iwhd9k#rtsaq^^zyDpyh8tr#^nSmZh(%j3Yf%at#?uB@tlgwG;4hw4r(#B%K zLL^@FtrODSe6?M{Q}RoD-2op`Tfs}R7e-xjkSqSPf5z}~wu$F!@l=WB`>TTQWpXA?ma@NDh<{QpGT&Lwk}%W9Xtg$r zt9OHqE6@0xstq{p=e1a6M!h(7a51g7rpceCkNKf|8fqmG-;ne|o&i6X{@W|G;X+!Z znLfMYVXnpNfn}*mH_4yMw0Eo!P#1ep_hzs8nammdCdQ!C^6IYk6!dh63}ny(grRm5 zb-cOhcl5Kuaj-B|#>K~@OA&y0^|s4;q^8QV6QRjURvLsjr0Q8YIlm7=q ze>zeJv`aAce+dtOBx%2=zXZ)_p)|Mr61NF3? zf?MimsK@LHO|E9sUCrcyo0LgKPNuBgL89@M*63B$P8c~_(uR7dqY%srz}|F>ORn`Z z9gms&Os5ex^jUI^;}p+s8-ivNME(x6+uzQ$b(yj&v=zs=RO-UT$7dE6!jdBrp-fL@ z8Tx?=kTOnVks{HKn32V=4rQixW#&!^`c!Pht^Za=(N!sszKz~1Bdqzr0MJ>Fb+8B5*Oz4<8 zWUZq5l(i_T0uU)#=}eqFa9(-5k-<$2$(@V;BW+A)bunroUG1tBkGl{X?Wjs;93jk9 z5tDE#W{`*zDKAFeMqhVL77ahUn8y`P5!$@H7%N?J?Ic%Zl=OS8O8$=%lPWHSJgwI4`&(iwf+-Ao+hYgX=CwL44nkm}PH+n^TFYDRXkRk7 zARLu-&Qq*CT=`>uOwO9*yKRsQMU?Z(>!EK5I|O)r-Gfy_YnKa;#zW$IPbSyE|2?2j zdtd&2K3f$WR0T$S$sD-dFZm#NPeAqa2R?SQ*xC`9>vIVUiF`Ya-h0y5hR!QWssR@! zoO|q0^X6OY1xX!;sEy9P>I1?nZNZ?LA@mr5RD@%bgsdX221uV}pFAm&_f31CiyIA;Y|^l%ti*u$)moD_ z{}zggKKieND(5Ntjzz-&E8RxcEfJ!oj~Ao2zQEwRE$#B#0P)YdJx8lilf&r2`Nu^= zL;HZJEG#wUVy1;nbT~L|KB)DyT1Uzm{^$9M3**tTPjs90>9y=i7R^HdXdKnjoU%&C zpIZfIYw^FFyh&+@e!j2n&74XKHPc7Gxz72c=rHK+uA?@?{;In=3PwCSFs+k_*wCST zt9>JEdaJN~fERc>oNOZzYz-V*J55f!kEjv*0<(i^F-r7!_DU{{^WT>3ssF#-TB|~x z1lK6`Sock{4P}7MmSFdLNH^qsRgO&8)4uMe_mP~Q@0n?~|ApA1ownFv1avN1Euyh-4M$qQ$7uK%#s;nm8I(GK}_jJ_FS(?5ZEEC~HcN}wrQ!bqJ8%PPXmZR0@q zS2R`vj3ImiBR)U2{-{Pg4oA$zOCVng@ze}Ez|#5gugNU=eF(}?@R*Zc`Ph-H&KCvI zPd8?klq76Pwf1&6B1CMT`BfvYtP6j4Zb=U4r-y;95o<|YdW3ixTv-4_y%ERPD`sdB zduu2|hV{JD9~1?c@H z$qvZ9Z$n_2ozLkkgi8nsrah_!wf5tx4~@Q(BKoON&{V=(u<1h*puPF5Y~gQ*^F~jP zC4CuNzB|PEl9-+~jF{GXILjt^a+c$_qw{P4UX2Y_nJe@7+}jW2inSw*h5 zTx*M6oIrGdm^a#s3EwyI^Nu*<>KdZvO^H&AJ{cLYCjXWc6vxA7(s6}}bL@>|pEo;i zVXpA^;e6HS>(4&QG1w~&D&S0HWnU)=br8(#_qq9l)WhG0v&1CmV+3^*0YAvg^Cjo_ zi;pnq8u_|+X$nq3pX6LO%Mtk4!^-{p@W7XDxi%5EKid$V(R( zJaxmsUrI#ivOOhg#2*x#xG~YBeZ@)F76T^u9Fw_LW~^&dgn8~0AIf8{ehl}cQ@GO- z*D`GjITNu_p4PFiNAyE)*!)4HGLHPVN+!YNIq-R$m?-e1A9at(iChS_Eiv>tR^>~- zqJoGIis|l+FoR*WI{K{}MI6Z@#$5^uI(Sun-JHn8mjm%^W#xA8+SODj?)*mQHjTcP zx_L+j)aA#352+Lj5*VEYSqqGUat7|)41QJ1^4XDTt$4;2vSH^{e;v#SV)(ry)xki9 z($2KuDw#9v0C=vU}6xjY3Qw01}Ic`u54 z4u)*7SAb@miym>U&%AVRwLY(ho4h1wzyyw_uIr?Z&0z(kb+yI*swMw#HqX1xUGb=w z@c%S$@PeNhHsCyulIyBH%pN8l@7lloAMgzS3%objI-v`K06>a9IH`PT@}`fK>gh6L zs>SDokL3LVSl}-=qJ5p4GsYwSb>TeBJ z@^Q3E51f!Ed13ZRqO9F7AtoNoUDWY!Z7gh!^SDI<@dJ@A;!&*P`uZ_7_oC~!q_xx$ zeGT+UwNW=3ZYF5mUOWN(g}?8U_O?_b70ka05K%n2V(l)ZEA-rxcC0$xT#S4xtq!aJ z!|r;~z{j0_Qef=;x_}5$K}DMFoc@k1UP*~CF~rqyYFbZk!pa;!Z6Pnz%KtH{370KH z`5>luwn~oF&5}1*hR1_#jwp21F*vXlpmDRd-5>fQqJoYDl?Xi~(ro<+i{Jveh~E3+HFn}#*sAb5L`h0U!_ zL4ULRgm$&?<;D>4CZ_@8eaXzJD|6y0t@QR}n($#-_RQ77x$6o!v(r5*ORl+TJu{4g6tnBAN9FCheI$}C zHQ>;t_SKO)#s58|BIy6?+m0qdUvs#>deeI+&PDYcT zs992%1?N&9zUHgp{&v#)K<3n`M0YRJ*h5lCNTT8ljf;NLGmWdw(?m~U??)A4*%di{ zFUq|Jcc4>h7wyuaqEN^}xbTAejD)z-Mpah#OSlOrWlTyl5k}oanY)k}Xr{~(E*A!a z%$C4ZAGS05dISlFcpYcpAXyQaxYg^BF;1&>`=}dX7QdwwWvxkCmYqykU3zd(B&}y( znLl7jW8A3v>@kUCM)9PKu@{<sW6|ki^+9Rf%nD~*;{xPY^M5VB66bjq4^qq!&)>D#gtq>%b9pjc^6P1Y6 zV4a^jn;t1-`&k)3{!g0bU=hZeMuVjQoSs1Mcp)qI?P>z9`tZ)W@__Laq1nZ%SVPru zzJI@sR(qMFV;vb&Psa8ScOOvZ&5bJ+*GSZ*m@bS9767Mv$u>-^k^W;L!wG)V;OHxP zdsm$%LtU(7Jq0n*GhK@4Gq*(pMcy2~$mOd1vvL2Ve#=Oq42w}#vS4!zcm3^TqmGFk z41;;{M&LtT7Wojqc`4wwP?Qqf9 z-fhkPgz;^1>-b7{t*vuPP%nkx;QjPn;$MF7$^HMDO-*`E;yv;V)vw+<7)w>x4=E(l zYj-+)?+OgM;Am_0DaYqqZeqcXH2O6)16?$^{SJ;gbI46G2cCNOiUE#g0&fZ4_4q&~ z_!`kA(U{12NtFdGJPspjepapNnHiJ14U={0c#Zetgdcqawx(xyR&>dET@qkRwls@+6{Ej2{|z6~!?>7G7AB;Zi`ehftl5Xv?=keU zMMt7F0;kN-UVk!_GN#1di1VLZEhfC!(Q;okDgtMaY>p1{7c%K`qViV>BcwAbrs{%E zFb+P3hP$Ag(uY}$5OY)NLPr5z?hESRqa4Ti+M-LdRx~7!*sYh)dDI^2Jl@5v%o!#w{--z>x4}sZ)H7V0>9=I{DB5R#E*C_>%wm1_Ne@@ zcUK5HkP+GswV5t`iz}tE9o6Z&M=iNwtKr=bR5X2H_qcQRc%O6iu+;xMCorkK74YL7 z6u;GP&!Z>6rvz9Lkg|Ln9w}n@d}gDe|+3g!e0W z_%eQ`reB4M^^VBc`tbAec8_}p`9@ry96qA9j#9PAS zc_=EUMoJbkKXShl4pSZsYm8V3VhJM zpXENA&MY2yJS0gj(I8Z|eLiU$4b0X!0qxDizkJQvA#Fu#pauxzl*)>nHq#}}ScZWi zW7UKMgxB_m-0X6;G`R%pf7T_pWj$nZe+XY2!(Vv?ZeOwDuKf68b8UMql&%dYd}$wh z$^h-l2^+ERr~7*+v zb8m-xhv5D-*xJRJHwXq?xAh0iTDNDv>2K|S32OHOy^Q*u?RVQY`=A76u60&j*j{4C zuivz847+bv*^X9EJe;|lJzll7KmN-u?8Hd@5BsCU-`$+IosuGE^;4eS>fN`~=^|Ul zu?j945r00rzg^5Nm0nQywF(fwcl}IXe3QlUH0-mt~V3{^>Ef z?x0j8Qr1nlT|ivG!(Ne4`f=}U`TbC{jbl=RcP`@X*_Ms|oWjfti}ul`(7}z>a?ZNO zZsbQh9n?j*L!wL&ZuY?h&|S~)XwBU5f}z0s3ctf^Y+vY?xC~2Km zl^VUhwqonk)e=>$JO5-TF0l1f?!-PBtDsz@$e`=Ja7fOUFOe973!u8XU*vtb39px=0&tztIwPdf#T?J_^@@#)zJY7m`@G&)uoBf z5IWxv++A!H z>^VzTs0|yu zF0K7U5p3`A8L(!scyw*PJqMg{47Wn189>ljx4oyn=gLDs1~0gaS@Kbtr26r_xVL0& z>uC6v;K82g^Goe{ad$PU4YHp5wGg?`7qI=y1~g8ZK2{r&6A5^x^07?+Glm^yyE{1h zv02F9-i|cM9*ot^`}nwaU$3wJj#D*5-!0L+l;8`y1TeXJbf$K?Gj|5LAEs2lTXMX6 zoM&~j1`!Ere0Fw+T;l1(Zf^7?*$QZHa#h}Tl1STM^7H$5b@gufzjc9BljQS6007eQ zw+@*Kyr1hb=sm%CyHy+C#0v<3pLD_CgV2tL!-R3Id2t72gcms}uE)Zk&0T5d?4Sz2 zQ}X&4JMnVn@EhIM3a{tS7a7<}^$l=An5aXl5QoQAkWdj-xKvY#iMIjP8KBM8`KBR5 zRN4tM;3JSOLp@Mm4jls@Jq`E-n+SkXs*V!x6v$>ys`XHqGWH3oL7+15cd*FTwSA{EIMT{OlnyDrN&BsP$yLDVvglD2wLGc6K47WFk+mJvL@= z-gn{>>{0OG0<|KiF{vo~3GwRb-xx7H?eXmZs8dZzCFaXK(3$rY* z*su`iq%zt{j5du#EwXEp{r<)a7)0$`RMO(B{ah$Cj^BC z@#EV;f`ZMi)wkYDpwX{E5KqY6#6h!O3@==r9YZ&06+(fmU%!((#I;^c9{^vkCtmHl zLiM_f!F-B{%=o6%!cU`KU$;usxS-LFh8eM}BzUPm} zL+tI(FB|9r>ra_g{tTQ|psXv`jjV3oApglaYhX1jL}s!O4upka3SScf8h~ zeB8?JAv;B8t4W&%jr_!Jx;>vQqv*+!(ZtXP7!2HB{*u?fp~2HkxYnV#UW8M>m&8h) z2s>gfQo=^T)XJ8;dty4SvnnK`kC)|KZgKkkTM0{g*z?D8?^CAHdaD@*_?`xeup-D$ zX>*d2a-<2ygqof%(5j#Z7b2a}Q?j+xZfoR(vjljZS=s$Qj&ZGoG!+tw;Ym{)9`8iU zP!2TmPKBj1&GD_)%mdz~nu~j49qR<{`&moEbz3PwBsuv2kvb0VY&oK&J$?Ux4HTIK ziiBeeHVvx40F*l?M{*G}?^uMaukO&>)O#H_PdZ*&*tulH*HKF-)6OKmi>lEjGlWd< z8+duR)$3>D2#;LyWK;lOE2ind(FyiEJLQ-N7KF<qh` zm~*fm9WehJ@)ztRoXn={Uy;g(DJ@z`2A|aA!KPj49DCd_7~A<$gvWq07149hy&za^ z4~M90SNgcXOQ_>wJOdJkMd@@l&uYgv`Eru{uBCqX@^!i!yJdZK2w4zye7*eb@R6wF zQsI)*A#N!FxOHvZ`o`~%Ki+oN2}|3(8G=iP4zpwaJ_Us%wHKWN=R2jn;p5-7PzinG zj)-A*#|z7F>wR1OneXz<{b&hg^}Xyx5EeE_2f+K{I_DY>(tGo%G|d@)aPE3;zBrmX zDLT8^yP7k|8LjLVVyWA^;zM0we^m_tOdQ=OWW10&|N8g9y8?^))L>Qduf>T7%axi`WC{1%r#Xqy?~H$) z=RPFNby(;w_sLul8GwfD^t_!(K2gkQ9DOq8;Yr?bAV zA?C8CYu3YmrsRx<5r-|dYGNshf#{uovv*A|o>Yd7i1vD(MBym0o&Y4e94re-ZBg|r z;B_Is3>FQ48%_bR9XVyl7Xp&s={BiX^w6WuW;qGqWTU zt6;Gxhaz9L;J9KFr0R+>e2F26;r3z^@;Sx6_s^lhq(?b|V-ijHKDBwFQ?Opo4)Pyy zNF$%=Rx-zixDrk4wO3~qt!2atm703|Gx?@tKM|zcjvl)djgc@?=C9%gNtwfnWgNvb zb%%|&MitQ898A|MT)!2Q7^-Cr^r@pbKh-bQQb!FMYoQI6zoe1s3Pu4v=C$JY0{|mq z1}I(kFj;r6X`PmMQ8GnwtkrQ!jWf71OY_Y0LGXLSachd?L_2BWr&fovclzxMUf$nQSqT=Mt{+}=R!H5O-?dbo&c4n}K?^>zU%L)}n69*4zrOoB z*EweIx7B+FHi;E#(0+N_+m1dbgySvNhO58ysn!PAw|FvDy?4Q(eG+=QYCQ8Jet*)g z^f8-$>{z&X+#$Jgbzjd1cLo5qeV#B@m5%Oh3o=Wiy% z>z*J}+mWVbFeG?la%1v&a_eueABO)%W6N{1+NIsM5fCiH<6DIvc^~$F1_1DY=r-wk z#Alt$1RG?WmHYv_H~NeNC-9pm#0@#N)eQ<%ZO6U=q2}B!O$qjOe@%W`gg7NIq;k~6 zA8zq5$L$M!3`vqJOD&Rq8_enck{2P=vJGnxXp9(CAQ>v6LQQ(HXmEnkc)f&Y%OhI3 zo)^&Km=qOUgU;ytBHMpHnwc##HD<>_ycR7Zfn7GsJoem6=XV9;`?@&e>Op3#yuR$| zfRP(eRd6JZk+CgdZ!7?o5-4E2>aQV9W@eu$m@FUuM)oV9mFV}U6*M%H2oMa`qV#K4 zJ!rZ-!a&-U4x1t_6;mQtmnhUIo22MK>3+iIu$7b8e5op!kh?Af6si_KpBZK;M>y)c zz(#iwcki@4zXucQk$^@b@AI3HI2)xzfw{m(jW^#V3fH&w)Z ztt&}22<(MMy*|CiHZ%L}(7_ZHGXHr9PSau08dES;u&>cq=6OgaLqWH(^%gkyW@|}= zlRtbdHs*19O6r)U_Uo*(~Kyt-5Fq@Nx5gXpQ6n44&RTVo3l!! z56l^#uKR)Af@``u}q zW!ZZxC*x{n3E8b0RJawQul?>8q)+DA4GSM*^7`65;um2@p8)bRk$i=F^(Ab$So144 zxqL&$Kj(veJ-@Q6*%~Bx7aF1mz3@Y)^seIkN6zttf%ac1B+jULhYmLtsyezEW*l32 zdTp?wjprkqLI;SAwVA>2Vw8W}!-l@|l~bvvE@xOzpg(shr^p82<8o$XxzZXKKu-TxctI$bsk~8xz&1FrmUoTcl3Fl_;^#HRW!%Y{;wCF} zx#0v+3F=sOYw8Kb?7bP>PKYg*L|NWv$sNRRq60nW(o#g(eWrtj=XS0C&# z*%R?t?VyHWdCtF2n43G{Rxj?)>>I_D7LocB92ZYGB21|rDkTvHEBz+Qs^zc=uQO2U zAS0+h*G#eOuRAHUS()x{BUOMsg_b%V`77TB)}?AT)Fqf|D|WB<7#2PS(*3Aijya)f zVm-T7JIq`SHkE^t(B~U$8g7c`osL$J5Fl0pYshm9ZVtzdtSl^Z7T-KsQ*qzTVidS1 zvpD$hcD65~HJi2hOkis=k?J3Jal7=%uXGvG)h^me6Hh%CM_++bQ2KN1;;I zx?}osmgx-v$h01Z&ZXzoRJbjs?^Ax-$G3<(J@mPQl@FI3pIyp@V7>Q)XgM!lll-@C zyKd_;VC(EIuRt&ioXEJN@5z()*SEaYGmycc*;S$&M(9cK;t^GMRl^&&C$x1Y?~dp4 z?x@x~^?1_@_jdKQy?evB29z>#)!%*d)a&OC+nurn0>4_HZJpXJ9lzKQTpUqY^O9*> zy*CNGyIk0JqXdDAkilu|e#B1AM1mGs`d_R4z4a+>&-`AmZuwU`wY8g{g6|;v1_lq&&}`mlhy{Njg2;z zLW4?;i4q9{T9ZX_s{wVwS(IKfPCh~qGabBW<46RvEAuhiz+Y`Fu@j)!@i3OE)=Z)t|7U*&-=?p);)9GuE0Z=JP zaysoaP$};`K_T=kGF*41D&WISp)o(8wFu7h-w|E7j4D(HT@tqEEGQhj=fANwAC8)Y zGGD2?LB0@%M|>j#*6tFbrgO35j7|b>`OxoI`{OIV9P}JSH^K5!L1-R@SCDXuK1S2X z`hfu5-Cif~rQ*-pm2q>c<5kB2*eBr?Z@C+d4QY1>^swlCR(T0*cjo`nvh%|KCca{D z0s0BjcWdqubJ|C%SI-+wdZN)P2Qhf(~x-}$4@LnQB!z^Li_DGjjG?f1^7OAi;EdaHLe;`eL1)wm{z zz;U7tqh%t-2lqP8R#iuMorsSklh<20cZkG>GN6AE= z`o}_$@Pcw@+a@e9n>gD~Z(#TnS|VPEj42&W*d+Cl$KUL;aHm|xQwqb9lu+a8#F6Dm zm7%6VlXrXO~;XpM601MaHv8rpx%f8k{Be0-Xu5vk|KUkLv) zFCC;veVcze?NpWwG_vD{U;0dP$lsh&%U&z%Z$ztFZeD+S&Y;CPUf_LXMEDvpZTklz zcb@jO=99jiruYaEj@i6~-=wRK9!IOFm!4+U%v#bQKVGe^g_G6asq=cVumoL#N&9pIPpxv#b`+Q{bfGi2q1d?BC9E z0oBOb|AEgxMbrO(G`YJ2`1DhexOc*4>eTK7qGqExMMiYR(e?^Ip@$dTYtHHfSh;=K z+lpY}tT-bihn0bG-N~ZceoQ&g<~{%R- z#UWFbNxN?ybXhCqM{%0jfb}$fI{VUm3jTlL$OJqE*MT&T)`pklC^j)0ATs^#SZFRgRJK!;18H@0a;aD*z(`xVUmR*=31vUxy zMB@CRC^0#)q|i{lKXR#X(gBMlh2DSTccRtwzOIV(mSB43YvM$T+roIsjp8$qw+Tbh&Ou@wMLNHJNEm`i=a?#Tlp>geT(AP2fL!>Spf9ppy=oMMCA7bdFpMa0WPsaBUI1m9t8t*{9Z{0Hph57m_k58X@%exF z*@+SV@0P{=8Z>w%MW;|~zrv>HbpT(b1CH65--K&1obN%w9a%ZY>$BKxt{nnbFxnTZ?zM?cz>#o0j9%AF?!4m?w5_3Gr zlC0`V7&vI&Yk6|RJQAeex!>`$5p0h^i^Qslyv=%SH#pHJw|b@)@H9IGHUA@Oc%%+7QT zwlcCBd%#d9Pu5s~-fSC<(xl5^W%IATPjjih4$!8E+M{~2Lw{UQLk?aPYg~zwWZKf) z4ftCDyznRVX40PI*}B}nCn`uC-N<~s)s*2t7{>TPbujC!FKUljWbaR;fn zVum2Y%T`E#byuSDj=3nXGiChPDdw?6zNZ#9G;i4|Tc%(*p$wP$@B~|-&03<*iPOxa zA8AX8t{N*mTgD)3wU^rJ&FsuMX*e9#Ip4tU@N{6VH1a4`c=1w!y@G@lMX9+=il`T_ zXe}k)`2A-1t5}mjM1dxawOry-$|q(kTd9&J3Tf??{6W78GDN%mZ*mJN?5R{0kW6mxo+H4w+5s8Cjm6i$CJrd-3{SoK<`~|K?fDRrmAy zzoiIV|KEAt5AKK|n~dw*ug5LHLG9|S?b|}er^^J^4$wH|$-|mX*Bhm|0{!lOg0>?{ zcDQj7Sz0~Kb+%rln(t@h@;#>V=s0JKNdsY3@*(6I$ia+yFA*w&=Hz-?d$bI9^BO}L zs5Q5u8ee~om@JDvZZ{g`xu9BWNwMHO5I=fZq(JCuPk1@C>F9T zq#PVDNz zh$@P5F4dfub`ar-3DMfoNf*9~UtyEAH(I@2X|U7jxhDdYXz{|Wor(^Td(`cPfeIc~ zZgwC2zxn&ot|^?G0_l^$d*?6Ig5eWD(A&&KXpU zT2RU{iBGve?f~eO96aU6Vf^+|`m9CK%NKt#*2DUmR%^{KGj(pB=|VJ@?D9pR9RA@2 z#`8m-I^OVMbB%pZ#HeM&+OX&qwOzM@=clYa+=hIEjS_ky?@4J}M z9ztMLOj*^)>(c1cZ`n0X5YJ^?O&<1RkYy65qT?#J@<_eu5%u>CWl8aEr7UDzk=8Iq zb>{wQ+|a$-t5M4u<<9bMfB{WwkYW6i)kV`0c*h&_kK+%Ve<|pK#`=F%Ncz83NdJDy z-*4*i=v~Ux8lI%XKhS-PZ!{P&#~Iw7^1S_!^>z&+(0SX@-Sqbr9G0)>r(caS%vDyD zyBDry!UdgvPVV&hjU3vzTcCOLC(>!UUi6gQ7hOgyD_k(s0tuPk8M7v~`xjjS^ z!1K8}YUpbZ%v89>A<@b}S&9M^&r+xoJ2P&|?|OqP7GTw70X-njl@=eqdP>ND%!5*a zFd{pIrn9O_=d|qLE}l>?8eu!Bx5{RL>ZT^h5wzno#d@-i>5c)lsAsDe9Vi8j905_^4$ z=|lj4NR$HA!U@Vo?MeMFKXyBx?Kfk2B$4zHsD!^{3GJXwvcF zRJBzGXX@tAy1K@0!wRY&#O1%k+66u&ngrcJPx?M0kaYY1>Gr?r*ca=1e)}Y!y&7g) zK#STIS?D=z6p!6tpn*RzyXU>!<&0%#$6-Phd^04$XrM+YrD%>)&oTAabi(DZDtqvb zN{vNuE2%#;S(&dg(_Eil&e$BaDE%WvQ89W(lPn|}$^g$`YEQzg#`F$dxNn2nmafpvkVG5hs;`nQV1=rg zm4o(kA)*-Pf#a}WrPfZFFwgp@E08E`J#qXzZ#-Z< z(bEZH*2az45TR3>tUsNRY{{KRTHWItD_KT~m2==vv}w!}>?%S!>(+0uSf#RZUa0J) zx_GLkI$-zC`(_(j+LT}xx!J9{?LuaGFfPkV1-9m~lko(EDIQW;OPIEl5!Y|B0GFvj z2ovHKh{q~B0*7()h6oExXaa5R4#N-yP3ZF|&==nh!BhnoJ?!Z}kQ%up{{JC${Qp79 zZ+k3>)}^5t9UuKy#GOhe(FHs*aJTPS;puJ9y!E=@+vo97o$DQR_O=&c-s%K)-Sl$A zpn{Yrfw3qFo$=2Ww7pH=kY6WlV59BLA1;x{H<@~hhB<29jydX%n(G^y<2lRFS61SN z&&71lS63zMYZ>99?Md4@e@!mGe?ds@!)!0cLABLVZ;?PN?RUKiuTbV^o#nBOVH`cg$;jWPU<04=>-;-*^8kj_O~36 zk#<&3V6i*KhQ8F^Bt?rnqj2{(l*U^o6+j@K-fpP4($~X5v`9vz!79qQ*e)j&{yQ!z zJs!GF1u{!k{6G4e^r(6&*2Xhde;|Vg^^!!c7FB6fa_HAOUmDjqi+S0cySvVT5_GkPRwT6d5*CjDo1@mny0R|Mxj)|P*^+kH6!|6T0ae|@Fm z{vJ>YTuhb2j|ncs%T?OUeP-?mOe!8Z zrK74YkAHgepbN~zJLJmlIyFiTft@q6n=C3(^rA|exi}mTz$1c~w2jaClBqb4ZU&R7 zyLeRruLObLCE3I#)?fz7am3X{cb1f-J!H8qiGfcqT(jKOK_xFLZiH*%YSw5O*#f^* zI~cRdasC=xMS=$$2M+cjNzc8wh^#jJx_`zcfA}{DV9-(R5a9jx7X88?u$ic)I$Li~ zA5#GFq~b&vRdqMW_55XleFgIEzMhq8@h4Q-%D~G-^wCZy#4dlGEd&)l49?SC6nMX~ znAvL%W@5BM1Xux1wrtX_Gt}y#`8p(Fd2~#r(W0%C2vMh>Rjf%Btzv=ZUbXwj@-NMF z#ZGGmeZN_8NQBP@F>g>9I$Ys0;J z!F-pB9tM5fCw8=w1*t3AHECZZ`zWzaW^6gnr}$r9+UQ#JQWg!A5I1V1(A-4;Kn&7o zr}^F@X~V7VL)+RyYjfZ2*n1$Q(z$3-A=<2JycXb=Kq%2C*5}j0PNaqu#aV%##iId! zo&P;`NSC}0p58d7(XhB&$+a&@)o|5!{YxDkg~8M^rP;kPba$Gdp^Z~Se^|&Yh&-7i zILxW;QsEBmXf0fSfIUfKADB zUab5nEa!jEX~fT;&BRs54bYPMO7q-Rz({@cR8Q;i+Gy*V2SoHHi=MDSY2R- z!p`T}L}2UE_2?!|o=$&7QrR5~XlZ!-qVTPwEAqFcYU)tSmo+`*cMlref;$9vcR#gv_Zi)N`ixP73oh#F zt9N~CzH>g4Y;e%q^FZQf{5eR+3n8-z4KPn`vXtJpTa4U(Us-H?MzBw?)}!VK4_qhw z;o(89e>~O27QS?I%d&s2qj*ug2ccaP4#=UZjrJ>7Cq66T2A1FID6i0}hm4`rAd8pL z-FH@JbDddD??yVw3Dj4w~H>eP0+jfN#8npFi>KP4o zNQ&t{kIjGHlc3M(ezc)AuOTQS6Hx2Euo`Y^TvRbUX}zmSnq&;W9*8YnTT(%#ZA#r+ z)#2N=`QE&7k>d=^t{aX;Gv(}F4=6rvC$agy9pP5%J^w?OCb(V2_26{-hxhhQJ-xdT z1WT8dQRl&dS2S&f8GL{l<4fY=?k3a z_NB^~lf>x=cOykh45V$Kw$8VazH!e5O^bRGn&c_$@t+XGSXc}jE|E>|Be~@G8i<*b z!{`=9G>n?Y*oZZ^wUT@-xHED}wkTjxB}`1K;qR`2$(PxThfE zeSH);5Q?&fdXJK#tN@>syeJ|c6`kvRN?lzpsC?$fZ4<|po3ogzV$9qmYgmYOzV^2W z8*PA&dYzT}P`(CMD5NrPL}Qfw}4_hlFs36s{m5Yq|=$6gp{pYT2P< z=^#sW^d*A`dwbSNKZ@4LYLO1gu!Otm(m9A{=hs3tB9*BZn_kD@!)5>z5e?=4Kx=$R zJV3g2x$@$(f|H4Pk}LQ3m;x$q25rzC8F7rq0#G1x?dzmrM}&AX5+VxEZFSCA=DO^X ziw(m|?n;lTqJ!l}2LGv_H?*IuE=`{1kmxyhTIz=gKOdl>n7;YfHM>4vno3J$LJrjd ztA{Gp=h+*COoP5&%h-C~2OavEpi+Z1Vf_FU>P11fgBQr2?G~PA>%mq^aDO5sVk@2Md{iO*gI5!&fryL~I4U~D;s$;=okt+pR$VkF@ zhkgcN!sln4MLIZr%A^oDT%=$wV2($l2&7Jd$)|2&AstG~&)A?Y^ejj`3f8Eny34|5 zE~3qi~W%trSK5))nxVsm*jyhuLZg{>PW{|h|cgnqy!{mX2Wz3=U0x5yVbH4&o;E~Rs z{_*)Y0=|y$X@#U^k5?An71@#U5y^E2`r|dp0I)!C-fN3b~e#y zDJC)H!6OUjMAk45R=S7zA8%KF19|On!N!ysSO(9z!g#^%&{yjewMkpkH;mQGYAG?c z0pbB}%eibyBfCn)_%y=y_DpINO>z=Og4xJHEp6>@dRYpCr!vkyqY%13Ir%uwEX_{c z7gyo_MRYK=^@-Ui892rBk5&qa#4$24ctWaZ>k$w$prc7xkX!Cua_skoygbg}s`oIsLF@l}Uxs=bm zN}qJWxx4-GO17PJs#h^76PZ_}^puIWo%DA4B_<{;xy`uxkDiKqkU)LBN}AAn5FFg3 z9fhq0!#+!{+3n)Zu$FvS%t9Y$56;_&1@&PWn5#E_vQSIF+VKF)Q`M=FJAmKx)VZKH zu6u@Jm%S`A3bLsj!%!Z*qp}s|(?@!-)|r_aA6kG!pW|{$Q0r`lyo(vKfNee*R&8qTcgBZ&U zS`k1qd0S68!b6f}nsCod5ffugIp#r(LpX6aRHYzXZHN%XI!~vbD@r;SBz0}1RnI4y zv<4tL>HGa^H6CqJv=~b*^L6%26+u<%c#9bj{3L8oe|Z`%@vI%D2V~FavJ4;-Pafad6jcaN3*B^(AHS#09F# zeFY@LrKpHqKMa)?jZ@8}=6|+gUai+gkE%bZ|0_Z?h>6w#19yuha_hQ^r+<9{utkAb zD?&5=`Ho!B)me0U5?P333N_``NX^VPYz|U*oEG7x`T5=r7iN#*?=BF_#YdKOB^vRF zI=`|z_^_Ux9^ARa8vuv&rGa~HQO4AP-Yaq%sGmI|$hp|yHJT2KX()jj|Qvv+(mOBWUS#o*; zS$>KJ*(Kwj;tCE`s9(4T4g-J=#B@21pqCPjm@HtKxGqlD;Z4QtnTyIa%m8i@w51UtoK}nz=YxWadxh6cft5 zCuW?46&v`1Nw5ulB^@C)<H(#^r?$i|9rzP+nRSG;W^W0FBB(yyM0t7?QbRng^Mi^Jz zL(?@4Ys+Bkn@!9&&`{yG4SQLZ*+kI|gI{9q+k^`h;*)q>W?oy&Oid8D;^qd?NWvN%j%pZ)$H5Z=RG1r$z(@PZ5C5g!Apro&ftGlC%S zaB1$~Yw_=_dr6dyb9*?*|4}I%a;%+S-ru|4^G5x@Xqg$0#rSbeOg=p=Wm(Ki4Lxkq z$>Fm9Xw&WXFFS4aUt>oa`s;z{>JNbptx;*WUe9Y9UY&PbT&H#WlG|_Y)({5_ry2V9 zrp>&UZFO1kC`haipfhmcv**w&r@b#YwTO)ML%sEs9S(Z_%tHIe$pGP5`?D2RXucX+ zq>d?+P6`XX*B+vVfD1w#g5W7Vm8rdDq(LiP`Bk+V%J}z^YS3f7-6T6_EoEOCc)89F zgb3#4icwt2@+_+e(kz~Lsc!?7@X|fiHw4o1`6R0or6TuJF~fWuyY6SN#X7XzGF=`E z>=UfVs+hv8Ok7zd!_=LgI(+@*oLZJr2E?F8oF3|NMV_W{0%XNloQAMy zt*n}u@qUG6pny`{i5NrJyVJaH@V~IJaMbyIbMb4mjBQA(#f`zhxJyerYg$P?DzOj2 z^^TpIZc0K^Kj*sko59)HwyPi7Q1tb3^0$ekraj>1=7^9Xbe181U+dhbTcn`rRQ`<$ zW`y}fqht!3J!5KYxB@MLAQ5QZytdrH7=CEAhzf_m%DX0Zv>;5>X)!i_v2!ul^tqluqdq9EXzwwO+$tT)0Z?(nau&Z zY{38pf&RUmcB~5rnw!f#>b%Zi{Na^u=>bA5J39JD-%$ZAG z3hy04Sg*hDNibiTmaHm8;XLG^&;np%u^htWX9T)8aZK_j3AGGu#i4l1Le#F${tVE(-0 zw8Knb8j_pK87A>eeQa%H=NOsLZx)q0VPw`!e9Td2HUB$*4G?Fe&Q7bnVdkp2vwJeC zCdlQ=fBMI@mQs=JlT6E`6rNy$~N?o_*xZ0L>zg`uf)Q+9|aiD!&OY* z8-OWGwG&`oGyX==z%Wo!~dRdG&LDd}QT5Sn%u za?8cKT!@Zlyu`DEG|O+60-C7J)Q z3R%v0z7Lro?-Wicn2uXXl$W#ICKv;7@xUdvzeQDi@bKDa9?XGpKz6l_Ni#M&wPg0f z`hm>clM;_*LTIljMk-!Z{5 zvId7R67xUMJNn(SmUu7}oetHXh@^k4eYaoRYVI*sYFTW1f4Ga^-(ReUYz|Uh5cM1? zJcc%dTgiU3d32xayZik5p#K+hsHW>VabzE3dVX3$TRGuxj}S-_pKZjIwU{yqz-B`~ zXgX+Y#vL{d2WG+LkG%p9u{w-vN#Mo|N(HdN3;v4f05ZGJNsWm|VVT==Qpv~;# zA1mEx2ZVQ8k=oSP#@&f84#BaZ+P=Q(BRtd(`HgZzY}76$I&^OL*yB=>`{vR{>3q+F zqor9Op;@)iIssKHeYR-jI#ub+O(8i=+$1^-KcTxygCveK0JG) zEAG{wACT^J7FN6gGEw4zQ3YQ2D6F`zxUtP@=cj)MhmA+6kyVY?tW+E4?{J?lr9de1 z%p9CMy&qmMe(L(j4C7CmPS5GwD{O;FlK?5cY7#@@`@WY~Hv|1s__|D*1aH4;QEq!!)=iYxnW{95>rsKOKz290b>TRzE?_}RBuB;+XcFz zeQZe)6lwGuj3}w25sX5V>o+((RTLvyLLydN8v5blTx@ z#_-WmT-rNqMh`VO5`@cvh+(SA9y4gnIRYA?+z%Og-oZEw^vGo5JvuMdl+2$_9S^D3 z>SHrSjQ;YSk&S59EC886p5@PcpOR|yh^d6Jjrqt-p;_*kL*dY7WEyQWV%!xN$P{bN zCIdJeF+R%7xTU`^KFW2o6L<&k$d)A37eeO{2%c7;N5sVa3^)TmW8bT+tBKzwf(M4yoy{LPM zvbLGuulhBCUiZ{|9`wc&$JXOny1l3GP|`%uH=|3+njAr@hL z4k#I79%WJX!x9`>@c!RR0PHC{up(8mb1P=oTz z&XE;w%*^WHWu$Wg7ocB zHFaiL=(JX<6m3$islS#kvRA^y@Y}?pnjyQX{&Z}Tz=f6OZKCfV`CBorUTD9v z?v-gqE6KK>xH~}_AUYf6Nh_(l7?n3>xqAr~6WSH&$=_4dLiceHo8n?C?f+7Bp5z0^ zVrus!YE3eF_gez~HvDQ5ym%Rj`C}gM?mvBpOSIS@aq?1KQ+9o07x$uzD!%UzokqO+ z8dtN$Ditt*#~9#i==TJRF&HZx(+eP-{xys{%O$@%5lMY=^$J|o_ZBff?(OSRiqYDeP>Ma+ymsLYl>Tt#s z&x)%frL$k+iSJKcDJ4|DpdX&M`$iZ)X@r&YhpV z72{mbhw7yuM%Nb_?sw)nJeDb`N5wWiAM{bjzWsTX1^FSbZ&*n;>{(ud0UgXEENjClrOGT_G&n8aF^>@shx80K!YBSl3jGbta zcB(IBMCcw6D7!MmYNx(ZPVOb;mv~PFF_InzMl~w(hITI*D`W zG`pp$M!AdeENIiZh>$3(49c3%u*e)GBBRc!W=kJW9E|RrMDH8^`dO>0>SGq7%TF1jl+Ni@x zI1tpPd~tvC{s2*}{SsjrlPEK{w@*RAZ)#9bP_=c<1A~Ja0w?ALku(_cR3;7rxR63V zIeGcIx`y7#Q9L9%wo^NM#ApNxO44|1!Y8wRc%|%pQ4M9}sJ4yA+cb2(ip+G}`9jpj zZyN?eDgg}s>lD`H90P32u=@0h+jR?#h66tU4=*cC?-t;cw4wj_*^Z zqawPKXhW;PzN4EU@?5VVS6;vZf&BdBBmx(6tDWp9SwUQ+6p=r3ZG0`&3N$dk|FUGR zSe!7v>ciXcv!&AWiLuo*+}a+%(fU=MOA(DvldC)sVTQwQV+t@#>$x^vwcf1%w7!kd z7)1(D#TtuH^N=WP1!r!BIu}BVzS1ix{1{^93aLIDqc&vEsf4vhR&=)TJXP~OZb;8Y z4>9zjG>+vogY%5XJQjD6MA_1o){^5G7;*q^Z8U>CSbBSU)mDh(Y_+~Mt=q#aSp0-! z+1$gp^8NNI18j)*Zuq1!3?dB@;Z5lZlu{-gwaSsAq!~-eJHY5n_U0tgophth3e=%I zKLG+*d$ncMaLdfBJ$Owq-edl!R(w!}2-DZk6))8zdHqsXH0|TN*Wpzj3g0`zzgP(GjJoUr+c2HK)_!=n zP|~szI1;07Z-XOaW5|J|$c~1NB7H#-F|pq@R&c)UO8(Q$Wh^WJ1NSU5B=%=sXJ~p> zx71pSlz%X~C;2-j`kL0uu0pQY9p9-(faAJ4hY0ZgcUB^uc_j8*&cG@5N&@)|y2aMZY;JK|kqh06E8Qo03y^B<`eEaAw zqv3&(P8gZx3NjZ*PMxp@EKvN^!Vh(JgBgADPgfr_)&cZ|%qXFE*c|F?^k4Je9O|u) zoov2_7Ox{IS4UZxnL^pP)s6GM0CI-7^0f&fY!$fiH71O=gg794O@L-i=jeQeqtwAQ zEk2ift-C&IksnrRNn3#izVvy!KYDRm?bV#aE#9_LjXY$j5G6U&sU+pvU-=y>?Ub4l zzQYLbI4E_8oK@zgG;dS>dCg)@Wql85I}=ehLLxvD<)MS`Y3%h00V(iB1*HSf; z%L{#t;j3)D+uYBf{)G?^+D-gllJ#~MbXrFk1Dsaz_`x!h`CZq(tKslZx9S&gIAhDC zK-Q}>uv~ye{T@Q>5ze>DSp>^UIgoZ%!gVtV&xEn85oT+M z=+}{2Yq}`2aA7bM#6TCpAa8d596C;?@N;uD!4kgwskj4oQF%pe%ASOrE~>|fON8`| z`}&rJH8W~>9BKEi|73(g_NRF-d!KGph6-kATppdnSB;QVOXt(Kw6f*Sh%58D-KmS8 zV(94vfk|l$4U0@h=b7g8y3-Sp>uiTMBZXELSj%+0%8*Kni7RT(A(nbl+o z+n!rT{;G8MrT+2Op*+nsX;YsGRSYvMMoe287KOvD^0e~Dy&FYWmDsyyt)vKpREBo6 zq%-x=)pVyu@=z9EDl-#jhzu1zW;k%PyjDoTEtQpSC2m=TP#uc`GzgIlbaNI4SusIP z00*|Ps9XsX*_pbes-r}-5Pfstj%){?GW`KBXhS@NgYA${E-L>gB#C z2k!94t-NocvRa=MNZz@_9+^3YF%EtR>Vcu0kW=2j-uF?cJwr?*@BygV!JS1rzD~&Z z1029_@YW~abz2Q>Lh*6JT_ON${_C@)ubvK|jYn=B+IYzLqq*(sv_~L4->DhbJ z_dwN+(zvOIkSDj;V%^_W4$+-e^|z+}E#Tcu3G|zR&;*ExJX_oUo}7(Yft3NcfP(7s z!0Jq*_R;pzQfqK=d2n+WQRcC?RhbpIsyMVbl`*MAYx}QRxMDirP7aj`!AX=x80wDm z>(bqv!dIdVxubDB`q4gW{Utd|MJy^q-oa!c91)~M>Qee*#?h=H)@|zu2pOpobDO^y zT{T|FO%?auVL{F6asol^wq94r-*@Oi=dV2=b1^H;~^{^ z@X1d+n>t+aWW{k)X(}de5u!RJoY3mNd)*)OIR_a+?%)R3V@yADCPxGywSd$QjIykV++h= z&KQ*D7tFFpU?>5x;+neQPO%HfY-;(XWdv+Us+22=#inmSHCBElTGYSO>^Lf%clntI zbcSQ%B!h@3#ImQeB2tcYmW>%S$H#b9Hde;gmT~!AB;M`q=^0t3cHCb$>RgsgEJ z{$23`%V>cim(IQsqCAtfKDDSRQ2KsICW-P5`~O>m^rhA4Q*j3KT4#1 zP8IdEF^JPH+ZuXyTX*Zq-e0rA;E=Cpx%Z1c{u>^^Clky?N;G>HrEu_gZ zDPE^Wf3!c1CJNxPzywR+vQD}}VJD3%Eo$_WQ6PVevX9Gx0q9n-iQ%{dJQ>-b*zY55 zv=u7owSJeM2dT?yx!Do$ImDZb#Z)e=V#>qdFev53WR;;gP~=sPWxTW$KX->HC8uB^ zT)J9T4tT0S9=VO}anCuj0CfzyJ0|SAYi3f*`RO))wA_qov&9r68_vjZVsC`KgeW<; zA4Y|J2t-8B)Yz#DAWnjb`0y|zJ2TKcrY({GC7N2YI;2b3B>nac_vw(CG9wFIRWl@t z3hepqw8N6a9{J(-H(+|$T;Xpj3BZ9kNn*r^FmC_ z6cL%N4ux`CnHpDe19eZdU+Rt*X&?TkkZ!VxJ^=T8yw->hqGUF3lSMHs=i9Y3kn>PP zkzlw21l>s86?U!=U5#u&T(le5R_zKVD*s^M-D3cZ`SB=*oRC`x#pf#(GA&$ZTl`7< z_%wcIoQAm<3rg^yrSFHL>)-_kXi$GmVc~W&S8Dzlld1lug(bPYnjGZNg-XkzytwFB z7A@AsDMI>-5tYJ3pUMk*LaYS~eM4JpdE4E)Wt^;>EU$DQ_)4crt-TO>a!W8b8vLj1 zOIj?F2+byH(e*d>a*;oBZe}00R7>u5IME)}y#t!Zd12Vkyl{YH+@JYXfw6ns+Z8T9 z#t|&^j1l^0^^}n7NP5-y}A|peR9}Y9O4lv2qPgQ zR#ad7GnFI~rkpE)BvLBIc*zj@y?QvHK(QKF?Uv%;WQoz)+xIfj}s@rY4}`x$5dJ&V3BYoApJ*Vapc1 zHt!Uq|L^qLyq2o-31{G1BY*@7_NE~DriY{B`3eHTdv5udCRev+B~4ugOGza2BJpJG zy)FFHKwaPOpAn74iidWH@I#I;RrQK>Of4OSJqrbQCL}O)lV!Yhv6Sxy?6|K1ZL_pe zxdt-lx6aQk45S=?QlE?+Tzz<1jM#Fm4en0`+SB}M1j9wru>vyqa|k1+vuTD+N?Hg% zPk%;cQMWWQMrv45@p|6HgIl;&;i!Pkp&3Nxm^N+fK%r}{A*-Oy*$yK;?L1Ala~#y_ zaB4M&&;Mp4y^(l?Ieu2?F(Icwq&;$D+0t;d1Q+#etJUp;B>P}zHXX=d=l2ev&3LCy z4l~xv3(+wC1c7E}Xr=wGtv3owhG%1)%@U^~-np!>9MN40wCQ3R<;kUA>dx)25geOd z%o}Rw!3vG~)ZFz5GZpYk6u#*hM&<|{8+kDJd-_uIV>fgb6rCRBXiSeP@c3!D4L3b2 z# zV2Z0sgd1aJhc7aCWXw&RmWgGNbLV30LowT1PMOIqizdu6#PTQ{I{UXj39KTW#cRo7 z=I_a$GC64Fov1x13pUUdU;8wgyWOLfJ2X{WeBn+M&x(u`=)A^CWEWlWp2obX^?)&z zf$m>YJyh#0!WvWV8FD1H3K*Tu+;BXk-P)<~4oMAH_GQfnW?0N++eh?Ck!p2lve)HK zst4F*7jIUY#`J!<%R@pdK&Jm!H}hz{fViQ2qG#d1{`fnt>&vP2r0+MVD_OK-@M;&& z`X9G1Y0jj-uTMx2LtX-;JZ%ITu-x^p1;zU{Y^CZezz`57YR+2xv~_p@SFp>Ou#!vp z49~08X@aMm5twf74bD9dH7UU1)iwZ0JLj7nxn^G%AllFsP*WvC$B1_}SrZQFvRcWy z_5ml6b_$e0Hht9e(%XMe8JjWpnHhRQ4`g$x!F@s8dQjQchI8SL z&fL=CN4v03F6~93`k~8>d|GTwJu&usPp4%Sg_ce@v@{M@^;?sr<=@qIOx2o4Hv|x_QkCVp9|v8W!J{N%QbH69-c6MPnC)mL0pYdQtU3lp08(? zPy2lbzCL?=*9*4=>bqknAD(eI241Nb)wu^5Qyaxz6LMV;z=+}djqJwLp}oM{Ii0}c zy<_LcnWXR2+0d!qKC^)~8j#fvL3kSNddMy#D`V&MFrq6L`ySE%u*B!5{$ZSoI<+@! zFFj9fD@)+7EX zC=0{b9ekt7WP-ORGqS=nzSZdII(8ogsg2lH`;bTZEfH0v?&@V5_em8Vr z6CXJqq_UTL&|8db+>OUKyCEGfYiA7TR3KJ$adW|R-Vp|$-8%sLsKnT?)TB;c%+cPGq znnf&R^J!_vqoomFh1nL6s$4zjZ@`zM$mG;~>C$M`rDA+6z}}40J$*4ny5CLAsna~Y z$dS(93o9ZfA!S2a-RY0ZO`!Nm&3vI_%T!d!4Zm2XsenFBobK(L0RNZ(b=nKx8=PFP zH*JB(ZRjq(53GY4zac>;zy1nH3zmBImS!3WDiNi{)Ai9q-p#h5K|eoNWI=5S%H%Y3Cf(M-f|Mwl|)f1v_POUz&jNq z{oD5NBLbBxbfr?d)v(x2BsC3i!b6dgU}m7{DvcgH;Q)G>;&Ltl z1m;-qT%k06XL63Qdbm^vLt)(w0UX`9*Ys9%MzRc$(DO9nhCi#RIr~xT%uDWNW>{fA z@62Zp%wBXw_~*jYxX89T?|s3U%-dO+m;D!bJ#ST$_~trElH2p|&$R?9!~LBaf5wm3 zbxZCm)8$@hD$o7(sf!n_|Na`e^+53-(9df=?tneu0`6yOOHXjdey~g|lMDq~X^Mog z8d0EhF-|H^?_sg$a6fw7wOL;#TV&}J*!av@MN_L0Oo61PJHzcoLsdyrNW%Opbj1Nn1$RpKeRBg7M_5ZH zUb@lG3FsY^L+J=}LuuO8=$RjyD!(PK&~meshqBI63VAYF`UVNaiSfhWmw)~>9Z+Io z!+d2Z>aMC|QZzrx=l4u4R^|-g#f}s}&asJ5t)!yBlAcqf>!F>Gc@3Rqrj51`OC^Ti z@XB@3>;^-YI9*Q%2fp`b;l7u9;l3|#9@*aSn49|VnM{nnlbi4Fng;%FUXzA=Q`b9u zM|`cZMJ4Vx4=#Dv;~i7m2}F~@qVG8B18C_P8AcAgh#asK$tjpW#$rj0Ef}UP(f$U)o-$+7Atpe~YPRm%7^r3i8F@1xB-wLh8%dZhAJGQX%% z#qH-S_eYM5iHT|wt5cx7CW4g_+=N7*fmB?BsvQoL3?~O$Dk)LA9xN$i7PSu};M-Fj zg{yJ{qJR&c+vuRO)uv?ap=NM0lOO8L8qa0#&7&*kPjyVGgRLg=@iNJZKg3D(2pjwl z3>Y@1=}NdGNqJp?lDdq?=DplaW~}x@CS@p&zTQaM9=@m;GC|ULhM)EJuOSb@q<5+; zH;yF_l}EP6bPQ;UfJpt{h!oa;@|3%S zqcxd~Qt(4;%mJemB-CodM0{Am-sS{09Cg&{V&IKLkc>9<&Ap?sOe%xpyE&O`b29pp zYV^>pGt|pih8|&@v??WSjP#7tbZJW1&un6;V~~yWJ_i{6OzvO-QV!aht#0|%C=XxZ zhd1VX-Mgnm2lK#Cl=-}P=geZy`=pHm<{qofFd6c+MiD7P1|BijaY?$Wv5m-Gy0Y5p z>dfh$m_-CUs#T0pVhgmioA@KnZ_})AqaO-0<`JvpVIeC8@qYtN%85lutCSx&CD;9t z=`0H`-=PG%c+=%RKtehTw??YDlb7T;_rIQDHnU$a^G3C<(;9Eqi!Pq7UrT^@uhL!5 z=Pq6ETXX^`%?H)KQ%Dz`<887BkKwn^@QgPT^DeJ%GlEj(BZG8giTQC~^Bo6WDRncA zS*DXgtuW)1F+&tM)%IJTK3!8wiwUcd#1kqKn@x=91Cql>aMp=4cP5$hA|ELw@=HqM zTJpGbj(MpC3#5Kl;D_zR0P=7nyc7An*fLp|31iyB(0jd{$ma@8&yId0P+&=X#gFtc z`wlkkguEm5)sg~+!Z7W!RO|9`!%f=ZQ*`$mb{+*diPpteGu!NZkI2Dz7|dm|W@@5P z*4AkO;Evgac=87hT8$;LNu` zH6RJm@)B)~C{bB#(+VJCSFCFNowYWXV1KGm!CA0H#qmNc%y>#00F zRw->n+1%s_=cheW_yEEGF~JN;{%eBWLAgH}%h|{c6fxwna9ouxQ2o@&`e)$QmlT_G zXHEC_?jX#*U>EF(FP*^K_NT0`?%RS5$nyo?F>U*lYodkQ7{5|vtY{@y6aaL-i5z8W zlQ*a4V;aTE%P_|Z?2o7CN{Fps&e7kE*isiXHif_!gGgsaLvZkIFIi_y6}?%(VaR)1 z`J&St^O6CTME%6xA^9ISpG3nzW{1oab6E4rcr1Ed%)e4n7@C_o)mj!4v@$08DS4^o zI3U`8#+547w{&b7S3g=8E=E#*u0+&SiOY?8*Ib><@hm=!%X=;fuE99Tx02eA0r2Zv zxZ-80j5ygwF7Ao<<51g_*9+6dRb2Fk*$`cclkLcMH1T^cm!NO|OMqW^tM%UFeX5au zmej?0N~K_P_*G1GL;wjOdgSPF3CWV=_enp7~hFHgjWnzcMY#{xq^j za-SW-I7o6+C@v&~`iPqWnS&$p3#!T7t`kV+R)nD+|0WERavz4xbONfGXg%=WQ~&fe z=jmhF2lbM6l27>~;DXYLOaIp=^auknjPIG=7r#i!ql82d@}t_n5l;JD6hCZ$Xa+f& zO{o+%Pbu*a95{VExawF2J3lgbcxT45dBoGn-On$97vPMil^Vazr-`R@w)GBoV`(>B z=&;*->kr=MHGavdM<=mD33Lv=n20rUooYhLhN2)g$4#SwKzYn_y)aE{*2y~vo~UFR z%tE+$td?Do=u|29-Q5(+AB9cZ)r-m0res?`OWa6R07GMHARqX3sGQt7c(9I_u%le9 zok^~@juUID9r>BFkgbw7=L#u_W>tM4i3-*0HwKa5d(>3-qX-+NTNlzRA(x)NTXJWv z7r|S~_4BV3{-foFP@j6+fTuA>Zr%neuOWXjf=zIdrJ~OH*ce|Qm;Fa2^YFCl;HDv^ zF3TfW-(c1LIm^`RHrp$CQ|iEkZx(RK{Gf~Jo@^R{8^ie_L~Z|^EX6sO>rw1C_G)iI zv^xIpuZYa=u9Hh7+})|r@=`3Ps*0-`3Si0&MOGOXAmh)Kenl#MNtkT%C7Ge@IO5$< z@IhW)NHa&yi;>W2JSbllP)TEAd zI_}*w6OYxq%_IV=%$t4vGe_61++>KN86L!OyJ1f*W-pQLuFixwJk$X@BEJOuD!%y{ zC)rsnV4#iUHCG`mFbh)6Yefc#`;%r(>9}<_0DXR~xWJ6nt?xVPZXe)JdJI_B4%8vv z|0y>;+&Nmr4MoN?1&r$27X(_g4O-UCq^-QG?_)r5P8g!y2yh72?A@u|v(a#wYn{21f0K=MFgptzSljqElouOvDX3|(+{v1WYu-R@s50>2`;u@h z81;g6bGUhA9$QScb!B1+cY88bH;{6gm(@SrCO%Gh9;1pX2|PP3jgB%4oB|ZLJ(%=9 zj>ugIrdQ?%YpWUtuD14i%Hj5JL^%uC*zET2ivzWmhtEa%(sg-q6cx>{h#yj~F1dzU z+en~4w$5FNcf>t5g?~?sm?`W4fnewiAQR{$@7(k`@kq^HyRUD(;z%1)cJW%P*xs?= zdKm6_>a0*v{ENb!^=-L4m4zuq0#Y%We*6g1xX@Xzg zQD+BVuNM?IOlR-NJT=I-)RLlPi%NaI#=0%H&RJ|NCr8`r!+~wqK-yJ2->+Imz*#&T%;j4id zu_L&?#yo6tl17tgV50ETm@ioVw{z2l?!lDIm5YWCL4USpD(-XA%M8d-cM%ROU+A9M z#c^0(YB`fv)%7j@8ZJdZr!7QW$9`60v-$CD;Y!Z#%3k;k!@ls*3+`mi5H`z&8L8do z>6V5C^80ipe^0r2A`Z8#NoynMtu(A0nBvO52y1Hj_3Msc&e4e*l zFdW%MZ@i7eZo0gt@2k#JgY>!0u_41-lK@5&3tPB)UNn01qdai&kLDSh%aXui%zcx2 z&hvS`W9y-o=;jYEIH2!!JPoW9x4C>T8&`Vne59+rRKLG5??^OGd^+}^?E%-!4=dcQ z81dVwoW3YgI_WYQ3ZGSU0_R}BJTrdxz87*ST7U+94vqQpxDEZ@8}YBPHi-P$X^YY@ z>LMkFH{3z@wv=HrRRw$gdgc0E_T)>MRrNMScOWMD&}CK(-BLV8f7XrV?8y@`^U_#S zfI(wA!_gLyI;(#^9l^lwjsni@s811}wv7a&zr81Si?q2j0n5{gjnfxa2xD-%3_bPt zN-YG=xuHA)PmtLES7mGpJ~mp{_t4S7D_=LhjG7vXTC>c6BEZ)<#u(Qg)XZwqu~jOF3NHxT7%@qhl>;K8&tAM~R=jpE8i8Ky=o zu1An3>cG%9_v7vLW3YEQk}-$(X6@mFtDh=vkc=uM%>s67BRx{p7$(a=aPSDVn$5xH z`@8hC>xqM;rle%?m*!&v4(uTU!TMCp*?>E{AJ(iOvF^{Yip@f&|S(% zMG>oMkTreF84@)T7Z>G7nGCA93e7#)u*-OSCtiF@lLn*7hyqmRGtDZX8e%D*?p=!8 z((&oDwK9byuDbFKi%=MrWMVTyei7E?VfSKJb+Ip~AC+9zC|gM9EjUlVM{&~ftMfKJ zjs2!9_KtK{-u#aHqP!$VU>N^V>gr+!5cR__Zgo-_C|74cWg1;y>3(fD0gez2){QSM zXY0$YL~F%L2RlgUVBV+g+R!y92UK}-6r{@Y;vAQoifl+2P#V=wR$}^EvIdx)Q6O{v ziSp^sTrQ}!^x=28Z?RKt8eh3-`Bf>NC|uNn9p!%3PWwupBz~m(c^`fRG zFzb)}USC{d<#J*QdFp#1`=>Q=4VtXh$-qsQNa#13=owTCXT|B5$`eKx0CldcQOa^c zW4>{~{Tko_S2GQ|=Ke{Wu|&6TW~t<(bhcRrV-@90yl7OGL|DpimJd1RgdYPM{=)Of zEYs_Afo!ws&TK)~-cuQ4R*5%@SVfH@Z9rzq@!Jwf}t1UBITaSL1D5 zmN8YJd3I=5C^mZu-( zUM?VC`?F%O(@r;>{aGWm=|#qA{df)CzLbB60@KN;w#E2u=Gc&aF~TcFAIrORr_>=s zdd+%yfVGNuJ?%*+QJjeBnx*n+-k_vvZ5zm%M+Vp)zHKiAkEEY132Zho{~yBMDy;3U z`xbp`v=oZFwzykxeTzHA9ST8;yIWgaQ=Fj19f~^?4G=uIdvLb|JNeG}e|w+j?2EH* zlB?gHYsgED3TTs&lx+GnljMA%U~=!Kp~ss%Cqgh0K$) zpL>A(e!sh2lrickOR2>hP7^Dszn;+2S5^JhU!N`rvH7CdsUD}UF*D0++-+mU&**tw zJe=BSd$ah|^}hF3=(2Z-qS*_4c)GIj*N$A!$Ly_3og||7-tg`L9^O2+KI#-FS8JyE zN~x)0J()RxXKmGb$zll9?YVESh86**SIIO|@83zMi3?NCZA!!sF!cdb z3uxET2t{OpUF^r>;C<~Bs+NU+c-bO|+d89QT%7M^z3W&w(yrg=w`ZqHY8rp~v?JsW zA{&~2hdtoy1w8tq=IeeM11#2U(NpgqO`y?{QMIyGeEs&lYjZ@UZhm%hPW@kfQ=of%+K^%_dExnvGM9qn~8u zJ_e#isA3i{I9-+T@8JjYw#>>K?i*nH58g(bwp?DMs)Izs>V<1zb{NU8Cc;B9=!eIW zdj}Dx9*O4Wa}S|C`@qi#_SQ((sO$P|zF=-086QHP2qgN&8f-$!(N3nK zDbGWN{IZkqo@rVln#2Ec878Wsb4g*bBm1b6RVxD=%QA-J>^ zDLt%57K+79bC+c&HckkK8E}9pqVyWoIqhnSVm|mhAE#YM&ksmGkoY@wt5HgqcKKj> zetZ168irg4l zuNP{=us3{t$!^BI3g=jjh0-L#%V+rNU#nSZN`Hh{sqFaL2w~7jdY`mVPn3p>sr>kn z6hrkHXzYOR=Vh$IR#o5cMpb79AS=Y&s1Je)C4x8Z)HY>f<3bv z5k`Ym5PIgQ`G)o}?4k>DGce}$PW2=XlYB81H&EPNi9)F#Oi9q*7&q3B>kg(=frp$c zXM#sA#ae-LqqttF*;lTIvBANBxL2a@SjX~~riUaSorTfeiA>Wa0GU-Tov(L`U@Hq? zc#a>{{RvjH$ZdgU>eyYe+(ta9eAB}h3Re96Ols-gQilrhhSc8|HU${S+taPjR2Vb3_l3Qg^z&H8?8L;Ip@lN zRg%q_T8Vz)$_w|eRgL}WB=FiN8NaBl@&GHE>Y>aAarhE9P)gzH<}k&d)!ZCDd$Spq z?BH5YyCAjNHN0DNgY#wHu%ofWJqeQVO=KS`3v3w)*D$0Xu2I>NWzmI1Y*XjkQ}!|# z>XE3ybYzt`WgAYwk*p2Xe3o3@6FRv{pMdr*#hdo0{w zZZ21O9_!lxjP^&XpTlso)OoZTxYZD0<^E*OUt|yuF>ecS;1u}GXT5X$f$7*mrPDg} zf7POFQJ*Q#hkoK5rPDE-wcPXK=LcOBzeg?p40S@ls3o>0^EnX*hnzlFGtbUX7oSpc z=KH|6ro}sp+z+#CA3c@$#`-s2^AB)UiKZNhm3z}W@SuD@>b@TGm^|-bqgQ2pUQBo8 z2=WD~pcYaN?=i;l$v5PD%s4i- z!Y`MfpG-xcm)+XWdZI;dgNcWnU3TvtN-H=Ox2Ea^<0zJUV(4WmQ0A2{_J1^o#Prlv zi(uF(?lW9nJ38>)S{2o?a%7UmVI8TyTQ^a_@`6UAtAN)1r2KgNwWw8(P~P;3A|)JZ zX3Dd#54@_Av-$T8@D%p1J9w$r6b%seowv)_@=+j*KCW}d{$+HG4`0vz;Y`4&)A#a_ za05AQ7XJL&n#?d&$(5;_FN>d)*n|CgrPbqD_SW&n9oPQc6|Nc+djnx*jB%`!{8O%+ zcew0qMQtJ@FC{)6d#bWr!FH6HDbU3t=P@mb~~yG3HTS*jo>>0GnZw zq7apjPWs%5@P2@WCl`jPp~Bq(qItG>OCx9aZNhGMqg=Z2WHwpI&R|KAM>C?eg~NYIh|GEFdb1A zd-E6l^ZYltDY{cKx%tc~1N-kZBzT_F64fKWxn0(h)>Lv{$_IXdTE)nWUXN!QRl3;L zvF(~`g?-vnza-FTiZSxjCRMM@KYzpw|P_P=_M|& zh5*ODhfFQn12bV&^7lgZ2gAp|Ya=O2sWsbd515S;7rGhRhPjjTR~#$n2%c&yQXBL% zN1}B`7S<|E?c=C%p`A_W>}__R`nLs0kf$*zUBwZAv~T ztg-1l^<=BmorNkNG1t}L8Cukc&+VL-YU?_>A!^#=B3!AM0`gBZ+pX%myzOUi#&H6A zWguk8u;|zl6fZ&)G_`*y_mC6x%m<=+0`ryI>b2A-tUW=;5x1!6M9w2JAdbpV3A&Li zJ-waSSf5_e;4w{U04*redb;26Yxne_KhJ>v$sq5YueG1DwoK$_Po5^Vu~1f{=N3u` zInu%_D*E*+`Coru-qO;VzG=cu6C5=iO=f07Kc^1mJ1$r`^s*Lt@Tt+KXNF;FgqMct{j^mQ!M+AU4Vo1a@b=$5! zvRjh@Uoqn1QA3qC77_YF73kT0#fKTCChB9EeA*Ok7i1>fTO`~P;5^Hj*ZpsCiK$h6 z9Lbab_@ydQn3d*1%)ncJg>*Se5yE zV_Qd%933JERL`M?a8S2Fje3h|YQ1g4y(zb@DP^pSZ$)YiT}BI?`Fq8e{r zw7F8q=xGl5YI5+v(}BF95fqP62vz9g$d>Zgrdq(b7&OpPhITgiUmSuWjf|yDb3uO= z_vtEGESExocN`;@&MIhQ9^mG8Jh4zbWSVH)_rEo--`uSeZ%cid5M&6w=kc+^bb8zu z@Sd@nGW@Lz(-KX0b8#H(=@kDEUPRE!u37}iv(J=H*No8TON6H0^vs!_&unq}JX`~1 zG7tPAcYaR=Y2VY<0;ud@bVywcx(6{n!vA0q70hw`ib?(Ycc!O;u+N&%T`aTV8XWg#1dUhHT1Sro{PT znv~c}O$WPB(W0#-D`F|X&_{NfE*HCoXiOXBQe_)P_KG3Kl%|ExCvRyTE)1JGN&|Nn z|5K3geJbbI%2Wz((ELc3hEPBXG+j)eQ#PqbJrf`g*T_oIxgT~{-}@GoTRA`0ip`eW znC##i1vCHwmXJK*DJEeg@kgcC;5-|ElmumBuTF-+pJ1kFQM3kBnMrtCJ zJ2^^hsMT1D8W2UDd`@jnMA(Yk{1ekEQE&2}4jN=CfqpIXsak4{H%&ndB<}bQi?-kr*?;Frl&jZ(gKH+U6;_D=U+wr{dBzJUzJrj8X%q;0nX$irzYL>bE#PZb3Aq{_IO_Wla`Kj!1tQ( zK79Dd!;?11C4mQ&lvjL5&;RNB{N2}0+Mn`K?`aq;cpMYuCWEZliXC${A7(BqRvXfH z+H`*ziU#ASj(zF!MSmeFp{_8woig&ZWc(vm(g^%`SEjwGBu*2oseq4cY&uoFO%>r@ zh6PI@gff+t%y-s5S6or`Aepy>#KmGC*3k=)F}r)2{&Az}{kcVCMf}_NbG(TQ<*cM> zC*#nq`%OZ@D3eu_D@s0`rXON1+N4fe=G9Vw-w`YQHaNV z`Q6Xm;&Y7~L199)sYTahBsrrPGT4bMj zpeb(;?mZDxkvs}u?^~P`eKH8LdGbo_I#s%)@7tDI z0kM^2aYB&_!YTL~l@_A}7{FnCA$Bo>$ZQ*l;q%HQ!8*Cq%Px1&BSRG@R1??KB%WQ$J~HG) zW+(eU9h{~DLqD>NLJAs`E|A9m@l;8uDy7ram<^$yzg8TB>fsbgM?Vc0MWKJNKZH^k zAfcP1Mn=Pr3^O%m;%Q%j$_kHmODpU6pRAPsI10#AL;~LlM@mpyPfKk8*hT7%Q}@4` zsWJiAGmx|X%bucCBe(Q(a?D*eE&E1*8TF&yv(5Ec{z^~X_CbA0)5_zdM$O@J<|WPZ z5BC45Io7bly+eAVzfxUjkYE5I#K)0||k zRl@!%!}}r0lkh*Zd0JC7`%WHKnNUf3-6Fr817q9TyvtonSt`gT*1wj^xi4bTYO8P zKpAejQ$y9JpG*S;bohk0l3MOu=q|Z*bYXZoILfYh5~T_~YkC{L^%C8h{`=w+iB-9@ zqzSn4r}ETz7`18ILgDFzGs=csuaEBptWJO`0QnA*-Q3cOpOGke znVn4092f$1GN!F}6n3$Ll4y~r6(0?Su(5vh3=qnKhjHin@Vm{<@wB!4JbhfhzdBq? zRi~9(-#r@s^CDX^$MJ4l)#9L4!(1m7R>@12nvQA09<#eMG5K0e2|mNIQgdh_*Hne& zj=MKq$l3rUPtyQH%iVfI`D@(SV|#PiUO>QR?pP$s%Q0-CD}FH45E9SY5=aaPBef&0 zJwD@)TkDXYu5D>%SI)b5@H9^)th>4RTt>vXy_}`Q>tr;Lr!uO{qS9Z;kKPpZ8cp^d zXHOz!eck0XTI?r5cedQmKrj=1XsVFWSsbvG`ulj@!C;K6zKQ9n5H|1D|+a(vBb0bQa5Q$CF@mX3nX z9oO>fFFEB9)IKeHve$^J;v**H*s=&n>Y)`>YQ>Vjcv|TSHfs7Vn8FgNW3DO=c8a|q zyH}^5DH3iv$h!z%epWPI+G?p(Jxm);Q@=&o?!`mVx;$Uk1etN92m06!a1z0#@A>LH z)$8Ye8SgSSh!Kiw-D~!$?sDD9C~+n`8B{kwt$#ID#?b}0Q(Lw~KBV8^an}Pns%lpC z8-gJAELp7XTGz}C+SkRA21FkW)9Uiot@QnrI(1WQ_UvfW8~-S%9lBTInbzlcy{=5txGkm{e}B+o`+~h=+fDfd(D1xn0*`gCO6dNo*;2JO-DJ=R z_}UrkgLzT9Q?qO5V5&boDF|7zx7m|2Gi<;ft*@aFNC~itEUpXn6>W&QxG8HqXfQpz zl&SskkZwTbR)XTMQ;w`W0P*AQn5CWS)U36v5F+&jRePsIYWBUdC}taFH+WgAZ2)1b zzVx)zj+_u#PW7=bnY#_OoF)E?h80XpQRL=zQqwzp9=P~|N5<(3igc};gRuR)qgwtYhWU(RA;-9YcRjD*ylcDvC5-raFNMj zB`)&xY52cWJNxbO>cy^kA_1d<90I?Ki08}uoc9oH1PbWOfOg_2v!NnD6kWhR~M$Ttx3mop6Pjr265|aoN zH7d>oSiT<>e8UU5j2KjSeUF@&seD^Q50kG>A5EI(dP(zYC_4qZxuY~FA8oz&=LOqR zdlYKUtVnLBS7XK}CUv`&CCujF!Z#2EMn2#mjE)+K$l(B@8X_K zJa&m9uT61OkZ}y@Se1;njzkjP`$8$~h9S3qHZZA$6N=JZWn(B5mn(Rw9v)s;OZa^6 zm>BEez?chDZXEyt&HexUw$|0q*mCyV=b-44{7Yaei%A_4bnI@DO|A-$^6pLKps#7? zVoZWR+~XzTLRhOk^E8`I?&w3dc*RIS!*oT{J2jWFu!2UdDJ2r?ivvUq3)BKJl*P7C zo3Lxl%Cyvy+0QtOXPo^vO}p;gAT6eAeJN7Y|J}Q@Pi)>4@t?QeW$~arpP^r6k;b`c z5_x(sU*SCSZ*R^fMbFhzxHf9zYKwe;FG^<;6;8SNG=53>e9SjNcx{jF!PL$`j%HrC@ZrS5Uy*N9!PVQJf z+Jra;)T8x8SyMXE6q&R0guhLk<2n_Ejn7sIatLUQqotPmz0+w!!Sc9tl4DKyd=Q9A z>@cqfEjB8~JfUm`p=q$S@1@kSmb(!CIJt$=#X z8a!~g^!*x+@=_FzG=Y>~K3(TetVQ^Uq&WfKBzg?DIwkRQ8ym*B%$ST%r!XAGou|7{ zOul_jo-t|6H%@pt`?G_$rk{ui0%J-&WI8qd#we~oqr=evi2dN9433q1Q`S;m?w9qEQ{Wh708JcvMTQ<(W#I%%Vo@A&} zPW4_S$|Dqb9#lzws9zr$G2HaC&}uc}r`jtkirGAo#PWiOU;&?w&RRKb|q-}glG z9w9^d;f8lnZ?)^?2UDAy=NObg(!Bzu#hOPAFNx1X5mRbWk0*?xPe?Z^F@##@9rF3& z?mP#9JP$gTc)omVn@WhUmo>Ef$nI?^ji+fU*|%tmMig>ZzLxbB8ZVq6AYdlIm_8ZG zEG-@W3x`_OR^v5ksk#M&+%E!Z#W3gWG#ph83liK!hRGRN=yEI1Y$LBrmuL?4SVs5G z#MdeY`m_8ubqMV($s#PO9w_I-KTVD|n9jFfx0`XVPAowg9bzk{0g*ofe9s@@rrRwi z#?Je*$#V%)LJ66DQRn$W*$ICm>%FU#$2Shr3AYJ}*AGr&ZX-6d9Q498how`f4AhK{ z?W>+PjalXdwzfBV(kAow7WcI?vsz&g7-6*uhx^Gnk80J8GMPY%u1P>BALDH;;>gT1 z%O^GvMtjZ@Nz-g8?qbKf*ZT3zedvNaXJr0m43mi>#dx2OP5aSiM07B#r!hsyn|f@l zDlr#Ik))FJeeW^M{!T6u_<>$Kc@bV`XOe=YzOw3v{5Cf)G|UrFbmA*jF<-7pFScMb z@ArXeD#hRz?FrPTJvJA$@ZM9x7Sy=?An`17gDil|cSbEam zhZ^0EWv|U0&BT)Q=y8w#?ON-|b&lBAQ*ublWjTm>!31iDjm7<&4qVW%gNaGnmxP`V z7PNUS-YIpK#icArHRQau;k_L?l`l?OjKs`5baOW0rG)px&MH(bp`ihp&yy-gtofw% zET9@~g&0nd^+q;J;MI`vzV#86m~TmhQr0QiS{$nXrxY^RC~G&HK*+v}43cwv?=kZA z28x^I#OM2PxaTi00WXV-`+is0XjA3lRCs}5l+^`5`W6DQTzdO0Xl2vpSg$`2XLVu! zhtNc^^G_kscL3EeMx)N{x}f{Wv)fNWh@2q!W!FP|HT7dC=M!Aw?z&o{z--_DB{YE) zwb(Xa7>ZdoNaNy1i%+b=So8@r(JFcYaw>uy$@}jX;o%ZW`WrDtqJJu&3quc*QivoR z+jPGzMAvNWR}SO%L>y={;4<$<_OK6R}9DW$c`% zfOG6~S{{LHm(nNY%$$)u#H1iS2z~%M!JFjGNNOO=47l~+r&#UpA-o=OV6&XOA$nQF zfG12FI#NVK1GmDVxl;e*fSVn;rPpbP9?sc0a3Do46bPp%Y@3~O_N87K2JSa<5r&6G zVmb|4J}gV;Mb-M8Bon`(Y@KXN4gXjz!m-l2u?$O2N*7W#)a~x0M+g$h)jT1&IXb<< zFC|*l`m_0@iBxErgz%ozaHcTaB6H#g6wNBWm)W%QtmNprKE5*)O)V86if_(Kswlin zQx^cO_TF#JKr~Boer(nM(+YpHu8pIFi1&ESo~{CldhI)F^_6kHr%62Z=jipeQ&qmR z1Eqenbz*}pR-z8^M$O0U(K&1!VaCYsVD8srDg$1=n4OyWgH^2N(l#t&hf~hPNY`>` zfwd-sql&&DJ6Q3wOlx@<&)H$jBJIn~>iEg|dhlh%{Fk1fr#b5>wZ%x3bhm*<)uU6% zA7gbRFF*g^ds$7m4eoL2T7d-q)rZv6kN>Y*7?NgJ^32%ot#TMNb@-4qF~>@I8KKsh zW+S|7FhrdQWJDX8;K4eQv!%+|A+YkzgCZalC*JZBZ< zo{`0SYQMQC=M^!kyx!L);LH-oU}+~tU+wVGMU!tUmDt=gnWm$co`EIl+j;-pISSN; z;jrW6P^g+K3o@T?(9ucb;gb*lPDAJIY|NU&I^FF4i4s65Z%C>#=w7-B3IKvZ=`4DL z#0p*6j?F@g0RL&{Fh6XXpjvFDyjOa-n`$Mg#96`}eY2Tt;wA>r>z|loGH+eEMY5DG z-`_P=9+JYhl{s~FMOTdVnt-p!)lMwdc6#kQ|74_hu!8Hog~b#%NeogHXOX_Hv%2c7 zZp@c+=s`_vbS=VJ-TJ+xLO{_iYn6@UdiD9ycUGfM+L>*hG5drq^%QGE3HWWy!tIeK zJDXNYEOE)`7RTSV7uBe~v=qgsng;xsvD4+Usuy^5?^E;J84EukAWdHcB+})ZQ!8cn zCxdeMdjj3d6hc6wCmEK{MssSxg~leyQDX6W(q_1|BbO(7XFJ|0RLQ2(9SW9A-T%~X zfTPm%^PS1qHSi&?1#>udz@_KoOXBTw*8@EY0E6o>@u}M>u|&bao-BpZmP?gVv79l_ z!V>v{`^G;FN@YHe#~CB(6495ZCcQFeb-ErbThOWVOzL1a1(LW+pi5d#l#&gZ*skol zu)zEAMdf8X*uHiSeTU#$B{xR+E?XwCO26BJvM>9nr4fub@bVsncsM^@aQ&^YIPZ7Y zZ|eP^=Rc*m=8RCigsfdmW(YL9BBJ_Y1td0`g6uh;dpTD(Jna^ep-nz;;!PNPW5dkk zKF;Rzy9BYa%KXbp$u;N6|K}iB&F)GW#l|Nl5vnvRjA;mPO&1NSGREj?Q~Dsf?n! z{Yq%2&*=vaeE ziL941{cy<3ZDWS`DKZOIROfmjU*+gPBSMoDWuuU3sxzXdJIB2_lRBsvSJc&pUn*3p zT*(FM zt8x}RXEIMQNS*&EWSeiu48t&VW{b!({7hmTo$p*VUZAA%Qp|R0Uxxy6Ihl{nHk?}r z#6HTR*k7p=$j;8bIyepkQtK9ts15*C1>*|2o0A!*y4XVD0LDo>+jjwg-aWiS=rb0% z;%$;bbJ5)-tv`XM`j)eD`bfDM?H&;zOx0y;FMU_7NlT)YL4_bL)VIg9?a!oONZf$< zAbA@JwTUXpC@V8?KNA4%oFVP;W_IVqgj8{1rY?URYxf9^W%P1+>}|{J3vZ; zkGPcxOr77A6y5FTDWM7df!fHZih*>nrPN5WVU{TO<}*iUU+-gnOOJx&Q5+FlPULfA zKnCo6IWvCfN)f?Q32L?nZ4W$W&aM;X{5kq^?~-PSYZ-)`Kgtr2Im(fy>ckaqr?xTyx@mNIy zX@yu5h;+1&mvTuqRe!@hol<5b!5m4@$0ajiXQ9s%3=hF?wCAT)%mW)`g+1q$VzS0D z*sF{8z6X*niDpH{f-%`5EEF|~ssK{oTI(~01IqRMEjc|eX+AiFtINx7s5O&G{#*Qv zjL$if!Fv*(OjS}Hdt&Av10a}1bAvywNQHj|=7&!}t+@hZKLEdf=*!w zP;Tm%Ip7Fa7>N4wV!J5AtZHu|KjHJv<^1(J%e{EB!t^4K=c?%ENhDg_iZGOFQO}4k|HpdG)MUVJmXaV>*=I7Fx{*u9hs%C*&gY+pSi%FaVrHM#VvVzkFG zP;{r*tLAo9`zF92l($kYc#V|tG!c@0?7XIEbqPcEW-J68Vv1Bz7Q3Ia_*$q~xqp5s zVzShqlKMO0cIFNz^pXo)|0bs^@RsKyQ81OH zCoVxU>g~1l)9=sj`wuL=axpzYBl2j1x4*j*@oC?BDmMLb=l5%yU>BRDPix806ceP*+k2c~Q73m+kP1EE`{KbsFe>!P+cLbPhGJDP-p7(&$1!5GtAi7m%PnKe`62(yS zYLb0Gj7fkmY;Fat0F>LDg2j%lhyX0SgOI*HU)OrG0|Wd3u2)|&v$9;goChG*f>n}+ zy2QFgF;qjpI!)XUXR~l6vuZkNU81SvP`hEvxK%O$+4b{LK|Ba9KHAuE#!78jPeR+(2ht(Pu46oRfo17A2Ka=C2SNjW16N7 zMWoxKO~WU;3?kygFpY?JChE9}=XOh*IfiJwoY*%pE~$gA(;%SEO^g7TjNJeCz@2{n zk3egO^b9cvk}u2ZO+u1}aOTKO)-?20RiQqDAqE8$+}8J<+JCgZ)gYo+>zk-`kK%BI z6!M_YO-UcQ{+z#`B|LdNQ>j|+Qnj72=jgZQ#zYH-n2FB|&gxEgl#=pnnvST#>0_Y{ zhm>`w#?_*tFFgEG@ko9*#e#>&mH=QShnGz%ZLv&JL{zxJ*OOH$G^w1E_0x2nYj@E+Im`d1^AICd7Jo+o2 zt0JYsoORTuC?Uu1w91Btd6t)>OT9ZXMoS}1LhMB>3!+ju8DTR*`5pasqHe5k%Jovy zMZWheDXq3xAAHd)i#R(l>c;+gN>QfiT&k8YXGQ`?ttjoY4llSJ@-kLu(bsf-Z#g{l z{%F+obmI}1BQX*tmZ8VGXYi*cWw+zdd#iU&ztxh|PW5DSmU9i>u@*4A)P+bp%xNE8 zS@TzfFaKj?$MeS!CE`*c3rd)uA&cTQM50v`fKM2GBuAL#m9`x6{ zr~_10RbDK3$na7ajQ{Yn*8fu**On9bH$(;?gczb^xvjFAjicwKULMR^dnS1mM}mLaBDFZ&6pHxg%!%_`nHI0yFIb(a#ULl zL@AD~wG5o;*TiJT%fqQS*Y_q~5r^wKpYF>RJ93&^uMw(YGm82OaXJ6%WaPUr>7++3 zi*=57Fb}Y3eoNJ1Ou|zk@Hx~*nN7)hW?_YGeo1ofCr&GY9FO;qGD^|{Xbfb45v2kc z7;vg1Y4^#ugSXON6r_%tOS+JG)&)DWMi&QpkYT7fZVVTna`A9{|D2Bo3z9e8Wv{e2 zvv6wn4_~JQfV%_p_fG+7W|9)Y#qS9>-2A`4n2 z$<&Vf0s}GgQ+E;@i%G|km_<&No9NiM&tj;BP^6DYW%Km^zD582=hOIK{M7krGYkp` zQR5l%$6gDlYBW3Xl{--vpU*pDi8l|;TOul&{W2?^j6XWByeu0#%{@n&ncVLiVj5T)q-6flh&l0m94{&lItq^UW05s8lWI z>vh$!-9ncF7jagMixL$OTwec^6I%tHbsv|n$O5;`^w*!S81VQRll|KGUE6A6s2vRY zA0j@=DpRN^7|{s~@1Erc+YM6kg$O13-yMyJ2i7>EPX;67!4%Mf_}&P`IJ21z_L#x< zTSz&teGxvA(|WF=*PvokPJtdN6gqZN8}RnS#Xo}VZ3KY9i6bH0Qs5)jdJtyMOewy) z`qLdE)_ndSY@Ryc*%&4R(2YFp25&b}Skq>YAxdQye%o+d+`bNql zwJoo8;PLZcK^1dTj5JyLXm0g)lVaE5jq`B@26iPS3xu9145mQ4oe*`Aau+^8w z#vfr0tR*D^J9IMj?uy1|8YbO|Er!E|IV4?-=CD=?weA$E-FDa zmmr-Po#?`$(p@9_n~q9gBi+tH_(a>LnW!H{=q3M{5yC7Pw9s>Te+}UWc*wlbE^fkE z=8c|8BtNSfXRuWgs`IM;eK5#?O7u`%up`&=wxGQ9V&BSN(rwY^&XGzEO_F{c&r?fX zU0t;Su}iJ{$P%*IqN)FdH!JDrb)Kzt?cuuB(>Q2tV|FO3{eDXTJu5@fc!<|=Bx`6< z-^Iu-9dbUkZ=V53Ruib8n+akIfL!%1^nzO(lmst!-%RNwx!!e@H^aj>v&bXmPN^{W zmZDcXj!C#muuV+HuC5P;esxn}Qmo77n0Q{DZVmo^A-2E27~I)mM8fC)Prx$8{@Qpl zdW(9@AXbsR13ShIC;Y98l#Ak7Kes!tSdZ)$$#$&NAQOcoWI|vjC(NU$<7r!M8UArkOsbbYI4q#*d{2BD z-&z-@stIw~uy(4w9EG(f2}c1WI-rk1_aO=&U=hZq!$qv&==wYvx&7zu=TI*_cMoji zT82rW4wLc4&%Fe4w#Q>$51yI^H@(^*z&PlduF)0k4V36&YWqo4Vj=o~l65b26N1g~ z;kUCsJ?ucqyW2@H0LW1LJZ0l6r}|gOodD|P^i@xXg!2uim7Xn*n*e*BUEezzR=1X$ z^9*ked5eJ*vg|WL5VgcEs`vLte0kRAru_e&tgbizr3tM6rm9f^O6l-ZhY1Gw#y@&N zc#8fb{Vc~#jiNI;sCBj*?yg}b2ep>k2i&rhn@V&?-a6LYU%O>Ktn-UhWvy?^d3{3# zZ`^KrXF?PAbiv*0$kHp6xUhwvJXNMeaMp5NBWJ9S?{bhSLk4lBwJUocPbC=h5Tc-E zDf^3pKf9;!k~C8|-Vyi~S3ODb^v_08Rq1XId2!40#)sV*!VNLLpLZ=a6eb!2ajoXI zQ;&Mdlzmw{4HU+Lsr!Jfh_qDshfMdPZOBHFV9G|n%fm;r*Bx&<&~`$$1Z)I5#3V}G*wkq!DgW;>)*s;_dZ`wkN&QIdgbEfHL%c6#Tb54vDy^L z4)L56k$8EVAn2=Iux^q*_PbVjGkq7=NGwZT$(qNfTzLxXc5cMJ91fBJvICvUs>&t-dZ1GaFiWYC2xd-_o zr?SmMMpIO7PB_ZhW zoFM7v3$0dN23Q9hdRwjZ(eRct4$=^@ODg_&I_`bzMSZ@X6F9{9`~-Zu?h1lTJUF+i zW`#gU{uQ=X{`&Ah7kFvf1s4cTNjVLfRA z62T=$Uy8<~XnDkbeJ@b3*y+d1K2dWw_^7Vm9RgAQ#KreZAs6^g5rc+iHYa!C=P3X5 zmwG>Ru%X>P*-7l&yxG+uEIRWDb2%%k=5#SKEd9OcvXS%NY$$!K2jPqXIKgfemVJStEK4H{YGU|fk(L2dMKORNp@9E%-K@m@aW1xiWqJ2@Z@5Gw^h|eJc zopp(k=^3Nnf4g|URr|=snf3X_7Afv-xbC(E5+f!G%R;Zu0u3FB9C*Ejzdydd$Baoh z%pm<1z~;}>OZx!MBn$R($C09aAfYPfOqaE0#J_Fb*tFl(axFtBACP8xVcQr^furFBx2AzgxHH<^q zj^&TDSvc74^b+(7t0Rr4w;GV$ua~wH+^5fSYu4f*d1!f9vJzge-MG(r1HfBO{7ov{ zv7C0Dx_NO3Kh_x!;XK_#|D-0}{!oP8zTolYm$qdV{`p;UklD|DBh4}F59GG9cKrv- zvt=;VaCi~H;bMS7J#v#0k>#h@0b4a_&^v<|{oTsyxK;*S`t4SJ7>}|~K7a<1UZwJ5u_<+af z!Zwx0Nj*(r9!Nj;qBx|l#gU(w8xAW50fPhC`5|Q&t;*I7#?oTETZ3F{OA<{YcRuGy z@)w?5yKGCcmEokqg2a{PQ236(zf<}!ry$Dg0ZuhJyVbl>Lpv}-(MN@9Mk1|BzHrtCqOp#j<~=lM)A17gc^c-nZQ`J97fc?N z^K`!L@Y8ZI{4rkfE^PY^KyeQ^(JntQs+9L|rf79+Z}BwNPvB5y1v__`uBh0BbUpkM zGPF%w&=||BTUv15$AR(d)I5w>t==P+Mhp#mmZ(?FCp%@d*s{iVKvfCa@!i2{j?Q-j zCbkBOK9?|&0(2l7?)IX)Go{%Z^ku}E2SvibpNk9e&DrFHKMi54i1?UT?A+o+{Ks(2 z-x+O`)t)}+Ig_+|D_sJP0m~!;js$UIH%A@L5u>SCIIT?7#YRC>WmbT1eK&Y&7P%Hji?dS>4RCyegrB3NKKQ*?n)<<*vtFpg78d&-|~E&|ALIY z8t|C^clizC9z-$rSADe^`=+PY4<{hRPVBAkhG;YAqksE3#WW~TDxAx_=QYwBJOB3g z4@^=(*o%C*4Q3;16zP!9c)z(XrneXLkESHU8RYry6*jea((2kp%M%tcWmfU7QrY7bLrRhW1t zuzo&=qLO@3ht$$DFy->|!66tik>HSxYtU%_SgZc{COxvFu#xOA$G|G_>GjCSi%M~k zFoefm>BBP8GQ4>=eR!HIhrd7XJpfrgCs66J@^$M**g-zv|03%wquT7ct^KsLw0MCQ zm*S;JaF@2Y6bck~*AT3@w8bS*G<c6n76$ToT*~QbKWSa0rAi&pXaIXPocsU&+5@ zjQieu?X~8-X48Zf+3B2Ln&+=1k{K^$*{rL5ICU?SV&j(y{IzXx)BfkrqvmZHRUqay zc=4QH6KXoXHJB}E3A?ql&ShGc0~=f_7xVo3WWXrEp2gBwi?@N1elBmkBu%*pq92}V z>#|mb#gqZScU}^XC@+5dn#!%Ev$)NAJV*6bK-_#TQm~&e!gt~XVgA59_ZB=LaAkf4 z#d?z}^KhD~AXXhE(O{4vR9fzT?gZ7H{zG-9JYuYx-viA4$*~VZ-Nw1KJ(su8f7V+r z_%5Tq`f>H+;UC!W^1A$g2E`M-xMGUYPy-3dK@0Ot`Rys+T@{M$Vi&r->5MMV$H$lp z4&PMPn(evA-nn9Zv|U$BRrM@GEA@|*^5drqxDU#c-A&TN_HP}QPvxj|>0%QmLQJkd zdS{FGohP^oS_YFLNI>@U-8t&!CVf?s_KTm;58GG8`fC%a+zz^hlbr3bg%8wU%U@VP|W8 zhp$@CA0I$HzA2P6^8w`^D`C6Z2Br-CPxP?~!^8ur>~2@RI9SpB}%@pufMt7fv)6h&#OHO>B7-+e%3a(hJ$G(OAKZM4Z>R6Li`Z?pc)L$0! zC2QoE57;lb-QuvX(@TdlXcembMyX+!JuXffu7z%%f)DDGhAUs& zSbbw$3psky;$6=nB$dRw(tf%}VrGLBGspEk-zcIqvvTYD681R!q$A{p!P}eo(bbca z9+KScen#l89a3vIZ;y5KT1b3%XvFW?`PlpO`@4_@pTqd^bpF8Ofcr3G$Wa{a_Gk!Z zyX$njE09-u{p2sf9Uf&)$uHJg;rC%nu5mxIxbj_{kzDy22b;EJC!Lj+(0nI_)#icG z+>~4DeS^eNUvevDDx#mCT-12q57fPc@bjmp!a&UZsFQel-3HOPau<6{1lD{NURV%l zwSr~3xB#bDA+@>Jm_PnLa1O;j%&CJR=SaRw^{0&jWq2c*11fWiD9Vy9cDQLK&ji0{Q+3lx{LSj;xm$-`)--TB zs{V7U7-=m{FY^}Cz}qz6C+lEDSgmjFlOhmDuA`*BL>%#Iz#)eOxL+KlN>b@t*QWRC z*4EXv{Ey(i^@B!$gwPX-ui}P4Y&4=`YeHlC2Mqp~EEw6S4aI zgWfGjsZ^Z=(8KZ4nR}f4X5Iw$F@0$y%EK)B{Li%!jA6Q!>lpUT_|m2m2vNw7ba5#P z9_hqdE9GG3mHWB*nv-1u2xZ%z^fGUcpg;4CE`_?|fdRq=Qi_X48**C#h31WcPNi_Y zI1g`by-Gy?g)qXcD&B`&7EjsQIEvJBGiG1p`Um~+01fcRK9JvfA|FWT$6YE5ivxl` z>H5F=G~rM}x>h`2S!uZio5ctZy@vF@TPvt~hbUCM6S7HVxu}f)bcpWpuIT?Pa>!o{ zl$$n~eh19*p5f=Q5W(=U3}32mjCybz#5CJS|97+grE7~2(Cmx#4-zbQK#Wy=eKS4U zOYL;f)msRlL13|0tSGW9HISlEj{@e7LRt2Av@<>jnbLTJsas}jkp@|BFC(~3vWl&m z`BITV<$fE}KGYvF;TMQ)!#Yo#$Rjh>p!aNk?P>TxLAMIhJyCTmw=rY-{vz?L;5@^* z%plN>Cvxn_*46S=YOIIY>6Wm!OV#GelK{WN5pRmhiXWC*>{&hlYxMA8g-lIqp%1|G ze4Aw9ZouziRjSwd5>WeR!bs+3GqQnhRr!1@)ur7dY{h4+?s~+z0@@P0hCz4L?p`qQ z-v9m7v?BHfv-f)S_WVhOY-?ou{RwF>7yhns*4Xbdp+K&B6c5Y_4wYw5E#ZF+NcntY zqL@-lR`=uci8XBzhleZXbnu|fFGd$aT!%bk5XoNXt-GUrWuW^$(PHpG*A_}99v4lZ zozB`YYTWfrAu{}=X!C3jk@Z&Y$)xi)4xM6-3Gu*34>D@c+?q->Lr4M@iAoZb05@m5 z;6iI3T%v=7tk`5PDeYJLhm*7ckwFXq1@bm zg%GqV$}8MuY4d^}J+I7yHx(p0&d9#x=PxWGPWLK4(mRP@51pH?LnSv*DC^=m*5a-h`J75vB>QV&^s1 zVdEBFYxZ(T~G0ZP)DKcraV+|)|| z)zr57>My{zS}P|LM$DV?8RXiRg=B055MCWDR0sC_bB|L*M29#*EU7RXErePyudIkq zAy9Hj^|YEtE$FKK8)b-G<21ea?x1;KXrC zB;iqe*`F8Zj;)}Y<~d&q{_?Kn?aJDphMM4t6`hK;;wJv{(4MZn1+DC?%P(YzGr{Qf zIV#bMYHkQ(yrC-1tZ70d;(JM5|#~ zJcbUKW%VJY+pTXooP+0bvkotsOn4!0TdNE}&T)=eNVzgZRaTVc%7?`=92t?=Kw~rcUQrEHUk@!G<{(c zZvvUw$z?$j;aL!V>uKgpjU$_l937VqpNIpZ#mP>9K+N)Z|*d(Vdz~C)u2Aj@jaIHF>>J**xz4drE6AZ)tcK zJ+Exs_PMVyRo&zp&mC0%D8gy=eltPL^ z8Wf{OQpI21@Yx5mNiLGR49iFByG7GS4raenR8QDIB19L1g8_2W4-JB@Un(8v*k7Y6 zpMy;0=OR=UpZDw3`@A3xr^e1fzZwHXp@Y83T?I7TrC%PsDBjqeja?YBP>r!$pIswg zQ;~65Y>N*zV~C>@Nk4an2)J1=;u0PxdD{s%Mo$t_?{6Bb^tT#>FY)g#_A`2Lr)yFgIVox57Ld|r#nnP&RCE>>Cp z!K*1zvsY^AaY{X`(U{T)glYsvl1S9ENO!BWbtvC4_ifZiEU zK_od$?aiv07VmL+VRVKnG5Ot#P23zrvOmvEbQj79O=Z+(INh-^G?3!u02y|NTT&ZArt7oL69FQ0b78i%;&XR< z^0^I-cFjZ73;$s9AHjb*Md{V1$2^{UH&v9(Kz@e?AlE?LT}{9^>-4MEMyPb~d)TJT zhcj!^Ua7?KJdvd(<7*T04-1RSblt924HZ58<=ud!q=Z>-jE|d$zwH9r8;Iszk^vz>>I_ zML|9*mEebfe5ff+++jQ2(aVc7wWiSk?~2V8TXM+MYD2<}--}cU8>*XVsbjvRwxqFn z{LL}8si?4b-AyqtXD9eWDuaHT1W76r z;VRuGU}F&ys0g-EH6vTGj}+MWh0o3wA;8%OvDAqm&wY_#p2CQuL~x$;^>1`Gd;*0- zO(`Djz8*|SFPajqMUJNuWAQsG_>A^Y<+@FgeN5%0-FrOh_N;V&3q0`vN_!n9~~B*iOQxqx6tiwg{WYUf(R8_S6pzjL{^OQlg1b;(M+y zX&gQ=@|r6eJh8YVS)tzIJREZoVbdN~<*B!?$(57&<)2^ho#m%hTE7}hIbe<@<)Ljv z^u3BCY^{FbFf;pxre4?HJzE4F<=;HQYzET&*?90%ezk-he{C`5F{3e5g|9(ysi>o4 ziUiv)F@%gl<2}$02-Q_R3D?xoAF^5$v|Dj_X-mT+OB<@-$+Qt#aRs^siRL1IBWtfS z_EG<3&W)KUYFVzWPh3gIn*wDcl}sQ&eI%CaxYVVq$mQ8<1@)o=B3|8=G9|G3AQn#i zI2oGf8>uMq;qb$`1;=BA2mIB63sQ|`3+PO1XY+z@+dg*akZWkUIPX>04CK6JzOMqq zvt`{jyIOoMhy)m@YONAHgo0~&g;ikFv_SPaHz z(?6*EHysyQ8TXa$0Mlj)7LmSh%v%yMJ-DjZ!rKvnm|@%cKP7rW2rIn^Z#qY8y84xl z)weaHRx9Gt1KY|Grd69~FwwmUK&_&vqlHcC}m81Z$`?<0=eoIy$`9{0Sp$}?{QxQKx*#}9@IfnlG%oWwz}CI$w%~S z(5s#S4&rsW59)h&nc{3kIZ|A&nV?d=d9H$TyP34|CEOuqIT9I^+5;y}QwF(ebfdA8 ztI%f>QIs6MCsQqEQlbOk?Sbw2 zm&Fc`|AhaOAN1S8jOesDVA-IJErAJW_(1A9shT0AAA&{8UJcJvVWbnEnHN{TD3p+3 zSH_D@KZ_cUW4b0Kf5pG9eS19HDMcHdvQiJ0RYyYZCd*xK9`7 z8R#=S5kXAcE~h2f@|g8|oO%vg0MrhFz0e9L*>e6Xjc@rcN=d?l6sUWg>ok~wt_5ZRqxbowf<}Ov6|9*ZXI=zcH@%p zt&$EQYQju2 zh}Wte2roFP#Y zA0bZ2&piAJJbR??>?y~T#U^X`y8f#P?zPl(k5TIk*-BdKA>*FC}_bT)68ULjV@_oB27yaMa`ZrjQLTyCn`U&CHF;@CY0W=s( zA}Ys{$ospy@ifOf&knc4+m!(9*dz{fSqQR&$E7tOu@JPzUg1hie4eo|Z$DYeO1ix# z)RxjWQ7A)%Zd3m^Ucm?gR=N|)<`P`E{!QbzPt$UCWUBG>aGTCZZoJMWduxEt7DZg=mRE$iA#?L#Va*#=C*Ko8SI(M^5Y0=E2J6G2tm`WBG1+xRUWM z#hV8ddM2x|D8FIfw33NZv1|xefnVushW0Y~12yX?5a)({7h1Snq!P;`h+V!u<5~5; z=S5t&AS3T@Nsn(&BM%na!?{GqP-9q%@*a|38YteeoO zB<$+!G``GbxMuRoqSUw;FtT&4wq73|?c>QCYcOUZIu!^I}OWudq@&MfIAnus`W2_P%ewlmX|- z9Q;Pnu?Or5T)TCdEk^HxaenYO6@ZLwbl7Z8E@C|V%ww|nglUG$Rf%Jx8^dX9ywyRt zGaF9qS$dmIB2_e=ZTQix&m?0|;CV1!`;CcwiYAX|Y!d*GMK>gHmOR`D3+K~NPX-S*Qq@ixg3cd z+&WlmnbiwmcM@A!gSp|)ettp+%Odd+QZd&Si)JVMMb=ybMv4d#{0^b5vI`>@-`Y-r zIi8(@o%!2r9k`#Gj25g^_{k56CFiKabcpV*J+0O-&z`d{C{{}ohO(gGxtlepDOn!e z+KTpCS)!^;Ul@qEE|QlifurAi0pD;UP2V|s+O~R(r8fmhs1H8-L$N8kq+S#|*K#Of z7)uTIIKSl&Weeg#G<3wTv&|p2QB`vxd-rI{<}?l0^iu9J<#s*sh8b0UDF82g*pp*RaV)h{nQ;=Id)v6kxJx&Z^I-wX)Wd!8#y$ zL2ps%-4RJRsNs*0RjGR4yH<5vH4N#la;uk1;W3-Uvq)|8*CFW(b7hCzaM{?+i2XzB z=pbVVA_>D@m(^;=rHBqJ^qnq4L^S#a{I_s2_W=o`^bb1)9Gs3w(B-HJb zXMsJ)=b?5%G$5lMfYs)Rup5?lUC#G9c`+G?SNs(G}wnZADXG?1qVV)Ij z45W~zEnfea1b04$YSK{xS<17b3G2n?FK!e8fiRn8~W?;V&G z11pL=9l3M3lKdLk!?B`k(q>C0XJXLyuUecR++6vpI~xSuh6NA&Ct6zbUmyA)D3;FV6aV#Nn;+snP zf;9p})iV}!*>vt+J51a?ykXDnk!Fc67X+eRZEMBG6H1J2nWZzeA0q}W^eIB?uZVN% zeA_4<6U=|z(WZ-A$$l!KNrH7xJtdwlo(M{F0|pC8$Mnvb7(T4cJ`H| zZcp-mQD*X&7FrW%*Rim&0r9d@e*Gj9oKJ3QMz1QZ zpJFlWr0IuRZUr?AT*o!SJzS*mhj9BOFN9x(S@@7LKEVA)}U)`lpLLc4l;o(Gl zo{qZ|xIEYOmLq%&cy4p3Kn-U-Yhqr~TRuocD;5ZQrwTYMYDKdas=7WdORuF?VfZB3 zI4bSuWBk%RjfUlzM4~)bR_1i%BsyN&~T>1Kd%^-^^-lh zcKRE+TgBNm(e!`z)k~ zZk14ce%fs_Ms;BAN7NYK24)(w#s07SG%e%EiyJ4|*MrWD$?pK34v3G^fYGHlNjg8v zGdb8#jnvEyX&Zx!Zl!@dRJzIh7l>sZ>B{gbUK z8Wm7R{G8RST|3o&sf)#)v7B_{-Hb#jLVC7!cA@4oYl4vyX=84gCy(@>rsPF^p872* zQNdtzrY(@D-lVFcd~Q^AjXqjV3)D*gNPSEFiS@5XYA3IdMl&#qR^!8bwq{-I>t{~} zDi#<5x;V+&LZ#I(G_<@SHwT)T^Pf7iu)^)}&r0S&Z>QVXaeXgKs+z(&J51f&8?5_B z{BKCkUJ6Klfe%5?oIc z3SRkB$g2W!eZ~p8gEfAhg+=tny=0%;<_}O3#DS41uY4TaeL{&>m8J1!Q^<+9f~Cxx zy&9hI-P4hI<#p1IN#d_`g?a+1t;%nQIEdtUd{c= zh|2>nINM?GmT7%{P{osZ?>jY;wXS z6|Uc*5hAQ^QGq+~+G^-+==IQ65pL=|8gHo&w-q0hq zY_$54^`wjP5R-MGod21atGHxwk$*UIOsQ=If7It<|LCyey5z&<6yVgCw%lbxuj|g=IAiNdmC(C^LaqRA)Wlz9JR;!S zlsI8P4jld>@AtypUPURnv0pU<>>L{ntn4cz%{s8skw(Y@xcj4KJ#^t2J3yQy-b`#l z&5-qnUQyL7gLu*8M#YoJR0&{PQ$gC|yVht^qx~kZWhscrbd-5k_eObYCFT%29;Z9S zknvZTMqHO*4zvq$VNQ&W3Hc`Wm|#@#)ce`IS>FIfseK}d?21W`^4rz)t*pRet*XqGl<%m!R3hwM=4SpZaHKQ}cVFyVG5w(&!dJ8S(7T*BKQOjal#;KWx-l+DI>L zO}TPk>FZSb9wHaa9?j}~mn_>)Y(xRrP)x{nLhfpRf$znW^ERg5H(KggX?@N*}5#Pp#3Q=CDPLC@O{HyS5)zpgCL zXk?=tVIx##IbWtL-k2%VX`DxcE2L_Z7h0s+JDf6S`l){E+tgZY3D zrPZuzVW>j4d;Hu4+P4XhwOY8Nn0)jF^Jk~UFK)znq3+=WLA0f2&FL)-kFb8S(Hg9@ zPBxHAS~_4u+~z3`^l`+^Z>$@b0S7(Z5=`WCe{LNppA*jS506ELn}k4%75|#d>+kF$ zduoDl{+{fB>tjP}+$53(LTCu|=RNOMvSt_zhJX5@eo+3g+IETrO+0!#_*ngwO5yF0 zzn;m0(roT5cV`Pk8kAk)?@Und!;^p1Ly<;jrn%ZAp{Q?DcMI1LfIP29FPl-&8>%fK zAM%EH#g-D-*CPM2i{vegZcU@bP`z2tD}UPFIZOVfTTJ+N`@9X8xSKcdIUpX}rIGVT zoeW{5wpSAL!@2;3$GxXGQf`b$b$55xWR^(0d1*htDL1oBzVs?`+;!4Fa$Q5wUb$jX zklV<=w}0Mx;h$|4eeQaEU$=8B>MWKbz?h+!f_WFP zd3DW6#|v$D39Y!dIECAf1^NJ|>KpTaVy8iIn2Zyl9p_lKv-Fz)mMZeKMX6pI7H}867QP(QG$gMO=jbX{@RyuS41zeTf8&Bj)GhI z!No4N)L2uc#cfONRU9o-$3f?*TJ=++Dsk`eiX1-iRIiA`7JaaSy2Dty zu8{Dz1W729XG&{^`OBDjl128R_N^JS?hEt+=Zv)Pu@pfw50kwyOTbZ{b#G~oOIHH~ z2x+dl?V%h zY8Jpes3;J*xnIr5h5V?rU5bTJVIGj)KS>O(zY$tCzUIjxSQF`My;ko^xk-Du?11TA zmA~h?Ztst5xd_tV{_Eqif>AQX!B|e7K77n>{+h;ji2G7yr*V-14gC zL7yEfpS=Rl%IQOQ?x)*FMS zn>=uk&+nG%7Ry7C>V9IrHmRPy)B9+B$t{HOW;EtI=C{*D-s$Cq$nwqXQ zx0p>l6atx!VS9DNx(b}?xkAKiwY}8H$x!P^pTBbm$`()b!?E}aR$IH|>{J!(%~lpO zOt0EB!yn>|GkBD1ZK2198JmQ}eczFm3|a4<5a!SZPgfG^R0RxpKPOlW;Xz)r8$RiBuT= z$}Cc-Fx2T^I)CA^@Nu|!l)#XKuG-jqcn)|@C()239Y9fUtwfG!60WsX!pRcV|9g=$ z?|XT~8GJSNSmp(C1Ev(RRX&JhhtCJ16ws2U*f=*QYl?X<}f1GxA(Os%}n65 z%}k8TzvGyHmpqn+k5Nd$e)8)BJnyH|4LIFhl>HY?bea(5MRG&Zq!%TzE1r^M-?V;m zoQGPp&qGZz%tpix4T&G1wFcMrt#<8aF~ORar6F4y*M{Y3hVO(E8amhbT|WlV=1tB1 z`yiVKpFf6qH}U!QC>L1E?e1>%{3K;b&rDVYX!_hH6%3oyz45OS)PWP^(7k{vxK7?8#jL;`-Q@$`#Y z9p8t$$YITC%3HV&v-fs@l9f+tAyQ>y@b`w`!@Sy6EY6>1&Al*PtlKRJ>L#Ng#vtSV z{OFpgqO~RqYyKqJzMA|~U23&i{DKgMXS2NQfOQ}hw7cCYb=^}_m&10GDBt8|^IfdS zz!J0U#r=;T_J1YR55W*0DNJ7RjM(o-cW@)R5>MDRp?)FQY!WiOr+M#u-~5Avjj~)k zmicqp6_PA_yFPff>!{=sC}F#)%Wa%C)ZPVEQ(y54R81OQHd4FqF~GG`nksb`HOZ!< zS`PUq{({%5+4I+EZ$?8BH}P^X*cOrIv32p7x8oFd`;n<@;4QmaJP6s{6*#o6rsTV^ z(JbzFDc}>{CA!)LyMEFv9&pdzKE}8{0~Vxf5B^GfykfN-&O&!H$zwv<$9F&E^qRQZ za<{f}8NJ(HvCADJ_N8{Bhv&jPzs!0cy!9%manVVy82pU ztKILrb&rOli3X@=El$!ZysbO*}n-JY-Ka)`gBL956Jy}R0&&d z4GNd^sY1=&(L(RLX#=m;rB|+EzsAF%^ z5GVCG6%@z7ze4l>f92Q@>Hu4Ak62t85O43GtXAv}E_@FB0?};qGSf$zIF1|dIysr{ zIA$-1qPxZ;VlC?xWU4F+e_+uKNyfF^Q#W}pZRTfadG};Gf&VEBu(KzX;Us)3dz;^T zTXER1W0RbfzdM`JY9JEDzObYvp~%|JQr{QsQ{3t`X8m|#Gd33Hd0*t@aLFmg?Pj}y zKdQKG4SI#>U}Qg~n^M*Cmivo@NGbDkjdfpF-=Dod>heiVz8ph2ZHdv@DezfnaCZ|3 zP^HrkeShg*Z_6saxO?>;WxEA;WaStX1R7DOp!k23up*^BUdeI9{i=MG^u0~Wil#2w zQ~(ll$91)n`~-S;zNr`i|A%S*KWOV1z0gtOxA6Oj7AjF*sJ z=v#F19TmT~R3}s6j?k3CO-6m? z`js4Yy6C`|j<{EV)U$hsp0(&7hpk^DO%opY5cHBYcu0^UcF2^S;N2 z&d~GU%$G;Go1MS!89RMK*X}U?2C2Wh;jA4@Ai_SO)+gAgrE|#E{{OX1%Skk>pDZfT zRlZL$iviW;TakCdW&b96ui+DBJ>MgGacvi2*}Lh0o@O`FOl$yip@m6Nj;IX3sjc_u z;z-o+2OPURfyrB2vR?MMKo(`5*tQ(2UIl;uJ4E{;a{;JD3Wq@k^L1}lxaD$xxJ4_J zdS^CLrUQ*UmW&C$lZwjBRQ2fh(#u^^_3m{cHbtm?JQO-0V0OzSe>>jF?^}$nzr5j8 z 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 K2^#30f|Euf( literal 110232 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

~xmlG23>WU6;JnEqD)@091Vq&BzzKqyWh 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%0D@`yclHz}234pfL!&RuOadpn@4WkeoyG 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 +#include +#include + +#if CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA +#include "FactoryDataProvider.h" +#else +#include +#endif + +using namespace chip; +using namespace ::chip::Credentials; +using namespace ::chip::DeviceLayer; + +/** + * Allows to register Matter factory data before initializing the Matter stack + */ +CHIP_ERROR NXP::App::AppFactoryData_PreMatterStackInit(void) +{ + return CHIP_NO_ERROR; +} + +/** + * Allows to register Matter factory data after initializing the Matter stack + */ +CHIP_ERROR NXP::App::AppFactoryData_PostMatterStackInit(void) +{ +#if CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA + FactoryDataPrvdImpl().Init(); + SetDeviceInstanceInfoProvider(&FactoryDataPrvd()); + SetDeviceAttestationCredentialsProvider(&FactoryDataPrvd()); + SetCommissionableDataProvider(&FactoryDataPrvd()); +#else + // Initialize device attestation with example one (only for debug purpose) + SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider()); +#endif + return CHIP_NO_ERROR; +} diff --git a/examples/shell/nxp/k32w/k32w0/BUILD.gn b/examples/shell/nxp/k32w/k32w0/BUILD.gn deleted file mode 100644 index b19d08fe44e325..00000000000000 --- a/examples/shell/nxp/k32w/k32w0/BUILD.gn +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2021 Project CHIP Authors -# -# 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("//build_overrides/chip.gni") -import("//build_overrides/k32w0_sdk.gni") -import("//build_overrides/openthread.gni") -import("${chip_root}/src/platform/device.gni") -import("${chip_root}/third_party/simw-top-mini/simw_config.gni") - -import("${k32w0_sdk_build_root}/k32w0_executable.gni") -import("${k32w0_sdk_build_root}/k32w0_sdk.gni") - -assert(current_os == "freertos") - -k32w0_platform_dir = "${chip_root}/examples/platform/nxp/k32w/k32w0" - -k32w0_sdk("sdk") { - sources = [ - "${k32w0_platform_dir}/app/project_include/OpenThreadConfig.h", - "include/CHIPProjectConfig.h", - "include/FreeRTOSConfig.h", - "main/include/app_config.h", - ] - - public_deps = - [ "${chip_root}/third_party/openthread/platforms:libopenthread-platform" ] - - include_dirs = [ - "main/include", - "main", - "include", - "${k32w0_platform_dir}/app/support", - "${k32w0_platform_dir}/util/include", - ] - - defines = [] - if (is_debug) { - defines += [ "BUILD_RELEASE=0" ] - } else { - defines += [ "BUILD_RELEASE=1" ] - } -} - -k32w0_executable("shell_app") { - output_name = "chip-k32w0x-shell-example" - - sources = [ - "${k32w0_platform_dir}/util/LEDWidget.cpp", - "${k32w0_platform_dir}/util/include/LEDWidget.h", - "main/AppTask.cpp", - "main/main.cpp", - ] - - deps = [ - ":sdk", - "${chip_root}/examples/common/QRCode", - "${chip_root}/examples/lock-app/lock-common", - "${chip_root}/examples/shell/shell_common:shell_common", - "${chip_root}/src/platform:syscalls_stub", - "${chip_root}/third_party/mbedtls:mbedtls", - "${k32w0_platform_dir}/app/support:freertos_mbedtls_utils", - ] - - cflags = [ "-Wconversion" ] - - output_dir = root_out_dir - - ldscript = "${k32w0_platform_dir}/app/ldscripts/chip-k32w0x-linker.ld" - - inputs = [ ldscript ] - - ldflags = [ "-T" + rebase_path(ldscript, root_build_dir) ] -} - -group("k32w0") { - deps = [ ":shell_app" ] -} - -group("default") { - deps = [ ":k32w0" ] -} diff --git a/examples/shell/nxp/k32w/k32w0/README.md b/examples/shell/nxp/k32w/k32w0/README.md deleted file mode 100644 index 8d87de3f08b983..00000000000000 --- a/examples/shell/nxp/k32w/k32w0/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# CHIP NXP K32W Shell Application - -A [chip-shell](../../../README.md) project for NXP K32W family. - -## Building - -The steps to build the NXP K32W Shell Application are exactly the same as in -case of the Lock Example. Please refer to -[this](../../../../lock-app/nxp/k32w/k32w0/README.md) guide to learn how to -build, flash and debug the application. diff --git a/examples/shell/nxp/k32w/k32w0/build_overrides b/examples/shell/nxp/k32w/k32w0/build_overrides deleted file mode 120000 index ad07557834803a..00000000000000 --- a/examples/shell/nxp/k32w/k32w0/build_overrides +++ /dev/null @@ -1 +0,0 @@ -../../../../build_overrides/ \ No newline at end of file diff --git a/examples/shell/nxp/k32w/k32w0/include/AppTask.h b/examples/shell/nxp/k32w/k32w0/include/AppTask.h deleted file mode 100644 index 28480735fef81d..00000000000000 --- a/examples/shell/nxp/k32w/k32w0/include/AppTask.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * - * Copyright (c) 2021 Google LLC. - * 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. - */ - -#pragma once - -#include -#include - -#include "AppEvent.h" - -#include - -#include "FreeRTOS.h" -#include "timers.h" - -// Application-defined error codes in the CHIP_ERROR space. -#define APP_ERROR_EVENT_QUEUE_FAILED CHIP_APPLICATION_ERROR(0x01) -#define APP_ERROR_CREATE_TASK_FAILED CHIP_APPLICATION_ERROR(0x02) -#define APP_ERROR_UNHANDLED_EVENT CHIP_APPLICATION_ERROR(0x03) -#define APP_ERROR_CREATE_TIMER_FAILED CHIP_APPLICATION_ERROR(0x04) -#define APP_ERROR_START_TIMER_FAILED CHIP_APPLICATION_ERROR(0x05) -#define APP_ERROR_STOP_TIMER_FAILED CHIP_APPLICATION_ERROR(0x06) - -class AppTask -{ -public: - CHIP_ERROR StartAppTask(); - static void AppTaskMain(void * pvParameter); - void PostEvent(const AppEvent * event); - -private: - friend AppTask & GetAppTask(void); - - CHIP_ERROR Init(); - - void CancelTimer(void); - - void DispatchEvent(AppEvent * event); - - static void FunctionTimerEventHandler(AppEvent * aEvent); - static void KBD_Callback(uint8_t events); - static void HandleKeyboard(void); - static void BleHandler(AppEvent * aEvent); - static void ResetActionEventHandler(AppEvent * aEvent); - static void InstallEventHandler(AppEvent * aEvent); - - static void ButtonEventHandler(uint8_t pin_no, uint8_t button_action); - static void TimerEventHandler(TimerHandle_t xTimer); - - static void ThreadProvisioningHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); - - static void ThreadStart(); - void StartTimer(uint32_t aTimeoutInMs); - static void InitServer(intptr_t arg); - - enum Function_t - { - kFunction_NoneSelected = 0, - kFunction_SoftwareUpdate = 0, - kFunction_FactoryReset, - - kFunction_Invalid - } Function; - - Function_t mFunction = kFunction_NoneSelected; - bool mResetTimerActive = false; - - static AppTask sAppTask; -}; - -inline AppTask & GetAppTask(void) -{ - return AppTask::sAppTask; -} diff --git a/examples/shell/nxp/k32w/k32w0/include/CHIPProjectConfig.h b/examples/shell/nxp/k32w/k32w0/include/CHIPProjectConfig.h deleted file mode 100644 index f7ef2f6c352cce..00000000000000 --- a/examples/shell/nxp/k32w/k32w0/include/CHIPProjectConfig.h +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2020 Project CHIP Authors - * Copyright (c) 2020 Google LLC. - * 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. - */ - -/** - * @file - * Example project configuration file for CHIP. - * - * This is a place to put application or project-specific overrides - * to the default configuration values for general CHIP features. - * - */ - -#pragma once - -// Security and Authentication disabled for development build. -// For convenience, enable CHIP Security Test Mode and disable the requirement for -// authentication in various protocols. -// WARNING: These options make it possible to circumvent basic CHIP security functionality, -// including message encryption. Because of this they MUST NEVER BE ENABLED IN PRODUCTION BUILDS. -#define CHIP_CONFIG_SECURITY_TEST_MODE 0 - -// Use a default setup PIN code if one hasn't been provisioned in flash. -#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 -#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 - -/** - * CHIP_DEVICE_CONFIG_TEST_SERIAL_NUMBER - * - * Enables the use of a hard-coded default serial number if none - * is found in CHIP NV storage. - */ -#define CHIP_DEVICE_CONFIG_TEST_SERIAL_NUMBER "TEST_SN" - -/** - * CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID - * - * 0xFFF1: Test vendor. - */ -#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID 0xFFF1 - -/** - * CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID - * - * 0x8009: example shell - */ -#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0x8012 - -/** - * CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION - * - * The hardware version number assigned to device or product by the device vendor. This - * number is scoped to the device product id, and typically corresponds to a revision of the - * physical device, a change to its packaging, and/or a change to its marketing presentation. - * This value is generally *not* incremented for device software versions. - */ -#define CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION 100 - -#ifndef CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING -#define CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING "v0.1.0" -#endif - -/** - * CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING - * - * A string identifying the software version running on the device. - * CHIP currently expects the software version to be in the format - * {MAJOR_VERSION}.0d{MINOR_VERSION} - */ -#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING -#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING "04-2020-te2" -#endif - -#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION -#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION 42020 -#endif - -#ifndef CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME -#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME "NXP Semiconductors" -#endif - -#ifndef CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME -#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME "NXP Demo App" -#endif - -/** - * @def CHIP_CONFIG_ENABLE_SERVER_IM_EVENT - * - * @brief Enable Interaction model Event support in server - */ -#define CHIP_CONFIG_ENABLE_SERVER_IM_EVENT 0 - -/** - * CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_TIMEOUT - * - * The amount of time in miliseconds after which BLE should change his advertisements - * from fast interval to slow interval. - * - * 30000 (30 secondes). - */ -#define CHIP_DEVICE_CONFIG_BLE_FAST_ADVERTISING_TIMEOUT (30 * 1000) - -/** - * @def CHIP_CONFIG_MAX_FABRICS - * - * @brief - * Maximum number of fabrics the device can participate in. Each fabric can - * provision the device with its unique operational credentials and manage - * its own access control lists. - */ -#define CHIP_CONFIG_MAX_FABRICS 2 // 1 fabrics + 1 for rotation slack - -/** - * CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE - * - * For a development build, set the default importance of events to be logged as Debug. - * Since debug is the lowest importance level, this means all standard, critical, info and - * debug importance level vi events get logged. - */ -#if BUILD_RELEASE -#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Production -#else -#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Debug -#endif // BUILD_RELEASE - -/** - * @def CHIP_IM_MAX_NUM_COMMAND_HANDLER - * - * @brief Defines the maximum number of CommandHandler, limits the number of active commands transactions on server. - */ -#define CHIP_IM_MAX_NUM_COMMAND_HANDLER 2 - -/** - * @def CHIP_IM_MAX_NUM_WRITE_HANDLER - * - * @brief Defines the maximum number of WriteHandler, limits the number of active write transactions on server. - */ -#define CHIP_IM_MAX_NUM_WRITE_HANDLER 2 diff --git a/examples/shell/nxp/k32w/k32w0/include/FreeRTOSConfig.h b/examples/shell/nxp/k32w/k32w0/include/FreeRTOSConfig.h deleted file mode 100644 index e9e79054721218..00000000000000 --- a/examples/shell/nxp/k32w/k32w0/include/FreeRTOSConfig.h +++ /dev/null @@ -1,165 +0,0 @@ -/* - * FreeRTOS Kernel V10.2.0 - * Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * http://www.FreeRTOS.org - * http://aws.amazon.com/freertos - * - * 1 tab == 4 spaces! - */ - -#pragma once - -/*----------------------------------------------------------- - * Application specific definitions. - * - * These definitions should be adjusted for your particular hardware and - * application requirements. - * - * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE - * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE. - * - * See http://www.freertos.org/a00110.html. - *----------------------------------------------------------*/ - -#define configUSE_PREEMPTION 1 -#define configUSE_TICKLESS_IDLE 0 -#define configCPU_CLOCK_HZ (SystemCoreClock) -#define configTICK_RATE_HZ ((TickType_t) 100) -#define configMAX_PRIORITIES (8) -#define configMINIMAL_STACK_SIZE ((unsigned short) 90) -#define configMAX_TASK_NAME_LEN 20 -#define configUSE_16_BIT_TICKS 0 -#define configIDLE_SHOULD_YIELD 1 -#define configUSE_TASK_NOTIFICATIONS 1 -#define configUSE_MUTEXES 1 -#define configUSE_RECURSIVE_MUTEXES 1 -#define configUSE_COUNTING_SEMAPHORES 1 -#define configUSE_ALTERNATIVE_API 0 /* Deprecated! */ -#define configQUEUE_REGISTRY_SIZE 8 -#define configUSE_QUEUE_SETS 0 -/* make sure that Thread task can interrupt lengthy Matter - * processing in case priority inversion occurs - */ -#define configUSE_TIME_SLICING 1 -#define configUSE_NEWLIB_REENTRANT 0 -#define configENABLE_BACKWARD_COMPATIBILITY 1 -#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5 - -/* Tasks.c additions (e.g. Thread Aware Debug capability) */ -#define configINCLUDE_FREERTOS_TASK_C_ADDITIONS_H 1 - -/* Used memory allocation (heap_x.c) */ -#define configFRTOS_MEMORY_SCHEME 4 - -/* Memory allocation related definitions. */ -#define configSUPPORT_STATIC_ALLOCATION 0 -#define configSUPPORT_DYNAMIC_ALLOCATION 1 -#define configTOTAL_HEAP_SIZE ((size_t) (gTotalHeapSize_c)) -#define configAPPLICATION_ALLOCATED_HEAP 1 - -/* Hook function related definitions. */ -#ifndef configUSE_IDLE_HOOK -#define configUSE_IDLE_HOOK 0 -#endif -#define configUSE_TICK_HOOK 0 -#define configCHECK_FOR_STACK_OVERFLOW 0 -#ifndef configUSE_MALLOC_FAILED_HOOK -#define configUSE_MALLOC_FAILED_HOOK 0 -#endif -#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 - -/* Run time and task stats gathering related definitions. */ -#define configGENERATE_RUN_TIME_STATS 0 -#define configUSE_TRACE_FACILITY 1 -#define configUSE_STATS_FORMATTING_FUNCTIONS 0 - -/* Task aware debugging. */ -#define configRECORD_STACK_HIGH_ADDRESS 1 - -/* Co-routine related definitions. */ -#define configUSE_CO_ROUTINES 0 -#define configMAX_CO_ROUTINE_PRIORITIES 2 - -/* Software timer related definitions. */ -#define configUSE_TIMERS 1 -#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES - 1) -#define configTIMER_QUEUE_LENGTH 10 -#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 4) - -/* Define to trap errors during development. */ -#define configASSERT(x) \ - if ((x) == 0) \ - { \ - taskDISABLE_INTERRUPTS(); \ - for (;;) \ - ; \ - } - -/* Optional functions - most linkers will remove unused functions anyway. */ -#define INCLUDE_vTaskPrioritySet 1 -#define INCLUDE_uxTaskPriorityGet 1 -#define INCLUDE_vTaskDelete 1 -#define INCLUDE_vTaskSuspend 1 -#define INCLUDE_xResumeFromISR 1 -#define INCLUDE_vTaskDelayUntil 1 -#define INCLUDE_vTaskDelay 1 -#define INCLUDE_xTaskGetSchedulerState 1 -#define INCLUDE_xTaskGetCurrentTaskHandle 1 -#define INCLUDE_uxTaskGetStackHighWaterMark 1 -#define INCLUDE_xTaskGetIdleTaskHandle 0 -#define INCLUDE_eTaskGetState 0 -#define INCLUDE_xEventGroupSetBitFromISR 1 -#define INCLUDE_xTimerPendFunctionCall 1 -#define INCLUDE_xTaskAbortDelay 0 -#define INCLUDE_xTaskGetHandle 0 -#define INCLUDE_xTaskResumeFromISR 1 -#define INCLUDE_xQueueGetMutexHolder 1 - -/* Interrupt nesting behaviour configuration. Cortex-M specific. */ -#ifdef __NVIC_PRIO_BITS -/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */ -#define configPRIO_BITS __NVIC_PRIO_BITS -#else -#define configPRIO_BITS 3 -#endif - -/* The lowest interrupt priority that can be used in a call to a "set priority" -function. */ -#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x7 - -/* The highest interrupt priority that can be used by any interrupt service -routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL -INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER -PRIORITY THAN THIS! (higher priorities are lower numeric values. */ -#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 1 - -/* Interrupt priorities used by the kernel port layer itself. These are generic -to all Cortex-M ports, and do not rely on any particular library functions. */ -#define configKERNEL_INTERRUPT_PRIORITY (configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) -/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! -See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */ -#define configMAX_SYSCALL_INTERRUPT_PRIORITY (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) - -/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS -standard names. */ -#define vPortSVCHandler SVC_Handler -#define xPortPendSVHandler PendSV_Handler -#define xPortSysTickHandler SysTick_Handler diff --git a/examples/shell/nxp/k32w/k32w0/main/AppTask.cpp b/examples/shell/nxp/k32w/k32w0/main/AppTask.cpp deleted file mode 100644 index 23f1417ed8aeb1..00000000000000 --- a/examples/shell/nxp/k32w/k32w0/main/AppTask.cpp +++ /dev/null @@ -1,486 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * Copyright (c) 2021 Google LLC. - * 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 "AppTask.h" -#include "AppEvent.h" -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "Keyboard.h" -#include "LED.h" -#include "LEDWidget.h" -#include "app_config.h" - -constexpr uint32_t kFactoryResetTriggerTimeout = 6000; -constexpr uint8_t kAppEventQueueSize = 10; - -TimerHandle_t sFunctionTimer; // FreeRTOS app sw timer. - -static QueueHandle_t sAppEventQueue; - -static LEDWidget sStatusLED; - -static bool sIsThreadProvisioned = false; -static bool sHaveBLEConnections = false; - -static uint32_t eventMask = 0; - -using namespace ::chip::Credentials; -using namespace ::chip::DeviceLayer; - -AppTask AppTask::sAppTask; - -CHIP_ERROR AppTask::StartAppTask() -{ - CHIP_ERROR err = CHIP_NO_ERROR; - - sAppEventQueue = xQueueCreate(kAppEventQueueSize, sizeof(AppEvent)); - if (sAppEventQueue == NULL) - { - err = APP_ERROR_EVENT_QUEUE_FAILED; - K32W_LOG("Failed to allocate app event queue"); - assert(false); - } - - return err; -} - -CHIP_ERROR AppTask::Init() -{ - CHIP_ERROR err = CHIP_NO_ERROR; - - // Init ZCL Data Model and start server - PlatformMgr().ScheduleWork(InitServer, 0); - - // Initialize device attestation config - SetDeviceAttestationCredentialsProvider(Examples::GetExampleDACProvider()); - - // QR code will be used with CHIP Tool - PrintOnboardingCodes(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); - - /* HW init leds */ - LED_Init(); - - /* start with all LEDS turnedd off */ - sStatusLED.Init(SYSTEM_STATE_LED); - - /* intialize the Keyboard and button press calback */ - KBD_Init(KBD_Callback); - - // Create FreeRTOS sw timer for Function Selection. - sFunctionTimer = xTimerCreate("FnTmr", // Just a text name, not used by the RTOS kernel - 1, // == default timer period (mS) - false, // no timer reload (==one-shot) - (void *) this, // init timer id = app task obj context - TimerEventHandler // timer callback handler - ); - - if (sFunctionTimer == NULL) - { - err = APP_ERROR_CREATE_TIMER_FAILED; - K32W_LOG("app_timer_create() failed"); - assert(err == CHIP_NO_ERROR); - } - - // Print the current software version - char currentSoftwareVer[ConfigurationManager::kMaxSoftwareVersionStringLength + 1] = { 0 }; - err = ConfigurationMgr().GetSoftwareVersionString(currentSoftwareVer, sizeof(currentSoftwareVer)); - if (err != CHIP_NO_ERROR) - { - K32W_LOG("Get version error"); - assert(err == CHIP_NO_ERROR); - } - - K32W_LOG("Current Software Version: %s", currentSoftwareVer); - -#if CONFIG_CHIP_NFC_COMMISSIONING - PlatformMgr().AddEventHandler(ThreadProvisioningHandler, 0); -#endif - - return err; -} - -void LockOpenThreadTask(void) -{ - chip::DeviceLayer::ThreadStackMgr().LockThreadStack(); -} - -void UnlockOpenThreadTask(void) -{ - chip::DeviceLayer::ThreadStackMgr().UnlockThreadStack(); -} - -void AppTask::InitServer(intptr_t arg) -{ - static chip::CommonCaseDeviceServerInitParams initParams; - (void) initParams.InitializeStaticResourcesBeforeServerInit(); - - // Init ZCL Data Model and start server - chip::Inet::EndPointStateOpenThread::OpenThreadEndpointInitParam nativeParams; - nativeParams.lockCb = LockOpenThreadTask; - nativeParams.unlockCb = UnlockOpenThreadTask; - nativeParams.openThreadInstancePtr = chip::DeviceLayer::ThreadStackMgrImpl().OTInstance(); - initParams.endpointNativeParams = static_cast(&nativeParams); - VerifyOrDie((chip::Server::GetInstance().Init(initParams)) == CHIP_NO_ERROR); -} - -void AppTask::AppTaskMain(void * pvParameter) -{ - AppEvent event; - - CHIP_ERROR err = sAppTask.Init(); - if (err != CHIP_NO_ERROR) - { - K32W_LOG("AppTask.Init() failed"); - assert(err == CHIP_NO_ERROR); - } - - while (true) - { - TickType_t xTicksToWait = pdMS_TO_TICKS(10); - BaseType_t eventReceived = xQueueReceive(sAppEventQueue, &event, xTicksToWait); - while (eventReceived == pdTRUE) - { - sAppTask.DispatchEvent(&event); - eventReceived = xQueueReceive(sAppEventQueue, &event, 0); - } - - // Collect connectivity and configuration state from the CHIP stack. Because the - // CHIP event loop is being run in a separate task, the stack must be locked - // while these values are queried. However we use a non-blocking lock request - // (TryLockChipStack()) to avoid blocking other UI activities when the CHIP - // task is busy (e.g. with a long crypto operation). - if (PlatformMgr().TryLockChipStack()) - { - sHaveBLEConnections = (ConnectivityMgr().NumBLEConnections() != 0); - PlatformMgr().UnlockChipStack(); - } - - // Update the status LED if factory reset has not been initiated. - // - // If system has "full connectivity", keep the LED On constantly. - // - // If thread and service provisioned, but not attached to the thread network yet OR no - // connectivity to the service OR subscriptions are not fully established - // THEN blink the LED Off for a short period of time. - // - // If the system has ble connection(s) uptill the stage above, THEN blink the LEDs at an even - // rate of 100ms. - // - // Otherwise, blink the LED ON for a very short time. - if (sAppTask.mFunction != kFunction_FactoryReset) - { - if (sIsThreadProvisioned) - { - sStatusLED.Blink(950, 50); - } - else if (sHaveBLEConnections) - { - sStatusLED.Blink(100, 100); - } - else - { - sStatusLED.Blink(50, 950); - } - } - - sStatusLED.Animate(); - } -} - -void AppTask::ButtonEventHandler(uint8_t pin_no, uint8_t button_action) -{ - if ((pin_no != RESET_BUTTON) && (pin_no != BLE_BUTTON)) - { - return; - } - - AppEvent button_event; - button_event.Type = AppEvent::kEventType_Button; - button_event.ButtonEvent.PinNo = pin_no; - button_event.ButtonEvent.Action = button_action; - - if (pin_no == RESET_BUTTON) - { - button_event.Handler = ResetActionEventHandler; - } - else if (pin_no == BLE_BUTTON) - { - button_event.Handler = BleHandler; -#if !(defined OM15082) - if (button_action == RESET_BUTTON_PUSH) - { - button_event.Handler = ResetActionEventHandler; - } -#endif - } - - sAppTask.PostEvent(&button_event); -} - -void AppTask::KBD_Callback(uint8_t events) -{ - eventMask = eventMask | (uint32_t) (1 << events); - - HandleKeyboard(); -} - -void AppTask::HandleKeyboard(void) -{ - uint8_t keyEvent = 0xFF; - uint8_t pos = 0; - - while (eventMask) - { - for (pos = 0; pos < (8 * sizeof(eventMask)); pos++) - { - if (eventMask & (1 << pos)) - { - keyEvent = pos; - eventMask = eventMask & ~(1 << pos); - break; - } - } - - switch (keyEvent) - { - case gKBD_EventPB1_c: -#if (defined OM15082) - ButtonEventHandler(RESET_BUTTON, RESET_BUTTON_PUSH); - break; -#else - ButtonEventHandler(BLE_BUTTON, BLE_BUTTON_PUSH); - break; -#endif - case gKBD_EventPB4_c: - ButtonEventHandler(BLE_BUTTON, BLE_BUTTON_PUSH); - break; -#if !(defined OM15082) - case gKBD_EventLongPB1_c: - ButtonEventHandler(BLE_BUTTON, RESET_BUTTON_PUSH); - break; -#endif - default: - break; - } - } -} - -void AppTask::TimerEventHandler(TimerHandle_t xTimer) -{ - AppEvent event; - event.Type = AppEvent::kEventType_Timer; - event.TimerEvent.Context = (void *) xTimer; - event.Handler = FunctionTimerEventHandler; - sAppTask.PostEvent(&event); -} - -void AppTask::FunctionTimerEventHandler(AppEvent * aEvent) -{ - if (aEvent->Type != AppEvent::kEventType_Timer) - return; - - K32W_LOG("Device will factory reset..."); - - // Actually trigger Factory Reset - chip::Server::GetInstance().ScheduleFactoryReset(); -} - -void AppTask::ResetActionEventHandler(AppEvent * aEvent) -{ - if (aEvent->ButtonEvent.PinNo != RESET_BUTTON && aEvent->ButtonEvent.PinNo != BLE_BUTTON) - return; - - if (sAppTask.mResetTimerActive) - { - sAppTask.CancelTimer(); - sAppTask.mFunction = kFunction_NoneSelected; - - K32W_LOG("Factory Reset was cancelled!"); - } - else - { - uint32_t resetTimeout = kFactoryResetTriggerTimeout; - - if (sAppTask.mFunction != kFunction_NoneSelected) - { - K32W_LOG("Another function is scheduled. Could not initiate Factory Reset!"); - return; - } - - K32W_LOG("Factory Reset Triggered. Push the RESET button within %u ms to cancel!", resetTimeout); - sAppTask.mFunction = kFunction_FactoryReset; - - /* LEDs will start blinking to signal that a Factory Reset was scheduled */ - sStatusLED.Set(false); - sStatusLED.Blink(500); - - sAppTask.StartTimer(kFactoryResetTriggerTimeout); - } -} - -void AppTask::BleHandler(AppEvent * aEvent) -{ - if (aEvent->ButtonEvent.PinNo != BLE_BUTTON) - return; - - if (sAppTask.mFunction != kFunction_NoneSelected) - { - K32W_LOG("Another function is scheduled. Could not toggle BLE state!"); - return; - } - - if (ConnectivityMgr().IsBLEAdvertisingEnabled()) - { - ConnectivityMgr().SetBLEAdvertisingEnabled(false); - K32W_LOG("Stopped BLE Advertising!"); - } - else - { - ConnectivityMgr().SetBLEAdvertisingEnabled(true); - - if (chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow() == CHIP_NO_ERROR) - { - K32W_LOG("Started BLE Advertising!"); - } - else - { - K32W_LOG("OpenBasicCommissioningWindow() failed"); - } - } -} - -void AppTask::ThreadProvisioningHandler(const ChipDeviceEvent * event, intptr_t) -{ - if (event->Type == DeviceEventType::kServiceProvisioningChange && event->ServiceProvisioningChange.IsServiceProvisioned) - { - if (event->ServiceProvisioningChange.IsServiceProvisioned) - { - sIsThreadProvisioned = TRUE; - } - else - { - sIsThreadProvisioned = FALSE; - } - } - -#if CONFIG_CHIP_NFC_COMMISSIONING - if (event->Type == DeviceEventType::kCHIPoBLEAdvertisingChange && event->CHIPoBLEAdvertisingChange.Result == kActivity_Stopped) - { - if (!NFCMgr().IsTagEmulationStarted()) - { - K32W_LOG("NFC Tag emulation is already stopped!"); - } - else - { - NFCMgr().StopTagEmulation(); - K32W_LOG("Stopped NFC Tag Emulation!"); - } - } - else if (event->Type == DeviceEventType::kCHIPoBLEAdvertisingChange && - event->CHIPoBLEAdvertisingChange.Result == kActivity_Started) - { - if (NFCMgr().IsTagEmulationStarted()) - { - K32W_LOG("NFC Tag emulation is already started!"); - } - else - { - ShareQRCodeOverNFC(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE)); - K32W_LOG("Started NFC Tag Emulation!"); - } - } -#endif -} - -void AppTask::CancelTimer() -{ - if (xTimerStop(sFunctionTimer, 0) == pdFAIL) - { - K32W_LOG("app timer stop() failed"); - } - - mResetTimerActive = false; -} - -void AppTask::StartTimer(uint32_t aTimeoutInMs) -{ - if (xTimerIsTimerActive(sFunctionTimer)) - { - K32W_LOG("app timer already started!"); - CancelTimer(); - } - - // timer is not active, change its period to required value (== restart). - // FreeRTOS- Block for a maximum of 100 ticks if the change period command - // cannot immediately be sent to the timer command queue. - if (xTimerChangePeriod(sFunctionTimer, aTimeoutInMs / portTICK_PERIOD_MS, 100) != pdPASS) - { - K32W_LOG("app timer start() failed"); - } - - mResetTimerActive = true; -} - -void AppTask::PostEvent(const AppEvent * aEvent) -{ - portBASE_TYPE taskToWake = pdFALSE; - if (sAppEventQueue != NULL) - { - if (__get_IPSR()) - { - if (!xQueueSendToFrontFromISR(sAppEventQueue, aEvent, &taskToWake)) - { - K32W_LOG("Failed to post event to app task event queue"); - } - if (taskToWake) - { - portYIELD_FROM_ISR(taskToWake); - } - } - else if (!xQueueSend(sAppEventQueue, aEvent, 0)) - { - K32W_LOG("Failed to post event to app task event queue"); - } - } -} - -void AppTask::DispatchEvent(AppEvent * aEvent) -{ - if (aEvent->Handler) - { - aEvent->Handler(aEvent); - } - else - { - K32W_LOG("Event received with no handler. Dropping event."); - } -} - -extern "C" void OTAIdleActivities(void) {} diff --git a/examples/shell/nxp/k32w/k32w0/main/include/app_config.h b/examples/shell/nxp/k32w/k32w0/main/include/app_config.h deleted file mode 100644 index 08edfc477c2623..00000000000000 --- a/examples/shell/nxp/k32w/k32w0/main/include/app_config.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * - * Copyright (c) 2020 Google LLC. - * 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 "LED.h" - -#pragma once - -// ---- Lock Example App Config ---- - -#define RESET_BUTTON 1 -#define LOCK_BUTTON 2 -#define JOIN_BUTTON 3 -#define BLE_BUTTON 4 - -#define RESET_BUTTON_PUSH 1 -#define LOCK_BUTTON_PUSH 2 -#define JOIN_BUTTON_PUSH 3 -#define BLE_BUTTON_PUSH 4 - -#define APP_BUTTON_PUSH 1 - -#define SYSTEM_STATE_LED LED1 -#define LOCK_STATE_LED LED2 - -// Time it takes in ms for the simulated actuator to move from one -// state to another. -#define ACTUATOR_MOVEMENT_PERIOS_MS 2000 - -// ---- Lock Example SWU Config ---- -#define SWU_INTERVAl_WINDOW_MIN_MS (23 * 60 * 60 * 1000) // 23 hours -#define SWU_INTERVAl_WINDOW_MAX_MS (24 * 60 * 60 * 1000) // 24 hours - -#if K32W_LOG_ENABLED -#define K32W_LOG(...) otPlatLog(OT_LOG_LEVEL_NONE, OT_LOG_REGION_API, ##__VA_ARGS__); -#else -#define K32W_LOG(...) -#endif diff --git a/examples/shell/nxp/k32w/k32w0/main/main.cpp b/examples/shell/nxp/k32w/k32w0/main/main.cpp deleted file mode 100644 index 59b4ddcd063ea1..00000000000000 --- a/examples/shell/nxp/k32w/k32w0/main/main.cpp +++ /dev/null @@ -1,175 +0,0 @@ -/* - * - * Copyright (c) 2021 Google LLC. - * 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. - */ - -// ================================================================================ -// Main Code -// ================================================================================ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "FreeRtosHooks.h" -#include "app_config.h" - -#if PDM_SAVE_IDLE -#include -#endif - -const uint16_t shell_task_size = 3096; -const uint8_t shell_task_priority = 0; - -using namespace ::chip; -using namespace ::chip::Inet; -using namespace ::chip::DeviceLayer; -using namespace ::chip::Logging; -using chip::Shell::Engine; -#include "MacSched.h" - -typedef void (*InitFunc)(void); -extern InitFunc __init_array_start; -extern InitFunc __init_array_end; - -/* needed for FreeRtos Heap 4 */ -uint8_t __attribute__((section(".heap"))) ucHeap[HEAP_SIZE]; - -extern "C" unsigned int sleep(unsigned int seconds) -{ - const TickType_t xDelay = 1000 * seconds / portTICK_PERIOD_MS; - vTaskDelay(xDelay); - return 0; -} - -static void shell_task(void * args) -{ - Engine::Root().Init(); - Engine::Root().RunMainLoop(); -} - -extern "C" void main_task(void const * argument) -{ - int status = 0; - char * argv[1] = { 0 }; - BaseType_t shellTaskHandle; - CHIP_ERROR err = CHIP_NO_ERROR; - - /* Call C++ constructors */ - InitFunc * pFunc = &__init_array_start; - for (; pFunc < &__init_array_end; ++pFunc) - { - (*pFunc)(); - } - - err = PlatformMgrImpl().InitBoardFwk(); - if (err != CHIP_NO_ERROR) - { - return; - } - - mbedtls_platform_set_calloc_free(CHIPPlatformMemoryCalloc, CHIPPlatformMemoryFree); - - K32W_LOG("Welcome to NXP Shell Demo App"); - - /* Mbedtls Threading support is needed because both - * Thread and Matter tasks are using it */ - freertos_mbedtls_mutex_init(); - - // Init Chip memory management before the stack - chip::Platform::MemoryInit(); - -#if PDM_SAVE_IDLE - /* OT Settings needs to be initialized - * early as XCVR is making use of it */ - otPlatSettingsInit(NULL, NULL, 0); -#endif - - CHIP_ERROR ret = PlatformMgr().InitChipStack(); - if (ret != CHIP_NO_ERROR) - { - K32W_LOG("Error during PlatformMgr().InitMatterStack()"); - goto exit; - } - - ret = ThreadStackMgr().InitThreadStack(); - if (ret != CHIP_NO_ERROR) - { - K32W_LOG("Error during ThreadStackMgr().InitThreadStack()"); - goto exit; - } - - /* Enable the MAC scheduler after BLEManagerImpl::_Init() and V2MMAC_Enable(). - * This is needed to register properly the active protocols. - */ - sched_enable(); - - ret = ConnectivityMgr().SetThreadDeviceType(ConnectivityManager::kThreadDeviceType_MinimalEndDevice); - if (ret != CHIP_NO_ERROR) - { - goto exit; - } - - ret = PlatformMgr().StartEventLoopTask(); - if (ret != CHIP_NO_ERROR) - { - K32W_LOG("Error during PlatformMgr().StartEventLoopTask();"); - goto exit; - } - - // Start OpenThread task - ret = ThreadStackMgrImpl().StartThreadTask(); - if (ret != CHIP_NO_ERROR) - { - K32W_LOG("Error during ThreadStackMgrImpl().StartThreadTask()"); - goto exit; - } - - shellTaskHandle = xTaskCreate(shell_task, "shell_task", shell_task_size / sizeof(StackType_t), NULL, shell_task_priority, NULL); - if (!shellTaskHandle) - { - K32W_LOG("Error while creating the shell task!"); - goto exit; - } - - ret = GetAppTask().StartAppTask(); - if (ret != CHIP_NO_ERROR) - { - K32W_LOG("Error during GetAppTask().StartAppTask()"); - goto exit; - } - GetAppTask().AppTaskMain(NULL); - -exit: - return; -} - -extern "C" void otSysEventSignalPending(void) -{ - { - BaseType_t yieldRequired = ThreadStackMgrImpl().SignalThreadActivityPendingFromISR(); - portYIELD_FROM_ISR(yieldRequired); - } -} diff --git a/examples/shell/nxp/k32w/k32w0/third_party/connectedhomeip b/examples/shell/nxp/k32w/k32w0/third_party/connectedhomeip deleted file mode 120000 index 477cbaad6077b0..00000000000000 --- a/examples/shell/nxp/k32w/k32w0/third_party/connectedhomeip +++ /dev/null @@ -1 +0,0 @@ -../../../../../../ \ No newline at end of file diff --git a/examples/thermostat/nxp/common/main/AppTask.cpp b/examples/thermostat/nxp/common/main/AppTask.cpp new file mode 100644 index 00000000000000..8e3f8f1036a2e4 --- /dev/null +++ b/examples/thermostat/nxp/common/main/AppTask.cpp @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2021-2023 Project CHIP Authors + * Copyright (c) 2021 Google LLC. + * Copyright 2023-2024 NXP + * 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 "AppTask.h" +#include "CHIPDeviceManager.h" +#include "ICDUtil.h" +#include +#include + +using namespace chip; + +void ThermostatApp::AppTask::PreInitMatterStack() +{ + ChipLogProgress(DeviceLayer, "Welcome to NXP thermostat Demo App"); +} + +void ThermostatApp::AppTask::PostInitMatterStack() +{ + chip::app::InteractionModelEngine::GetInstance()->RegisterReadHandlerAppCallback(&chip::NXP::App::GetICDUtil()); +} + +// This returns an instance of this class. +ThermostatApp::AppTask & ThermostatApp::AppTask::GetDefaultInstance() +{ + static ThermostatApp::AppTask sAppTask; + return sAppTask; +} + +chip::NXP::App::AppTaskBase & chip::NXP::App::GetAppTask() +{ + return ThermostatApp::AppTask::GetDefaultInstance(); +} diff --git a/examples/thermostat/nxp/common/main/DeviceCallbacks.cpp b/examples/thermostat/nxp/common/main/DeviceCallbacks.cpp new file mode 100644 index 00000000000000..d52d933d26ef16 --- /dev/null +++ b/examples/thermostat/nxp/common/main/DeviceCallbacks.cpp @@ -0,0 +1,101 @@ +/* + * + * Copyright (c) 2020-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. + */ + +/** + * @file DeviceCallbacks.cpp + * + * Implements all the callbacks to the application from the CHIP Stack + * + **/ +#include "DeviceCallbacks.h" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace chip::app; +void OnTriggerEffect(::Identify * identify) +{ + switch (identify->mCurrentEffectIdentifier) + { + case Clusters::Identify::EffectIdentifierEnum::kBlink: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kBlink"); + break; + case Clusters::Identify::EffectIdentifierEnum::kBreathe: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kBreathe"); + break; + case Clusters::Identify::EffectIdentifierEnum::kOkay: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kOkay"); + break; + case Clusters::Identify::EffectIdentifierEnum::kChannelChange: + ChipLogProgress(Zcl, "Clusters::Identify::EffectIdentifierEnum::kChannelChange"); + break; + default: + ChipLogProgress(Zcl, "No identifier effect"); + return; + } +} + +Identify gIdentify0 = { + chip::EndpointId{ 1 }, + [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStart"); }, + [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStop"); }, + chip::app::Clusters::Identify::IdentifyTypeEnum::kNone, + OnTriggerEffect, +}; + +Identify gIdentify1 = { + chip::EndpointId{ 1 }, + [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStart"); }, + [](Identify *) { ChipLogProgress(Zcl, "onIdentifyStop"); }, + chip::app::Clusters::Identify::IdentifyTypeEnum::kNone, + OnTriggerEffect, +}; + +using namespace ::chip; +using namespace ::chip::Inet; +using namespace ::chip::System; +using namespace ::chip::DeviceLayer; +using namespace chip::app::Clusters; + +void ThermostatApp::DeviceCallbacks::PostAttributeChangeCallback(EndpointId endpointId, ClusterId clusterId, + AttributeId attributeId, uint8_t type, uint16_t size, + uint8_t * value) +{ + ChipLogProgress(DeviceLayer, + "endpointId " ChipLogFormatMEI " clusterId " ChipLogFormatMEI " attribute ID: " ChipLogFormatMEI + " Type: %u Value: %u, length %u", + ChipLogValueMEI(endpointId), ChipLogValueMEI(clusterId), ChipLogValueMEI(attributeId), type, *value, size); +} + +// This returns an instance of this class. +ThermostatApp::DeviceCallbacks & ThermostatApp::DeviceCallbacks::GetDefaultInstance() +{ + static DeviceCallbacks sDeviceCallbacks; + return sDeviceCallbacks; +} + +chip::DeviceManager::CHIPDeviceManagerCallbacks & chip::NXP::App::GetDeviceCallbacks() +{ + return ThermostatApp::DeviceCallbacks::GetDefaultInstance(); +} diff --git a/examples/thermostat/nxp/common/main/ZclCallbacks.cpp b/examples/thermostat/nxp/common/main/ZclCallbacks.cpp new file mode 100644 index 00000000000000..86ea10f4687558 --- /dev/null +++ b/examples/thermostat/nxp/common/main/ZclCallbacks.cpp @@ -0,0 +1,41 @@ +/* + * + * Copyright (c) 2021-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 + +#include "AppTask.h" +#include "CHIPDeviceManager.h" + +#include +#include +#include +#include +#include + +using namespace ::chip; + +void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & path, uint8_t type, uint16_t size, uint8_t * value) +{ + chip::DeviceManager::CHIPDeviceManagerCallbacks * cb = + chip::DeviceManager::CHIPDeviceManager::GetInstance().GetCHIPDeviceManagerCallbacks(); + if (cb != nullptr) + { + // propagate event to device manager + cb->PostAttributeChangeCallback(path.mEndpointId, path.mClusterId, path.mAttributeId, type, size, value); + } +} diff --git a/examples/lock-app/nxp/k32w/k32w0/main/include/AppEvent.h b/examples/thermostat/nxp/common/main/include/AppEvent.h similarity index 65% rename from examples/lock-app/nxp/k32w/k32w0/main/include/AppEvent.h rename to examples/thermostat/nxp/common/main/include/AppEvent.h index 864628869959f6..a0dad141a27055 100644 --- a/examples/lock-app/nxp/k32w/k32w0/main/include/AppEvent.h +++ b/examples/thermostat/nxp/common/main/include/AppEvent.h @@ -1,6 +1,6 @@ /* - * - * Copyright (c) 2020 Nest Labs, Inc. + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2021 Nest Labs, Inc. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,30 +19,21 @@ #pragma once struct AppEvent; -typedef void (*EventHandler)(void *); +using EventHandler = void (*)(const AppEvent &); struct AppEvent { enum AppEventTypes { - kEventType_Button = 0, - kEventType_Timer, - kEventType_Lock, + kEventType_Timer = 0, + kEventType_TurnOn, kEventType_Install, -#if defined(chip_with_low_power) && (chip_with_low_power == 1) - kEventType_Lp, -#endif }; - AppEventTypes Type; + uint16_t Type; union { - struct - { - uint8_t PinNo; - uint8_t Action; - } ButtonEvent; struct { void * Context; @@ -51,12 +42,8 @@ struct AppEvent { uint8_t Action; int32_t Actor; - } LockEvent; + } ClusterEvent; }; EventHandler Handler; - -#if defined(chip_with_low_power) && (chip_with_low_power == 1) - void * param; -#endif }; diff --git a/examples/thermostat/nxp/common/main/include/AppTask.h b/examples/thermostat/nxp/common/main/include/AppTask.h new file mode 100644 index 00000000000000..51b1df49e023c1 --- /dev/null +++ b/examples/thermostat/nxp/common/main/include/AppTask.h @@ -0,0 +1,54 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2021-2023 Google LLC. + * Copyright 2024 NXP + * 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. + */ + +#pragma once + +#if CONFIG_APP_FREERTOS_OS +#include "AppTaskFreeRTOS.h" +#else +#include "AppTaskZephyr.h" +#endif + +namespace ThermostatApp { +#if CONFIG_APP_FREERTOS_OS +class AppTask : public chip::NXP::App::AppTaskFreeRTOS +#else +class AppTask : public chip::NXP::App::AppTaskZephyr +#endif +{ +public: + ~AppTask() override{}; + void PostInitMatterStack(void) override; + void PreInitMatterStack(void) override; + // This returns an instance of this class. + static AppTask & GetDefaultInstance(); + +private: + static AppTask sAppTask; +}; +} // namespace ThermostatApp + +/** + * Returns the application-specific implementation of the AppTaskBase object. + * + * Applications can use this to gain access to features of the AppTaskBase + * that are specific to the selected application. + */ +chip::NXP::App::AppTaskBase & GetAppTask(); diff --git a/examples/thermostat/nxp/common/main/include/DeviceCallbacks.h b/examples/thermostat/nxp/common/main/include/DeviceCallbacks.h new file mode 100644 index 00000000000000..0abdc20aebe38f --- /dev/null +++ b/examples/thermostat/nxp/common/main/include/DeviceCallbacks.h @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2020-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. + */ + +/** + * @file DeviceCallbacks.h + * + * Implementations for the DeviceManager callbacks for this application + * + **/ + +#pragma once + +#include "CHIPDeviceManager.h" +#include "CommonDeviceCallbacks.h" + +namespace ThermostatApp { +class DeviceCallbacks : public chip::NXP::App::CommonDeviceCallbacks +{ +public: + // This returns an instance of this class. + static DeviceCallbacks & GetDefaultInstance(); + + void PostAttributeChangeCallback(chip::EndpointId endpointId, chip::ClusterId clusterId, chip::AttributeId attributeId, + uint8_t type, uint16_t size, uint8_t * value); + +private: + void OnIdentifyPostAttributeChangeCallback(chip::EndpointId endpointId, chip::AttributeId attributeId, uint8_t * value); +}; +} // namespace ThermostatApp + +/** + * Returns the application-specific implementation of the CommonDeviceCallbacks object. + * + * Applications can use this to gain access to features of the CommonDeviceCallbacks + * that are specific to the selected application. + */ +chip::DeviceManager::CHIPDeviceManagerCallbacks & GetDeviceCallbacks(); diff --git a/examples/thermostat/nxp/common/main/main.cpp b/examples/thermostat/nxp/common/main/main.cpp new file mode 100644 index 00000000000000..b2aadab98ceb8b --- /dev/null +++ b/examples/thermostat/nxp/common/main/main.cpp @@ -0,0 +1,49 @@ +/* + * + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2021-2023 Google LLC. + * 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. + */ + +// ================================================================================ +// Main Code +// ================================================================================ + +#include "FreeRTOS.h" +#include +#include +#include + +#if configAPPLICATION_ALLOCATED_HEAP +uint8_t __attribute__((section(".heap"))) ucHeap[configTOTAL_HEAP_SIZE]; +#endif + +using namespace ::chip::DeviceLayer; + +extern "C" int main(int argc, char * argv[]) +{ + TaskHandle_t taskHandle; + + PlatformMgrImpl().HardwareInit(); + chip::NXP::App::GetAppTask().Start(); + vTaskStartScheduler(); +} + +#if (defined(configCHECK_FOR_STACK_OVERFLOW) && (configCHECK_FOR_STACK_OVERFLOW > 0)) +void vApplicationStackOverflowHook(TaskHandle_t xTask, char * pcTaskName) +{ + assert(0); +} +#endif diff --git a/examples/shell/nxp/k32w/k32w0/.gn b/examples/thermostat/nxp/rt/rw61x/.gn similarity index 80% rename from examples/shell/nxp/k32w/k32w0/.gn rename to examples/thermostat/nxp/rt/rw61x/.gn index 81cec9d11a421b..c0a26c2dc77832 100644 --- a/examples/shell/nxp/k32w/k32w0/.gn +++ b/examples/thermostat/nxp/rt/rw61x/.gn @@ -1,4 +1,5 @@ -# Copyright (c) 2021 Project CHIP Authors +# Copyright (c) 2020 Project CHIP Authors +# Copyright 2023 NXP # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,6 +14,7 @@ # limitations under the License. import("//build_overrides/build.gni") +import("//build_overrides/chip.gni") # The location of the build configuration file. buildconfig = "${build_root}/config/BUILDCONFIG.gn" @@ -25,4 +27,7 @@ default_args = { target_os = "freertos" import("//args.gni") + + # Import default platform configs + import("${chip_root}/src/platform/nxp/rt/rw61x/args.gni") } diff --git a/examples/thermostat/nxp/rt/rw61x/BUILD.gn b/examples/thermostat/nxp/rt/rw61x/BUILD.gn new file mode 100644 index 00000000000000..76610146809bca --- /dev/null +++ b/examples/thermostat/nxp/rt/rw61x/BUILD.gn @@ -0,0 +1,278 @@ +# Copyright (c) 2021 Project CHIP Authors +# Copyright 2023 NXP +# +# 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("//build_overrides/build.gni") +import("//build_overrides/chip.gni") +import("//build_overrides/nxp_sdk.gni") +import("//build_overrides/openthread.gni") +import("${chip_root}/src/platform/device.gni") + +#allows to get common NXP SDK gn options +import("${nxp_sdk_build_root}/nxp_sdk.gni") + +# Allows to get various RT gn options +import("${nxp_sdk_build_root}/${nxp_sdk_name}/rt_sdk.gni") + +import("${chip_root}/src/platform/nxp/${nxp_platform}/args.gni") +import( + "${nxp_sdk_build_root}/${nxp_sdk_name}/${rt_platform}/${rt_platform}.gni") +import("${nxp_sdk_build_root}/${nxp_sdk_name}/nxp_executable.gni") + +assert(current_os == "freertos") +assert(target_os == "freertos") +assert(nxp_platform == "rt/rw61x") + +declare_args() { + # Allows to start the tcp download test app + tcp_download = false + + # Allows to start the wifi connect test app + wifi_connect = false + + # The 2 params below are used only if tcp_download or wifi_connect are true, otherwise they're unused. + wifi_ssid = "" + wifi_password = "" + + # Setup discriminator as argument + setup_discriminator = 3840 +} + +example_platform_dir = "${chip_root}/examples/platform/nxp/${nxp_platform}" +common_example_dir = "${chip_root}/examples/platform/nxp/common" + +if (tcp_download == true && wifi_connect == true) { + assert("Cannot enable tcp_download and wifi_connect at the same time!") +} + +# Use NXP custom zap files for thermostatdevice-types +app_common_folder = "thermostat/nxp/zap" + +# Create here the SDK instance. +# Particular sources/defines/includes could be added/changed depending on the target application. +rt_sdk("sdk") { + defines = [] + + # To be moved, temporary mbedtls config fix to build app with factory data + if (chip_enable_secure_dac_private_key_storage == 1) { + defines += [ + "MBEDTLS_NIST_KW_C", + "MBEDTLS_PSA_CRYPTO_CLIENT", + ] + } + + cflags = [] + public_deps = [] + public_configs = [] + sources = [] + include_dirs = [] + + # Indicate paths to default board files + include_dirs += [ "${example_platform_dir}/board/" ] + sources += [ "${example_platform_dir}/board/pin_mux.c" ] + sources += [ "${example_platform_dir}/board/hardware_init.c" ] + sources += [ "${example_platform_dir}/board/clock_config.c" ] + sources += [ "${example_platform_dir}/board/board.c" ] + sources += [ "${example_platform_dir}/board/peripherals.c" ] + + # Indicate the path to CHIPProjectConfig.h + include_dirs += [ "include/config" ] + + # Indicate the default path to FreeRTOSConfig.h + include_dirs += [ "${example_platform_dir}/app/project_include/freeRTOS" ] + + # Indicate the default path to OpenThreadConfig.h + include_dirs += [ "${example_platform_dir}/app/project_include/openthread" ] + + # For matter with BR feature, increase FreeRTOS heap size + if (chip_enable_wifi && chip_enable_openthread) { + defines += [ "configTOTAL_HEAP_SIZE=(size_t)(160 * 1024)" ] + } +} + +# Create the SDK driver instance. +# Particular sources/defines/includes could be added to add other drivers not available in the default sdk driver template +rw61x_sdk_drivers("sdk_driver") { +} + +rt_executable("thermostat") { + output_name = "chip-rw61x-thermostat-example" + + defines = [ + "CONFIG_RENDEZVOUS_MODE=7", + "CONFIG_APP_FREERTOS_OS=1", + ] + + if (chip_enable_openthread) { + defines += [ "CONFIG_NET_L2_OPENTHREAD=1" ] + } + + include_dirs = [ + "../../common/main/include", + "../../common/main", + "${chip_root}/examples/all-clusters-app/all-clusters-common/include", + "${chip_root}/examples/providers/", + ] + + sources = [ + "${chip_root}/examples/all-clusters-app/all-clusters-common/src/binding-handler.cpp", + "${chip_root}/examples/providers/DeviceInfoProviderImpl.cpp", + "../../common/main/AppTask.cpp", + "../../common/main/DeviceCallbacks.cpp", + "../../common/main/ZclCallbacks.cpp", + "../../common/main/include/AppEvent.h", + "../../common/main/include/AppTask.h", + "../../common/main/include/DeviceCallbacks.h", + "../../common/main/main.cpp", + ] + + if (chip_enable_secure_dac_private_key_storage == 1) { + sources += [ + "${example_platform_dir}/factory_data/source/AppFactoryDataExample.cpp", + ] + } else { + sources += [ + "${common_example_dir}/factory_data/source/AppFactoryDataDefaultImpl.cpp", + ] + } + + # App common files + include_dirs += [ + "${common_example_dir}/icd/include", + "${common_example_dir}/matter_button/include", + "${common_example_dir}/matter_cli/include", + "${common_example_dir}/device_manager/include", + "${common_example_dir}/device_callbacks/include", + "${common_example_dir}/app_task/include", + "${common_example_dir}/factory_data/include", + ] + + sources += [ + "${common_example_dir}/app_task/source/AppTaskBase.cpp", + "${common_example_dir}/app_task/source/AppTaskFreeRTOS.cpp", + "${common_example_dir}/device_callbacks/source/CommonDeviceCallbacks.cpp", + "${common_example_dir}/device_manager/source/CHIPDeviceManager.cpp", + "${common_example_dir}/icd/source/ICDUtil.cpp", + "${common_example_dir}/matter_button/source/AppMatterButtonEmpty.cpp", + "${common_example_dir}/matter_cli/source/AppMatterCli.cpp", + ] + + deps = [ "${chip_root}/examples/${app_common_folder}" ] + + if (chip_enable_matter_cli) { + defines += [ "ENABLE_CHIP_SHELL" ] + deps += [ + "${chip_root}/examples/shell/shell_common:shell_common", + "${chip_root}/src/lib/shell:shell", + ] + } + + if (chip_enable_ota_requestor) { + include_dirs += [ "${common_example_dir}/ota_requestor/include" ] + sources += [ + "${common_example_dir}/ota_requestor/source/OTARequestorInitiator.cpp", + "${common_example_dir}/ota_requestor/source/OTARequestorInitiatorCommon.cpp", + ] + } + + if (wifi_connect) { + defines += [ + "WIFI_CONNECT_TASK=1", + "WIFI_CONNECT=1", + ] + + if (!chip_enable_matter_cli) { + assert(wifi_ssid != "" && wifi_password != "", + "WiFi SSID and password must be specified at build time!") + } + + if (wifi_ssid != "") { + defines += [ "WIFI_SSID=\"${wifi_ssid}\"" ] + } + + if (wifi_password != "") { + defines += [ "WIFI_PASSWORD=\"${wifi_password}\"" ] + } + + include_dirs += [ "${common_example_dir}/wifi_connect/include" ] + sources += [ "${common_example_dir}/wifi_connect/source/WifiConnect.cpp" ] + } + + if (tcp_download) { + defines += [ "CONFIG_CHIP_TCP_DOWNLOAD=1" ] + defines += [ + "WIFI_CONNECT=1", + "WIFI_SSID=\"${wifi_ssid}\"", + "WIFI_PASSWORD=\"${wifi_password}\"", + ] + + include_dirs += [ "${common_example_dir}/tcp_download_test/include" ] + sources += + [ "${common_example_dir}/tcp_download_test/source/TcpDownload.cpp" ] + } + + # In case a dedicated assert function needs to be supported the flag sdk_fsl_assert_support should be set to false + # The would add to the build a dedicated application assert implementation. + if (!sdk_fsl_assert_support) { + sources += [ "${common_example_dir}/app_assert/source/AppAssert.cpp" ] + } + + cflags = [ "-Wconversion" ] + + ldscript = "${example_platform_dir}/app/ldscripts/RW610_flash.ld" + + inputs = [ ldscript ] + + ldflags = [ + "-T" + rebase_path(ldscript, root_build_dir), + "-fno-common", + "-Wl,--defsym=__stack_size__=2048", + "-ffreestanding", + "-fno-builtin", + "-mapcs", + "-u qspiflash_config", + "-u image_vector_table", + "-u boot_data", + "-u dcd_data", + "-Wl,-print-memory-usage", + "-Wl,--no-warn-rwx-segments", + ] + + if (chip_enable_ota_requestor) { + if (no_mcuboot) { + # If "no_mcuboot" is set to true, the application will be linked at the base of the flash. + print( + "Warning : The OTA Requestor is enabled without MCUBoot. This will prevent the application from applying software updates.") + } else { + # we need to reserve enough space for the bootloader (MCUBoot) + # MCUBoot requires 0x20000 Bytes to be reserved at the base of the flash + # Consequently, some sections will need to be shifted + ldflags += [ "-Wl,--defsym=__m_mcuboot_size__=0x20000" ] + } + + defines += [ + "CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR=${setup_discriminator}", + ] + } + + output_dir = root_out_dir +} + +group("rw61x") { + deps = [ ":thermostat" ] +} + +group("default") { + deps = [ ":rw61x" ] +} diff --git a/examples/thermostat/nxp/rt/rw61x/README.md b/examples/thermostat/nxp/rt/rw61x/README.md new file mode 100644 index 00000000000000..6e7a90448467c4 --- /dev/null +++ b/examples/thermostat/nxp/rt/rw61x/README.md @@ -0,0 +1,5 @@ +# CHIP RW61x Thermostat Application + +All instructions describing how to use a Matter application on NXP RW61x can be +found in [README.md](../../../../all-clusters-app/nxp/rt/rw61x/README.md) root +readme diff --git a/examples/thermostat/nxp/rt/rw61x/args.gni b/examples/thermostat/nxp/rt/rw61x/args.gni new file mode 100644 index 00000000000000..c2d91a5db7bae7 --- /dev/null +++ b/examples/thermostat/nxp/rt/rw61x/args.gni @@ -0,0 +1,19 @@ +# Copyright (c) 2023 Project CHIP Authors +# +# 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("//build_overrides/chip.gni") + +# SDK target definitions +nxp_sdk_target = get_label_info(":sdk", "label_no_toolchain") +nxp_sdk_driver_target = get_label_info(":sdk_driver", "label_no_toolchain") diff --git a/examples/thermostat/nxp/rt/rw61x/build_overrides b/examples/thermostat/nxp/rt/rw61x/build_overrides new file mode 120000 index 00000000000000..f10867042f4d19 --- /dev/null +++ b/examples/thermostat/nxp/rt/rw61x/build_overrides @@ -0,0 +1 @@ +../../../../build_overrides \ No newline at end of file diff --git a/examples/thermostat/nxp/rt/rw61x/include/config/CHIPProjectConfig.h b/examples/thermostat/nxp/rt/rw61x/include/config/CHIPProjectConfig.h new file mode 100644 index 00000000000000..2653e97705fe39 --- /dev/null +++ b/examples/thermostat/nxp/rt/rw61x/include/config/CHIPProjectConfig.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2020 Project CHIP Authors + * Copyright (c) 2020 Google LLC. + * Copyright 2023 NXP + * 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. + */ + +/** + * @file + * Example project configuration file for CHIP. + * + * This is a place to put application or project-specific overrides + * to the default configuration values for general CHIP features. + * + */ + +#pragma once + +/* + * Tells to the platform Factory Data Provider whether to use the example configuration or real/provisioned data. + */ +#ifndef CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA +#define CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA 0 +#endif + +/** + * CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID + * + * 0xFFF1: Test vendor. + */ +#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID 0xFFF1 + +/** + * CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID + * + */ +#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID 0x8005 + +#if !CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA +// Use a default pairing code if one hasn't been provisioned in flash. +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 +#endif + +#ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR +#define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_DISCRIMINATOR 0xF00 +#endif + +// Use a default pairing code if one hasn't been provisioned in flash. +#define CHIP_DEVICE_CONFIG_USE_TEST_PAIRING_CODE "CHIPUS" + +/** + * CHIP_DEVICE_CONFIG_USE_TEST_SERIAL_NUMBER + * + * Enables the use of a hard-coded default serial number if none + * is found in CHIP NV storage. + */ +#define CHIP_DEVICE_CONFIG_USE_TEST_SERIAL_NUMBER "DUMMY_SN" + +#endif /* !CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA */ + +/** + * CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION + * + * The hardware version number assigned to device or product by the device vendor. This + * number is scoped to the device product id, and typically corresponds to a revision of the + * physical device, a change to its packaging, and/or a change to its marketing presentation. + * This value is generally *not* incremented for device software versions. + */ +#define CHIP_DEVICE_CONFIG_DEVICE_HARDWARE_VERSION 100 + +#ifndef CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING +#define CHIP_DEVICE_CONFIG_DEFAULT_DEVICE_HARDWARE_VERSION_STRING "v0.1.0" +#endif + +/** + * CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING + * + * A string identifying the software version running on the device. + * CHIP currently expects the software version to be in the format + * {MAJOR_VERSION}.0d{MINOR_VERSION} + */ +#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING +#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION_STRING NXP_CONFIG_DEVICE_SOFTWARE_VERSION_STRING +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION +#define CHIP_DEVICE_CONFIG_DEVICE_SOFTWARE_VERSION NXP_CONFIG_DEVICE_SOFTWARE_VERSION +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME +#define CHIP_DEVICE_CONFIG_DEVICE_VENDOR_NAME "NXP Semiconductors" +#endif + +#ifndef CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME +#define CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_NAME "NXP Demo App" +#endif + +#ifndef CHIP_DEVICE_CONFIG_CERTIFICATION_DECLARATION +//-> format_version = 1 +//-> vendor_id = 0xFFF1 +//-> product_id_array = [ 0x8000, 0x8001, 0x8002, 0x8003, 0x8004, 0x8005, 0x8006, 0x8007, 0x8008, 0x8009, 0x800A, 0x800B, +// 0x800C, 0x800D, 0x800E, 0x800F, 0x8010, 0x8011, 0x8012, 0x8013, 0x8014, 0x8015, 0x8016, 0x8017, 0x8018, 0x8019, 0x801A, +// 0x801B, 0x801C, 0x801D, 0x801E, 0x801F, 0x8020, 0x8021, 0x8022, 0x8023, 0x8024, 0x8025, 0x8026, 0x8027, 0x8028, 0x8029, +// 0x802A, 0x802B, 0x802C, 0x802D, 0x802E, 0x802F, 0x8030, 0x8031, 0x8032, 0x8033, 0x8034, 0x8035, 0x8036, 0x8037, 0x8038, +// 0x8039, 0x803A, 0x803B, 0x803C, 0x803D, 0x803E, 0x803F, 0x8040, 0x8041, 0x8042, 0x8043, 0x8044, 0x8045, 0x8046, 0x8047, +// 0x8048, 0x8049, 0x804A, 0x804B, 0x804C, 0x804D, 0x804E, 0x804F, 0x8050, 0x8051, 0x8052, 0x8053, 0x8054, 0x8055, 0x8056, +// 0x8057, 0x8058, 0x8059, 0x805A, 0x805B, 0x805C, 0x805D, 0x805E, 0x805F, 0x8060, 0x8061, 0x8062, 0x8063 ] +//-> device_type_id = 0x0016 +//-> certificate_id = "ZIG20142ZB330003-24" +//-> security_level = 0 +//-> security_information = 0 +//-> version_number = 0x2694 +//-> certification_type = 0 +//-> dac_origin_vendor_id is not present +//-> dac_origin_product_id is not present +#define CHIP_DEVICE_CONFIG_CERTIFICATION_DECLARATION \ + { \ + 0x30, 0x82, 0x02, 0x19, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, 0xa0, 0x82, 0x02, 0x0a, 0x30, \ + 0x82, 0x02, 0x06, 0x02, 0x01, 0x03, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, \ + 0x02, 0x01, 0x30, 0x82, 0x01, 0x71, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x82, \ + 0x01, 0x62, 0x04, 0x82, 0x01, 0x5e, 0x15, 0x24, 0x00, 0x01, 0x25, 0x01, 0xf1, 0xff, 0x36, 0x02, 0x05, 0x00, 0x80, \ + 0x05, 0x01, 0x80, 0x05, 0x02, 0x80, 0x05, 0x03, 0x80, 0x05, 0x04, 0x80, 0x05, 0x05, 0x80, 0x05, 0x06, 0x80, 0x05, \ + 0x07, 0x80, 0x05, 0x08, 0x80, 0x05, 0x09, 0x80, 0x05, 0x0a, 0x80, 0x05, 0x0b, 0x80, 0x05, 0x0c, 0x80, 0x05, 0x0d, \ + 0x80, 0x05, 0x0e, 0x80, 0x05, 0x0f, 0x80, 0x05, 0x10, 0x80, 0x05, 0x11, 0x80, 0x05, 0x12, 0x80, 0x05, 0x13, 0x80, \ + 0x05, 0x14, 0x80, 0x05, 0x15, 0x80, 0x05, 0x16, 0x80, 0x05, 0x17, 0x80, 0x05, 0x18, 0x80, 0x05, 0x19, 0x80, 0x05, \ + 0x1a, 0x80, 0x05, 0x1b, 0x80, 0x05, 0x1c, 0x80, 0x05, 0x1d, 0x80, 0x05, 0x1e, 0x80, 0x05, 0x1f, 0x80, 0x05, 0x20, \ + 0x80, 0x05, 0x21, 0x80, 0x05, 0x22, 0x80, 0x05, 0x23, 0x80, 0x05, 0x24, 0x80, 0x05, 0x25, 0x80, 0x05, 0x26, 0x80, \ + 0x05, 0x27, 0x80, 0x05, 0x28, 0x80, 0x05, 0x29, 0x80, 0x05, 0x2a, 0x80, 0x05, 0x2b, 0x80, 0x05, 0x2c, 0x80, 0x05, \ + 0x2d, 0x80, 0x05, 0x2e, 0x80, 0x05, 0x2f, 0x80, 0x05, 0x30, 0x80, 0x05, 0x31, 0x80, 0x05, 0x32, 0x80, 0x05, 0x33, \ + 0x80, 0x05, 0x34, 0x80, 0x05, 0x35, 0x80, 0x05, 0x36, 0x80, 0x05, 0x37, 0x80, 0x05, 0x38, 0x80, 0x05, 0x39, 0x80, \ + 0x05, 0x3a, 0x80, 0x05, 0x3b, 0x80, 0x05, 0x3c, 0x80, 0x05, 0x3d, 0x80, 0x05, 0x3e, 0x80, 0x05, 0x3f, 0x80, 0x05, \ + 0x40, 0x80, 0x05, 0x41, 0x80, 0x05, 0x42, 0x80, 0x05, 0x43, 0x80, 0x05, 0x44, 0x80, 0x05, 0x45, 0x80, 0x05, 0x46, \ + 0x80, 0x05, 0x47, 0x80, 0x05, 0x48, 0x80, 0x05, 0x49, 0x80, 0x05, 0x4a, 0x80, 0x05, 0x4b, 0x80, 0x05, 0x4c, 0x80, \ + 0x05, 0x4d, 0x80, 0x05, 0x4e, 0x80, 0x05, 0x4f, 0x80, 0x05, 0x50, 0x80, 0x05, 0x51, 0x80, 0x05, 0x52, 0x80, 0x05, \ + 0x53, 0x80, 0x05, 0x54, 0x80, 0x05, 0x55, 0x80, 0x05, 0x56, 0x80, 0x05, 0x57, 0x80, 0x05, 0x58, 0x80, 0x05, 0x59, \ + 0x80, 0x05, 0x5a, 0x80, 0x05, 0x5b, 0x80, 0x05, 0x5c, 0x80, 0x05, 0x5d, 0x80, 0x05, 0x5e, 0x80, 0x05, 0x5f, 0x80, \ + 0x05, 0x60, 0x80, 0x05, 0x61, 0x80, 0x05, 0x62, 0x80, 0x05, 0x63, 0x80, 0x18, 0x24, 0x03, 0x16, 0x2c, 0x04, 0x13, \ + 0x5a, 0x49, 0x47, 0x32, 0x30, 0x31, 0x34, 0x32, 0x5a, 0x42, 0x33, 0x33, 0x30, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x34, \ + 0x24, 0x05, 0x00, 0x24, 0x06, 0x00, 0x25, 0x07, 0x94, 0x26, 0x24, 0x08, 0x00, 0x18, 0x31, 0x7d, 0x30, 0x7b, 0x02, \ + 0x01, 0x03, 0x80, 0x14, 0x62, 0xfa, 0x82, 0x33, 0x59, 0xac, 0xfa, 0xa9, 0x96, 0x3e, 0x1c, 0xfa, 0x14, 0x0a, 0xdd, \ + 0xf5, 0x04, 0xf3, 0x71, 0x60, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, \ + 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x04, 0x47, 0x30, 0x45, 0x02, 0x20, 0x24, 0xe5, \ + 0xd1, 0xf4, 0x7a, 0x7d, 0x7b, 0x0d, 0x20, 0x6a, 0x26, 0xef, 0x69, 0x9b, 0x7c, 0x97, 0x57, 0xb7, 0x2d, 0x46, 0x90, \ + 0x89, 0xde, 0x31, 0x92, 0xe6, 0x78, 0xc7, 0x45, 0xe7, 0xf6, 0x0c, 0x02, 0x21, 0x00, 0xf8, 0xaa, 0x2f, 0xa7, 0x11, \ + 0xfc, 0xb7, 0x9b, 0x97, 0xe3, 0x97, 0xce, 0xda, 0x66, 0x7b, 0xae, 0x46, 0x4e, 0x2b, 0xd3, 0xff, 0xdf, 0xc3, 0xcc, \ + 0xed, 0x7a, 0xa8, 0xca, 0x5f, 0x4c, 0x1a, 0x7c \ + } +#endif + +/** + * CHIP_DEVICE_CONFIG_ENABLE_CHIP_TIME_SERVICE_TIME_SYNC + * + * Enables synchronizing the device's real time clock with a remote CHIP Time service + * using the CHIP Time Sync protocol. + */ +// #define CHIP_DEVICE_CONFIG_ENABLE_CHIP_TIME_SERVICE_TIME_SYNC 1 + +/** + * CHIP_CONFIG_MAX_BINDINGS + * + * Maximum number of simultaneously active bindings per ChipExchangeManager + * 1 (Time Sync) + 2 (Two 1-way subscriptions) + 1 (Software Update) = 4 + * in the worst case. Keeping another 4 as buffer. + */ +#define CHIP_CONFIG_MAX_BINDINGS 6 + +/** + * CHIP_CONFIG_EVENT_LOGGING_WDM_OFFLOAD + * + * Select the ability to offload event logs to any interested subscribers using WDM. + */ +#define CHIP_CONFIG_EVENT_LOGGING_WDM_OFFLOAD 1 + +/** + * CHIP_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS + * + * Enable recording UTC timestamps. + */ +#define CHIP_CONFIG_EVENT_LOGGING_UTC_TIMESTAMPS 1 + +/** + * CHIP_DEVICE_CONFIG_EVENT_LOGGING_DEBUG_BUFFER_SIZE + * + * A size, in bytes, of the individual debug event logging buffer. + */ +#define CHIP_DEVICE_CONFIG_EVENT_LOGGING_DEBUG_BUFFER_SIZE (512) + +/** + * CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE + * + * For a development build, set the default importance of events to be logged as Debug. + * Since debug is the lowest importance level, this means all standard, critical, info and + * debug importance level vi events get logged. + */ +#if BUILD_RELEASE +#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Production +#else +#define CHIP_CONFIG_EVENT_LOGGING_DEFAULT_IMPORTANCE chip::Profiles::DataManagement::Debug +#endif // BUILD_RELEASE + +/* Increasing the retransmission interval of the MRP messages after subsequent failures */ +#ifndef CHIP_CONFIG_MRP_LOCAL_ACTIVE_RETRY_INTERVAL +#define CHIP_CONFIG_MRP_LOCAL_ACTIVE_RETRY_INTERVAL (2000_ms32) +#endif diff --git a/examples/thermostat/nxp/rt/rw61x/third_party/connectedhomeip b/examples/thermostat/nxp/rt/rw61x/third_party/connectedhomeip new file mode 120000 index 00000000000000..305f2077ffe860 --- /dev/null +++ b/examples/thermostat/nxp/rt/rw61x/third_party/connectedhomeip @@ -0,0 +1 @@ +../../../../../.. \ No newline at end of file diff --git a/examples/thermostat/nxp/zap/thermostat_matter_thread.matter b/examples/thermostat/nxp/zap/thermostat_matter_thread.matter index 37bcbfae360373..c2c580189efe77 100644 --- a/examples/thermostat/nxp/zap/thermostat_matter_thread.matter +++ b/examples/thermostat/nxp/zap/thermostat_matter_thread.matter @@ -2269,10 +2269,15 @@ endpoint 0 { server cluster Identify { ram attribute identifyTime default = 0x0000; ram attribute identifyType default = 0x00; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 4; handle command Identify; + handle command TriggerEffect; } server cluster Descriptor { @@ -2318,16 +2323,23 @@ endpoint 0 { callback attribute productLabel; callback attribute serialNumber; persist attribute localConfigDisabled default = 0; - ram attribute reachable default = 1; callback attribute uniqueID; callback attribute capabilityMinima; + callback attribute productAppearance; callback attribute specificationVersion; callback attribute maxPathsPerInvoke; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; - ram attribute clusterRevision default = 3; + ram attribute clusterRevision default = 1; } server cluster OtaSoftwareUpdateRequestor { + emits event StateTransition; + emits event VersionApplied; + emits event DownloadError; callback attribute defaultOTAProviders; ram attribute updatePossible default = 1; ram attribute updateState default = 0; @@ -2341,20 +2353,29 @@ endpoint 0 { server cluster LocalizationConfiguration { persist attribute activeLocale default = "en-US"; callback attribute supportedLocales; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 1; } server cluster TimeFormatLocalization { persist attribute hourFormat default = 0; - persist attribute activeCalendarType default = 0; - callback attribute supportedCalendarTypes; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 1; } server cluster UnitLocalization { - persist attribute temperatureUnit default = 0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0x1; ram attribute clusterRevision default = 1; } @@ -2385,6 +2406,10 @@ endpoint 0 { ram attribute lastNetworkingStatus; ram attribute lastNetworkID; ram attribute lastConnectErrorValue; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 2; ram attribute clusterRevision default = 1; @@ -2406,10 +2431,11 @@ endpoint 0 { callback attribute upTime; callback attribute totalOperationalHours; callback attribute bootReason; - callback attribute activeHardwareFaults; - callback attribute activeRadioFaults; - callback attribute activeNetworkFaults; callback attribute testEventTriggersEnabled default = false; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; callback attribute featureMap; callback attribute clusterRevision; @@ -2486,7 +2512,7 @@ endpoint 0 { callback attribute acceptedCommandList; callback attribute attributeList; ram attribute featureMap default = 0x000F; - ram attribute clusterRevision default = 2; + ram attribute clusterRevision default = 1; handle command ResetCounts; } @@ -2499,7 +2525,6 @@ endpoint 0 { ram attribute clusterRevision default = 1; handle command OpenCommissioningWindow; - handle command OpenBasicCommissioningWindow; handle command RevokeCommissioning; } @@ -2551,10 +2576,15 @@ endpoint 1 { server cluster Identify { ram attribute identifyTime default = 0x0000; ram attribute identifyType default = 0x0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 4; handle command Identify; + handle command TriggerEffect; } server cluster Groups { @@ -2601,6 +2631,7 @@ endpoint 1 { callback attribute endpointList; callback attribute generatedCommandList; callback attribute acceptedCommandList; + callback attribute eventList; callback attribute attributeList; ram attribute featureMap default = 1; ram attribute clusterRevision default = 1; @@ -2624,10 +2655,11 @@ endpoint 1 { } server cluster ScenesManagement { - ram attribute sceneTableSize; + callback attribute lastConfiguredBy default = 0x00; + ram attribute sceneTableSize default = 0x10; callback attribute fabricSceneInfo; - ram attribute featureMap default = 1; - ram attribute clusterRevision default = 1; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 4; handle command AddScene; handle command AddSceneResponse; diff --git a/examples/thermostat/nxp/zap/thermostat_matter_thread.zap b/examples/thermostat/nxp/zap/thermostat_matter_thread.zap index 6079af2bea9471..aa8b54c82574b9 100644 --- a/examples/thermostat/nxp/zap/thermostat_matter_thread.zap +++ b/examples/thermostat/nxp/zap/thermostat_matter_thread.zap @@ -1,6 +1,6 @@ { "fileFormat": 2, - "featureLevel": 100, + "featureLevel": 99, "creator": "zap", "keyValuePairs": [ { @@ -75,6 +75,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "TriggerEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 } ], "attributes": [ @@ -110,6 +118,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -685,46 +757,46 @@ "reportableChange": 0 }, { - "name": "Reachable", - "code": 17, + "name": "UniqueID", + "code": 18, "mfgCode": null, "side": "server", - "type": "boolean", + "type": "char_string", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 1, "bounded": 0, - "defaultValue": "1", + "defaultValue": null, "reportable": 1, "minInterval": 0, "maxInterval": 65344, "reportableChange": 0 }, { - "name": "UniqueID", - "code": 18, + "name": "CapabilityMinima", + "code": 19, "mfgCode": null, "side": "server", - "type": "char_string", + "type": "CapabilityMinimaStruct", "included": 1, "storageOption": "External", - "singleton": 1, + "singleton": 0, "bounded": 0, "defaultValue": null, "reportable": 1, - "minInterval": 0, - "maxInterval": 65344, + "minInterval": 1, + "maxInterval": 65534, "reportableChange": 0 }, { - "name": "CapabilityMinima", - "code": 19, + "name": "ProductAppearance", + "code": 20, "mfgCode": null, "side": "server", - "type": "CapabilityMinimaStruct", + "type": "ProductAppearanceStruct", "included": 1, "storageOption": "External", - "singleton": 0, + "singleton": 1, "bounded": 0, "defaultValue": null, "reportable": 1, @@ -764,6 +836,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -790,7 +926,7 @@ "storageOption": "RAM", "singleton": 1, "bounded": 0, - "defaultValue": "3", + "defaultValue": "1", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -1003,6 +1139,29 @@ "maxInterval": 65534, "reportableChange": 0 } + ], + "events": [ + { + "name": "StateTransition", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "VersionApplied", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "DownloadError", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + } ] }, { @@ -1045,6 +1204,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -1104,24 +1327,56 @@ "reportableChange": 0 }, { - "name": "ActiveCalendarType", - "code": 1, + "name": "GeneratedCommandList", + "code": 65528, "mfgCode": null, "side": "server", - "type": "CalendarTypeEnum", + "type": "array", "included": 1, - "storageOption": "NVM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 }, { - "name": "SupportedCalendarTypes", - "code": 2, + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, "mfgCode": null, "side": "server", "type": "array", @@ -1178,16 +1433,64 @@ "enabled": 1, "attributes": [ { - "name": "TemperatureUnit", - "code": 0, + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, "mfgCode": null, "side": "server", - "type": "TempUnitEnum", + "type": "array", "included": 1, - "storageOption": "NVM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -1609,6 +1912,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -1758,8 +2125,24 @@ "reportableChange": 0 }, { - "name": "ActiveHardwareFaults", - "code": 5, + "name": "TestEventTriggersEnabled", + "code": 8, + "mfgCode": null, + "side": "server", + "type": "boolean", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "false", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "GeneratedCommandList", + "code": 65528, "mfgCode": null, "side": "server", "type": "array", @@ -1774,8 +2157,8 @@ "reportableChange": 0 }, { - "name": "ActiveRadioFaults", - "code": 6, + "name": "AcceptedCommandList", + "code": 65529, "mfgCode": null, "side": "server", "type": "array", @@ -1790,8 +2173,8 @@ "reportableChange": 0 }, { - "name": "ActiveNetworkFaults", - "code": 7, + "name": "EventList", + "code": 65530, "mfgCode": null, "side": "server", "type": "array", @@ -1806,16 +2189,16 @@ "reportableChange": 0 }, { - "name": "TestEventTriggersEnabled", - "code": 8, + "name": "AttributeList", + "code": 65531, "mfgCode": null, "side": "server", - "type": "boolean", + "type": "array", "included": 1, "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "false", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -2964,7 +3347,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "2", + "defaultValue": "1", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -2988,14 +3371,6 @@ "isIncoming": 1, "isEnabled": 1 }, - { - "name": "OpenBasicCommissioningWindow", - "code": 1, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 - }, { "name": "RevokeCommissioning", "code": 2, @@ -3524,9 +3899,33 @@ "source": "client", "isIncoming": 0, "isEnabled": 1 + }, + { + "name": "TriggerEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 } ], "attributes": [ + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "client", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "ClusterRevision", "code": 65533, @@ -3560,6 +3959,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "TriggerEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 } ], "attributes": [ @@ -3595,6 +4002,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -4117,6 +4588,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "AttributeList", "code": 65531, @@ -4478,6 +4965,22 @@ } ], "attributes": [ + { + "name": "LastConfiguredBy", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "node_id", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x00", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, { "name": "SceneTableSize", "code": 1, @@ -4488,10 +4991,10 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x10", "reportable": 1, - "minInterval": 1, - "maxInterval": 65534, + "minInterval": 0, + "maxInterval": 65344, "reportableChange": 0 }, { @@ -4506,8 +5009,8 @@ "bounded": 0, "defaultValue": null, "reportable": 1, - "minInterval": 1, - "maxInterval": 65534, + "minInterval": 0, + "maxInterval": 65344, "reportableChange": 0 }, { @@ -4520,7 +5023,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "1", + "defaultValue": "0", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -4536,7 +5039,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "1", + "defaultValue": "4", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -4893,16 +5396,14 @@ "endpointTypeIndex": 0, "profileId": 259, "endpointId": 0, - "networkId": 0, - "parentEndpointIdentifier": null + "networkId": 0 }, { "endpointTypeName": "MA-thermostat", "endpointTypeIndex": 1, "profileId": 259, "endpointId": 1, - "networkId": 0, - "parentEndpointIdentifier": null + "networkId": 0 } ] } \ No newline at end of file diff --git a/examples/thermostat/nxp/zap/thermostat_matter_wifi.matter b/examples/thermostat/nxp/zap/thermostat_matter_wifi.matter index ff712e71043379..e9b2cc844f67ef 100644 --- a/examples/thermostat/nxp/zap/thermostat_matter_wifi.matter +++ b/examples/thermostat/nxp/zap/thermostat_matter_wifi.matter @@ -2180,10 +2180,15 @@ endpoint 0 { server cluster Identify { ram attribute identifyTime default = 0x0000; ram attribute identifyType default = 0x00; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 4; handle command Identify; + handle command TriggerEffect; } server cluster Descriptor { @@ -2229,16 +2234,23 @@ endpoint 0 { callback attribute productLabel; callback attribute serialNumber; persist attribute localConfigDisabled default = 0; - ram attribute reachable default = 1; callback attribute uniqueID; callback attribute capabilityMinima; + callback attribute productAppearance; callback attribute specificationVersion; callback attribute maxPathsPerInvoke; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; - ram attribute clusterRevision default = 3; + ram attribute clusterRevision default = 1; } server cluster OtaSoftwareUpdateRequestor { + emits event StateTransition; + emits event VersionApplied; + emits event DownloadError; callback attribute defaultOTAProviders; ram attribute updatePossible default = 1; ram attribute updateState default = 0; @@ -2252,20 +2264,29 @@ endpoint 0 { server cluster LocalizationConfiguration { persist attribute activeLocale default = "en-US"; callback attribute supportedLocales; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 1; } server cluster TimeFormatLocalization { persist attribute hourFormat default = 0; - persist attribute activeCalendarType default = 0; - callback attribute supportedCalendarTypes; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 1; } server cluster UnitLocalization { - persist attribute temperatureUnit default = 0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0x1; ram attribute clusterRevision default = 1; } @@ -2296,6 +2317,10 @@ endpoint 0 { ram attribute lastNetworkingStatus; ram attribute lastNetworkID; ram attribute lastConnectErrorValue; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 2; ram attribute clusterRevision default = 1; @@ -2317,10 +2342,11 @@ endpoint 0 { callback attribute upTime; callback attribute totalOperationalHours; callback attribute bootReason; - callback attribute activeHardwareFaults; - callback attribute activeRadioFaults; - callback attribute activeNetworkFaults; callback attribute testEventTriggersEnabled default = false; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; callback attribute featureMap; callback attribute clusterRevision; @@ -2335,14 +2361,11 @@ endpoint 0 { callback attribute wiFiVersion; callback attribute channelNumber; callback attribute rssi; - callback attribute beaconLostCount; - callback attribute beaconRxCount; - callback attribute packetMulticastRxCount; - callback attribute packetMulticastTxCount; - callback attribute packetUnicastRxCount; - callback attribute packetUnicastTxCount; callback attribute currentMaxRate; - callback attribute overrunCount; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 1; } @@ -2355,7 +2378,6 @@ endpoint 0 { ram attribute clusterRevision default = 1; handle command OpenCommissioningWindow; - handle command OpenBasicCommissioningWindow; handle command RevokeCommissioning; } @@ -2407,10 +2429,15 @@ endpoint 1 { server cluster Identify { ram attribute identifyTime default = 0x0000; ram attribute identifyType default = 0x0; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; ram attribute featureMap default = 0; ram attribute clusterRevision default = 4; handle command Identify; + handle command TriggerEffect; } server cluster Groups { @@ -2457,6 +2484,7 @@ endpoint 1 { callback attribute endpointList; callback attribute generatedCommandList; callback attribute acceptedCommandList; + callback attribute eventList; callback attribute attributeList; ram attribute featureMap default = 1; ram attribute clusterRevision default = 1; @@ -2480,10 +2508,11 @@ endpoint 1 { } server cluster ScenesManagement { - ram attribute sceneTableSize; + callback attribute lastConfiguredBy default = 0x00; + ram attribute sceneTableSize default = 0x10; callback attribute fabricSceneInfo; - ram attribute featureMap default = 1; - ram attribute clusterRevision default = 1; + ram attribute featureMap default = 0; + ram attribute clusterRevision default = 4; handle command AddScene; handle command AddSceneResponse; diff --git a/examples/thermostat/nxp/zap/thermostat_matter_wifi.zap b/examples/thermostat/nxp/zap/thermostat_matter_wifi.zap index 7f667adb460ce1..9e18152bebf2bd 100644 --- a/examples/thermostat/nxp/zap/thermostat_matter_wifi.zap +++ b/examples/thermostat/nxp/zap/thermostat_matter_wifi.zap @@ -1,6 +1,6 @@ { "fileFormat": 2, - "featureLevel": 100, + "featureLevel": 99, "creator": "zap", "keyValuePairs": [ { @@ -75,6 +75,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "TriggerEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 } ], "attributes": [ @@ -110,6 +118,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -685,46 +757,46 @@ "reportableChange": 0 }, { - "name": "Reachable", - "code": 17, + "name": "UniqueID", + "code": 18, "mfgCode": null, "side": "server", - "type": "boolean", + "type": "char_string", "included": 1, - "storageOption": "RAM", + "storageOption": "External", "singleton": 1, "bounded": 0, - "defaultValue": "1", + "defaultValue": null, "reportable": 1, "minInterval": 0, "maxInterval": 65344, "reportableChange": 0 }, { - "name": "UniqueID", - "code": 18, + "name": "CapabilityMinima", + "code": 19, "mfgCode": null, "side": "server", - "type": "char_string", + "type": "CapabilityMinimaStruct", "included": 1, "storageOption": "External", - "singleton": 1, + "singleton": 0, "bounded": 0, "defaultValue": null, "reportable": 1, - "minInterval": 0, - "maxInterval": 65344, + "minInterval": 1, + "maxInterval": 65534, "reportableChange": 0 }, { - "name": "CapabilityMinima", - "code": 19, + "name": "ProductAppearance", + "code": 20, "mfgCode": null, "side": "server", - "type": "CapabilityMinimaStruct", + "type": "ProductAppearanceStruct", "included": 1, "storageOption": "External", - "singleton": 0, + "singleton": 1, "bounded": 0, "defaultValue": null, "reportable": 1, @@ -764,6 +836,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 1, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -790,7 +926,7 @@ "storageOption": "RAM", "singleton": 1, "bounded": 0, - "defaultValue": "3", + "defaultValue": "1", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -1003,6 +1139,29 @@ "maxInterval": 65534, "reportableChange": 0 } + ], + "events": [ + { + "name": "StateTransition", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "VersionApplied", + "code": 1, + "mfgCode": null, + "side": "server", + "included": 1 + }, + { + "name": "DownloadError", + "code": 2, + "mfgCode": null, + "side": "server", + "included": 1 + } ] }, { @@ -1045,6 +1204,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -1104,24 +1327,56 @@ "reportableChange": 0 }, { - "name": "ActiveCalendarType", - "code": 1, + "name": "GeneratedCommandList", + "code": 65528, "mfgCode": null, "side": "server", - "type": "CalendarTypeEnum", + "type": "array", "included": 1, - "storageOption": "NVM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 }, { - "name": "SupportedCalendarTypes", - "code": 2, + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, "mfgCode": null, "side": "server", "type": "array", @@ -1178,16 +1433,64 @@ "enabled": 1, "attributes": [ { - "name": "TemperatureUnit", - "code": 0, + "name": "GeneratedCommandList", + "code": 65528, "mfgCode": null, "side": "server", - "type": "TempUnitEnum", + "type": "array", "included": 1, - "storageOption": "NVM", + "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -1609,6 +1912,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -1726,11 +2093,27 @@ "reportableChange": 0 }, { - "name": "TotalOperationalHours", - "code": 3, + "name": "TotalOperationalHours", + "code": 3, + "mfgCode": null, + "side": "server", + "type": "int32u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "BootReason", + "code": 4, "mfgCode": null, "side": "server", - "type": "int32u", + "type": "BootReasonEnum", "included": 1, "storageOption": "External", "singleton": 0, @@ -1742,24 +2125,24 @@ "reportableChange": 0 }, { - "name": "BootReason", - "code": 4, + "name": "TestEventTriggersEnabled", + "code": 8, "mfgCode": null, "side": "server", - "type": "BootReasonEnum", + "type": "boolean", "included": 1, "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": null, + "defaultValue": "false", "reportable": 1, "minInterval": 1, "maxInterval": 65534, "reportableChange": 0 }, { - "name": "ActiveHardwareFaults", - "code": 5, + "name": "GeneratedCommandList", + "code": 65528, "mfgCode": null, "side": "server", "type": "array", @@ -1774,8 +2157,8 @@ "reportableChange": 0 }, { - "name": "ActiveRadioFaults", - "code": 6, + "name": "AcceptedCommandList", + "code": 65529, "mfgCode": null, "side": "server", "type": "array", @@ -1790,8 +2173,8 @@ "reportableChange": 0 }, { - "name": "ActiveNetworkFaults", - "code": 7, + "name": "EventList", + "code": 65530, "mfgCode": null, "side": "server", "type": "array", @@ -1806,16 +2189,16 @@ "reportableChange": 0 }, { - "name": "TestEventTriggersEnabled", - "code": 8, + "name": "AttributeList", + "code": 65531, "mfgCode": null, "side": "server", - "type": "boolean", + "type": "array", "included": 1, "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "false", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -1953,59 +2336,11 @@ "reportableChange": 0 }, { - "name": "BeaconLostCount", - "code": 5, - "mfgCode": null, - "side": "server", - "type": "int32u", - "included": 1, - "storageOption": "External", - "singleton": 0, - "bounded": 0, - "defaultValue": null, - "reportable": 1, - "minInterval": 0, - "maxInterval": 65344, - "reportableChange": 0 - }, - { - "name": "BeaconRxCount", - "code": 6, - "mfgCode": null, - "side": "server", - "type": "int32u", - "included": 1, - "storageOption": "External", - "singleton": 0, - "bounded": 0, - "defaultValue": null, - "reportable": 1, - "minInterval": 0, - "maxInterval": 65344, - "reportableChange": 0 - }, - { - "name": "PacketMulticastRxCount", - "code": 7, - "mfgCode": null, - "side": "server", - "type": "int32u", - "included": 1, - "storageOption": "External", - "singleton": 0, - "bounded": 0, - "defaultValue": null, - "reportable": 1, - "minInterval": 0, - "maxInterval": 65344, - "reportableChange": 0 - }, - { - "name": "PacketMulticastTxCount", - "code": 8, + "name": "CurrentMaxRate", + "code": 11, "mfgCode": null, "side": "server", - "type": "int32u", + "type": "int64u", "included": 1, "storageOption": "External", "singleton": 0, @@ -2017,59 +2352,59 @@ "reportableChange": 0 }, { - "name": "PacketUnicastRxCount", - "code": 9, + "name": "GeneratedCommandList", + "code": 65528, "mfgCode": null, "side": "server", - "type": "int32u", + "type": "array", "included": 1, "storageOption": "External", "singleton": 0, "bounded": 0, "defaultValue": null, "reportable": 1, - "minInterval": 0, - "maxInterval": 65344, + "minInterval": 1, + "maxInterval": 65534, "reportableChange": 0 }, { - "name": "PacketUnicastTxCount", - "code": 10, + "name": "AcceptedCommandList", + "code": 65529, "mfgCode": null, "side": "server", - "type": "int32u", + "type": "array", "included": 1, "storageOption": "External", "singleton": 0, "bounded": 0, "defaultValue": null, "reportable": 1, - "minInterval": 0, - "maxInterval": 65344, + "minInterval": 1, + "maxInterval": 65534, "reportableChange": 0 }, { - "name": "CurrentMaxRate", - "code": 11, + "name": "EventList", + "code": 65530, "mfgCode": null, "side": "server", - "type": "int64u", + "type": "array", "included": 1, "storageOption": "External", "singleton": 0, "bounded": 0, "defaultValue": null, "reportable": 1, - "minInterval": 0, - "maxInterval": 65344, + "minInterval": 1, + "maxInterval": 65534, "reportableChange": 0 }, { - "name": "OverrunCount", - "code": 12, + "name": "AttributeList", + "code": 65531, "mfgCode": null, "side": "server", - "type": "int64u", + "type": "array", "included": 1, "storageOption": "External", "singleton": 0, @@ -2130,14 +2465,6 @@ "isIncoming": 1, "isEnabled": 1 }, - { - "name": "OpenBasicCommissioningWindow", - "code": 1, - "mfgCode": null, - "source": "client", - "isIncoming": 1, - "isEnabled": 1 - }, { "name": "RevokeCommissioning", "code": 2, @@ -2666,9 +2993,33 @@ "source": "client", "isIncoming": 0, "isEnabled": 1 + }, + { + "name": "TriggerEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 0, + "isEnabled": 1 } ], "attributes": [ + { + "name": "FeatureMap", + "code": 65532, + "mfgCode": null, + "side": "client", + "type": "bitmap32", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "ClusterRevision", "code": 65533, @@ -2702,6 +3053,14 @@ "source": "client", "isIncoming": 1, "isEnabled": 1 + }, + { + "name": "TriggerEffect", + "code": 64, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 } ], "attributes": [ @@ -2737,6 +3096,70 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "GeneratedCommandList", + "code": 65528, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AcceptedCommandList", + "code": 65529, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "AttributeList", + "code": 65531, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -3259,6 +3682,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "AttributeList", "code": 65531, @@ -3620,6 +4059,22 @@ } ], "attributes": [ + { + "name": "LastConfiguredBy", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "node_id", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": "0x00", + "reportable": 1, + "minInterval": 0, + "maxInterval": 65344, + "reportableChange": 0 + }, { "name": "SceneTableSize", "code": 1, @@ -3630,10 +4085,10 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "0x10", "reportable": 1, - "minInterval": 1, - "maxInterval": 65534, + "minInterval": 0, + "maxInterval": 65344, "reportableChange": 0 }, { @@ -3648,8 +4103,8 @@ "bounded": 0, "defaultValue": null, "reportable": 1, - "minInterval": 1, - "maxInterval": 65534, + "minInterval": 0, + "maxInterval": 65344, "reportableChange": 0 }, { @@ -3662,7 +4117,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "1", + "defaultValue": "0", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3678,7 +4133,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "1", + "defaultValue": "4", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -4035,16 +4490,14 @@ "endpointTypeIndex": 0, "profileId": 259, "endpointId": 0, - "networkId": 0, - "parentEndpointIdentifier": null + "networkId": 0 }, { "endpointTypeName": "MA-thermostat", "endpointTypeIndex": 1, "profileId": 259, "endpointId": 1, - "networkId": 0, - "parentEndpointIdentifier": null + "networkId": 0 } ] } \ No newline at end of file diff --git a/scripts/build/BUILD.gn b/scripts/build/BUILD.gn index 0e716ae7e9b390..6b13a8c1011ebd 100644 --- a/scripts/build/BUILD.gn +++ b/scripts/build/BUILD.gn @@ -59,6 +59,7 @@ pw_python_package("build_examples") { "builders/nrf.py", "builders/openiotsdk.py", "builders/qpg.py", + "builders/rw61x.py", "builders/telink.py", "builders/tizen.py", "runner/__init__.py", diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py index af1bd454b86ddb..8bc69a2fc620f4 100755 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -30,6 +30,7 @@ from builders.nrf import NrfApp, NrfBoard, NrfConnectBuilder from builders.openiotsdk import OpenIotSdkApp, OpenIotSdkBuilder, OpenIotSdkCryptoBackend from builders.qpg import QpgApp, QpgBoard, QpgBuilder +from builders.rw61x import RW61XApp, RW61XBuilder from builders.stm32 import stm32App, stm32Board, stm32Builder from builders.telink import TelinkApp, TelinkBoard, TelinkBuilder from builders.ti import TIApp, TIBoard, TIBuilder @@ -703,6 +704,25 @@ def BuildMW320Target(): return target +def BuildRW61XTarget(): + target = BuildTarget('rw61x', RW61XBuilder) + + # apps + target.AppendFixedTargets([ + TargetPart('all-clusters-app', app=RW61XApp.ALL_CLUSTERS, release=True), + TargetPart('thermostat', app=RW61XApp.THERMOSTAT, release=True), + TargetPart('laundry-washer', app=RW61XApp.LAUNDRY_WASHER, release=True), + ]) + + target.AppendModifier(name="ota", enable_ota=True) + target.AppendModifier(name="wifi", enable_wifi=True) + target.AppendModifier(name="thread", enable_thread=True) + target.AppendModifier(name="factory-data", enable_factory_data=True) + target.AppendModifier(name="matter-shell", enable_shell=True) + + return target + + def BuildGenioTarget(): target = BuildTarget('genio', GenioBuilder) target.AppendFixedTargets([TargetPart('lighting-app', app=GenioApp.LIGHT)]) @@ -783,6 +803,7 @@ def BuildOpenIotSdkTargets(): BuildHostTestRunnerTarget(), BuildIMXTarget(), BuildInfineonTarget(), + BuildRW61XTarget(), BuildK32WTarget(), BuildMbedTarget(), BuildMW320Target(), diff --git a/scripts/build/builders/k32w.py b/scripts/build/builders/k32w.py index 1cf7862d3d8739..86ba55217c64a5 100644 --- a/scripts/build/builders/k32w.py +++ b/scripts/build/builders/k32w.py @@ -111,7 +111,9 @@ def GnBuildArgs(self): args = [] if self.low_power: - args.append('chip_with_low_power=1') + args.append('chip_with_low_power=1 chip_logging=false') + if self.board == K32WBoard.K32W0: + args.append('chip_pw_tokenizer_logging=false chip_with_OM15082=0') else: args.append('chip_with_low_power=0') diff --git a/scripts/build/builders/rw61x.py b/scripts/build/builders/rw61x.py new file mode 100644 index 00000000000000..546c6549e2aab9 --- /dev/null +++ b/scripts/build/builders/rw61x.py @@ -0,0 +1,124 @@ +# Copyright (c) 2023 Project CHIP Authors +# +# 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 os +from enum import Enum, auto + +from .gn import GnBuilder + + +class RW61XApp(Enum): + ALL_CLUSTERS = auto() + THERMOSTAT = auto() + LAUNDRY_WASHER = auto() + + def ExampleName(self): + if self == RW61XApp.ALL_CLUSTERS: + return 'all-clusters-app' + elif self == RW61XApp.THERMOSTAT: + return 'thermostat' + elif self == RW61XApp.LAUNDRY_WASHER: + return 'laundry-washer-app' + else: + raise Exception('Unknown app type: %r' % self) + + def NameSuffix(self): + if self == RW61XApp.ALL_CLUSTERS: + return '-'.join(['chip', 'rw61x', 'all-cluster-example']) + if self == RW61XApp.THERMOSTAT: + return '-'.join(['chip', 'rw61x', 'thermostat-example']) + if self == RW61XApp.LAUNDRY_WASHER: + return '-'.join(['chip', 'rw61x', 'laundry-washer-example']) + else: + raise Exception('Unknown app type: %r' % self) + + def BuildRoot(self, root): + return os.path.join(root, 'examples', self.ExampleName(), 'nxp', 'rt/rw61x') + + +class RW61XBuilder(GnBuilder): + + def __init__(self, + root, + runner, + app: RW61XApp = RW61XApp.ALL_CLUSTERS, + release: bool = False, + disable_ble: bool = False, + enable_thread: bool = False, + enable_wifi: bool = False, + disable_ipv4: bool = False, + enable_shell: bool = False, + enable_ota: bool = False, + enable_factory_data: bool = False, + is_sdk_package: bool = True, + a2_board_revision: bool = False): + super(RW61XBuilder, self).__init__( + root=app.BuildRoot(root), + runner=runner) + self.app = app + self.release = release + self.disable_ipv4 = disable_ipv4 + self.disable_ble = disable_ble + self.enable_thread = enable_thread + self.enable_wifi = enable_wifi + self.enable_ota = enable_ota + self.enable_factory_data = enable_factory_data + self.enable_shell = enable_shell + self.is_sdk_package = is_sdk_package + self.a2_board_revision = a2_board_revision + + def GnBuildArgs(self): + args = [] + + if self.release: + args.append('is_debug=false') + + if self.enable_ota: + args.append('chip_enable_ota_requestor=true no_mcuboot=false') + + if self.disable_ipv4: + args.append('chip_inet_config_enable_ipv4=false') + + if self.disable_ble: + args.append('chip_enable_ble=false') + + if self.enable_wifi: + args.append('chip_enable_wifi=true') + + if self.enable_thread: + args.append('chip_enable_openthread=true chip_inet_config_enable_ipv4=false') + + if self.enable_factory_data: + args.append('chip_with_factory_data=1') + + if self.a2_board_revision: + args.append('board_version=\"A2\"') + + if self.enable_shell: + args.append('chip_enable_matter_cli=true') + + if self.is_sdk_package: + args.append('is_sdk_package=true') + + return args + + def generate(self): + super(RW61XBuilder, self).generate() + + def build_outputs(self): + name = '%s' % self.app.NameSuffix() + return { + '%s.elf' % name: os.path.join(self.output_dir, name), + '%s.map' % name: os.path.join(self.output_dir, '%s.map' % name) + } diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt index 3c6a2d3b643d6b..93ad9255c0b9d6 100644 --- a/scripts/build/testdata/all_targets_linux_x64.txt +++ b/scripts/build/testdata/all_targets_linux_x64.txt @@ -14,6 +14,7 @@ linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,therm linux-x64-efr32-test-runner[-clang] imx-{chip-tool,lighting-app,thermostat,all-clusters-app,all-clusters-minimal-app,ota-provider-app}[-release] infineon-psoc6-{lock,light,all-clusters,all-clusters-minimal}[-ota][-updateimage] +rw61x-{all-clusters-app,thermostat,laundry-washer}[-ota][-wifi][-thread][-factory-data][-matter-shell] k32w-{k32w0,k32w1}-{light,shell,lock,contact}[-se05x][-no-ble][-no-ota][-low-power][-nologs][-crypto-platform][-tokenizer][-openthread-ftd] mbed-cy8cproto_062_4343w-{lock,light,all-clusters,all-clusters-minimal,pigweed,ota-requestor,shell}[-release][-develop][-debug] mw320-all-clusters-app diff --git a/scripts/checkout_submodules.py b/scripts/checkout_submodules.py index 6cd87e96177570..aa220f7ff3b139 100755 --- a/scripts/checkout_submodules.py +++ b/scripts/checkout_submodules.py @@ -36,7 +36,8 @@ 'silabs', 'esp32', 'infineon', - 'k32w', + 'nxp', + 'rw61x', 'linux', 'mbed', 'nrfconnect', diff --git a/scripts/tools/nxp/factory_data_generator/custom.py b/scripts/tools/nxp/factory_data_generator/custom.py index e2f7584e2d7fa2..179f990c3052bc 100644 --- a/scripts/tools/nxp/factory_data_generator/custom.py +++ b/scripts/tools/nxp/factory_data_generator/custom.py @@ -278,3 +278,62 @@ def key(self): def max_length(self): return 64 + + +class ProductFinish(StrArgument): + + VALUES = ["Other", "Matte", "Satin", "Polished", "Rugged", "Fabric"] + + def __init__(self, arg): + super().__init__(arg) + + def key(self): + return 22 + + def length(self): + return 1 + + def encode(self): + val = "" + try: + val = ProductFinish.VALUES.index(self.val) + except Exception: + print(f"Error: {self.val} not in {ProductFinish.VALUES}") + exit() + + return val.to_bytes(self.length(), "little") + + def max_length(self): + return 64 + + +class ProductPrimaryColor(StrArgument): + + VALUES = [ + "Black", "Navy", "Green", "Teal", "Maroon", + "Purple", "Olive", "Gray", "Blue", "Lime", + "Aqua", "Red", "Fuchsia", "Yellow", "White", + "Nickel", "Chrome", "Brass", "Copper", "Silver", "Gold" + ] + + def __init__(self, arg): + super().__init__(arg) + + def key(self): + return 23 + + def length(self): + return 1 + + def encode(self): + val = "" + try: + val = ProductPrimaryColor.VALUES.index(self.val) + except Exception: + print(f"Error: {self.val} not in {ProductPrimaryColor.VALUES}") + exit() + + return val.to_bytes(self.length(), "little") + + def max_length(self): + return 64 diff --git a/scripts/tools/nxp/factory_data_generator/generate.py b/scripts/tools/nxp/factory_data_generator/generate.py index 1fb7d91edeedaf..0d905d41faae0b 100755 --- a/scripts/tools/nxp/factory_data_generator/generate.py +++ b/scripts/tools/nxp/factory_data_generator/generate.py @@ -23,17 +23,13 @@ import sys from custom import (CertDeclaration, DacCert, DacPKey, Discriminator, HardwareVersion, HardwareVersionStr, IterationCount, - ManufacturingDate, PaiCert, PartNumber, ProductId, ProductLabel, ProductName, ProductURL, Salt, SerialNum, - SetupPasscode, StrArgument, UniqueId, VendorId, VendorName, Verifier) + ManufacturingDate, PaiCert, PartNumber, ProductFinish, ProductId, ProductLabel, ProductName, + ProductPrimaryColor, ProductURL, Salt, SerialNum, SetupPasscode, StrArgument, UniqueId, VendorId, VendorName, + Verifier) from default import InputArgument -# A magic value used in the factory data integrity check. -# The value will be checked at runtime, before verifying the -# factory data integrity. Factory data header has the following format: -# | hash id (4 bytes) | size (4 bytes) | hash (4 bytes) | -# If the hash id check fails, it means the factory data is either missing -# or has become corrupted. -HASH_ID = "CE47BA5E" +# Global variable for hash ID +hash_id = "CE47BA5E" def set_logger(): @@ -131,7 +127,7 @@ def to_bin(self, klv, out, aes128_key): fullContent = size.to_bytes(4, "little") + fullContent # Add hash id - hashId = bytearray.fromhex("CE47BA5E") + hashId = bytearray.fromhex(hash_id) hashId.reverse() fullContent = hashId + fullContent @@ -163,7 +159,7 @@ def to_bin(self, klv, out, aes128_key): fullContentCipher = size.to_bytes(4, "little") + fullContentCipher # Add hash id - hashId = bytearray.fromhex(HASH_ID) + hashId = bytearray.fromhex(hash_id) hashId.reverse() fullContentCipher = hashId + fullContentCipher @@ -235,6 +231,10 @@ def main(): help="[str] Serial Number") optional.add_argument("--unique_id", type=UniqueId, help="[str] Unique identifier for the device") + optional.add_argument("--product_finish", type=ProductFinish, metavar=ProductFinish.VALUES, + help="[str] Visible finish of the product") + optional.add_argument("--product_primary_color", type=ProductPrimaryColor, metavar=ProductPrimaryColor.VALUES, + help="[str] Representative color of the visible parts of the product") args = parser.parse_args() diff --git a/scripts/tools/nxp/generate_certs.py b/scripts/tools/nxp/generate_certs.py index 7a98f7138ebe05..d6c23a92dc0f18 100644 --- a/scripts/tools/nxp/generate_certs.py +++ b/scripts/tools/nxp/generate_certs.py @@ -20,7 +20,7 @@ import os import subprocess -MATTER_ROOT = os.path.dirname(os.path.realpath(f"{__file__}/../../../")) +MATTER_ROOT = os.path.dirname(os.path.realpath(__file__))[:-len("/scripts/tools/nxp")] def gen_test_certs(chip_cert_exe: str, diff --git a/scripts/tools/nxp/ota/README.md b/scripts/tools/nxp/ota/README.md index 45766b1db8d44f..d96ab70d63c251 100644 --- a/scripts/tools/nxp/ota/README.md +++ b/scripts/tools/nxp/ota/README.md @@ -25,7 +25,7 @@ are also available here: python3 ./scripts/tools/nxp/ota/ota_image_tool.py create -v 0xDEAD -p 0xBEEF -vn 50000 -vs "1.0" -da sha256 ``` -followed by \*_custom options_- and a positional argument (should be last) that +followed by **custom options**- and a positional argument (should be last) that specifies the output file. Please see the `create_ota_images.sh` for some reference commands. diff --git a/scripts/tools/nxp/ota/examples/ota_max_entries_example.json b/scripts/tools/nxp/ota/examples/ota_max_entries_example.json index d5ba8ff8e80964..d28c2def45496c 100644 --- a/scripts/tools/nxp/ota/examples/ota_max_entries_example.json +++ b/scripts/tools/nxp/ota/examples/ota_max_entries_example.json @@ -1,7 +1,7 @@ { "inputs": [ { - "tag": 4, + "tag": 100, "descriptor": [ { "name": "ssbl_version", @@ -22,7 +22,7 @@ "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" }, { - "tag": 5, + "tag": 101, "descriptor": [ { "name": "ssbl_version", @@ -43,7 +43,7 @@ "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" }, { - "tag": 6, + "tag": 102, "descriptor": [ { "name": "ssbl_version", @@ -64,7 +64,7 @@ "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" }, { - "tag": 7, + "tag": 103, "descriptor": [ { "name": "ssbl_version", @@ -85,7 +85,7 @@ "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" }, { - "tag": 8, + "tag": 104, "descriptor": [ { "name": "ssbl_version", @@ -106,7 +106,7 @@ "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" }, { - "tag": 9, + "tag": 105, "descriptor": [ { "name": "ssbl_version", @@ -127,7 +127,7 @@ "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" }, { - "tag": 10, + "tag": 106, "descriptor": [ { "name": "ssbl_version", @@ -148,7 +148,7 @@ "path": "./binaries/ssbl_ext_flash_ota_entry_example.bin" }, { - "tag": 11, + "tag": 107, "descriptor": [ { "name": "ssbl_version", diff --git a/scripts/tools/nxp/ota/ota_image_tool.py b/scripts/tools/nxp/ota/ota_image_tool.py index 64715d784aeecd..592fb71329e7fd 100755 --- a/scripts/tools/nxp/ota/ota_image_tool.py +++ b/scripts/tools/nxp/ota/ota_image_tool.py @@ -24,9 +24,6 @@ The OTA payload can then be used to generate an OTA image file, which will be parsed by the OTA image processor. The total size of TLVs is needed as input for a TLVReader. - -Currently, this script only supports Certification Declaration update, -but it could be modified to support all factory data fields. ''' import argparse @@ -63,6 +60,9 @@ class TAG: APPLICATION = 1 BOOTLOADER = 2 FACTORY_DATA = 3 + # Reserving 99 tags (highly unlikely this number will be reached) for NXP use. + # Custom TLVs should set its tag values to a number greater than LAST_RESERVED. + LAST_RESERVED = 99 def write_to_temp(path: str, payload: bytearray): @@ -211,6 +211,12 @@ def generate_custom_tlvs(data): elif isinstance(field["value"], int): descriptor += bytearray(field["value"].to_bytes(field["length"], "little")) file_size = os.path.getsize(entry["path"]) + + if entry["tag"] <= TAG.LAST_RESERVED: + print( + f"There is a custom TLV with a reserved tag {entry['tag']}. Please ensure all tags are greater than {TAG.LAST_RESERVED}") + sys.exit(1) + payload = generate_header(entry["tag"], len(descriptor) + file_size) + descriptor temp_output = os.path.join(os.path.dirname(__file__), "ota_temp_custom_tlv_" + str(iteration) + ".bin") diff --git a/src/credentials/tests/BUILD.gn b/src/credentials/tests/BUILD.gn index 3b0458233ac757..2de5d99a7bb091 100644 --- a/src/credentials/tests/BUILD.gn +++ b/src/credentials/tests/BUILD.gn @@ -57,7 +57,7 @@ chip_test_suite_using_nltest("tests") { ] # DUTVectors test requires which is not supported on all platforms - if (chip_device_platform != "openiotsdk") { + if (chip_device_platform != "openiotsdk" && chip_device_platform != "nxp") { test_sources += [ "TestCommissionerDUTVectors.cpp" ] } diff --git a/src/lib/shell/BUILD.gn b/src/lib/shell/BUILD.gn index 0bc5ce60874e02..10dad73a268602 100644 --- a/src/lib/shell/BUILD.gn +++ b/src/lib/shell/BUILD.gn @@ -56,11 +56,6 @@ static_library("shell") { "MainLoopSilabs.cpp", "streamer_silabs.cpp", ] - } else if (chip_device_platform == "k32w0") { - sources += [ - "MainLoopDefault.cpp", - "streamer_k32w.cpp", - ] } else if (chip_device_platform == "nxp") { sources += [ "MainLoopDefault.cpp", diff --git a/src/lib/shell/streamer_k32w.cpp b/src/lib/shell/streamer_k32w.cpp deleted file mode 100644 index fadcb82f1913e1..00000000000000 --- a/src/lib/shell/streamer_k32w.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * - * Copyright (c) 2021 Project CHIP Authors - * - * 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. - */ - -/** - * @file - * Source implementation of an input / output stream for k32w targets. - */ - -#include -#include - -#include -#include - -#include "SerialManager.h" -extern uint8_t gShellSerMgrIf; - -namespace chip { -namespace Shell { -namespace { -extern "C" void K32WWriteBlocking(const uint8_t * aBuf, uint32_t len); -extern "C" serialStatus_t Serial_Read(uint8_t InterfaceId, uint8_t * pData, uint16_t dataSize, uint16_t * bytesRead); - -int streamer_k32w_init(streamer_t * streamer) -{ - (void) streamer; - - return 0; -} - -ssize_t streamer_k32w_read(streamer_t * streamer, char * buffer, size_t length) -{ - uint16_t bytesRead = 0; - - (void) streamer; - Serial_Read(gShellSerMgrIf, (uint8_t *) buffer, length, &bytesRead); - - return bytesRead; -} - -ssize_t streamer_k32w_write(streamer_t * streamer, const char * buffer, size_t length) -{ - (void) streamer; - K32WWriteBlocking((uint8_t *) buffer, length); - - return length; -} - -static streamer_t streamer_k32w = { - .init_cb = streamer_k32w_init, - .read_cb = streamer_k32w_read, - .write_cb = streamer_k32w_write, -}; -} // namespace - -streamer_t * streamer_get(void) -{ - return &streamer_k32w; -} - -} // namespace Shell -} // namespace chip diff --git a/src/lib/shell/streamer_nxp.cpp b/src/lib/shell/streamer_nxp.cpp index aed4796902942a..e46fce84072529 100644 --- a/src/lib/shell/streamer_nxp.cpp +++ b/src/lib/shell/streamer_nxp.cpp @@ -25,6 +25,7 @@ /* Includes */ /* -------------------------------------------------------------------------- */ +#include #include #include @@ -97,10 +98,10 @@ static void Uart_TxCallBack(void * pBuffer, serial_manager_callback_message_t * static SERIAL_MANAGER_HANDLE_DEFINE(streamerSerialHandle); static SERIAL_MANAGER_WRITE_HANDLE_DEFINE(streamerSerialWriteHandle); static SERIAL_MANAGER_READ_HANDLE_DEFINE(streamerSerialReadHandle); -static volatile int txCount = 0; -static bool readDone = true; +static volatile int txCount = 0; +volatile static bool readDone = true; -static streamer_serial_port_uart_config_t uartConfig = { .clockRate = BOARD_APP_UART_CLK_FREQ, +static streamer_serial_port_uart_config_t uartConfig = { .clockRate = 0, .baudRate = BOARD_DEBUG_UART_BAUDRATE, .parityMode = kSerialManager_UartParityDisabled, .stopBitCount = kSerialManager_UartOneStopBit, @@ -129,6 +130,8 @@ static const serial_manager_config_t s_serialManagerConfig = { .portConfig = (serial_port_uart_config_t *) &uartConfig, }; +OSA_MUTEX_HANDLE_DEFINE(streamerMutex); + /* -------------------------------------------------------------------------- */ /* Public functions */ /* -------------------------------------------------------------------------- */ @@ -146,6 +149,8 @@ int streamer_nxp_init(streamer_t * streamer) BOARD_CLIAttachClk(); #endif + uartConfig.clockRate = BOARD_APP_UART_CLK_FREQ; + #if STREAMER_UART_USE_DMA dma_channel_mux_configure_t dma_channel_mux; dma_channel_mux.dma_dmamux_configure.dma_tx_channel_mux = kDmaRequestLPUART1Tx; @@ -180,6 +185,9 @@ int streamer_nxp_init(streamer_t * streamer) OSA_InterruptEnable(); + osa_status_t status_osa = OSA_MutexCreate((osa_mutex_handle_t) streamerMutex); + assert(status_osa == KOSA_StatusSuccess); + return status; } @@ -204,10 +212,13 @@ ssize_t streamer_nxp_read(streamer_t * streamer, char * buffer, size_t length) assert(status != kStatus_SerialManager_Error); /** - * If we are at the end of the line or the buffer is empty, - * consider the reading process done + * In certain cases such as a copy-paste of multiple commands, we may encounter '\n' or '\r' caracters + * although the buffer is not empty yet, so the reading process should be considered done only when the + * bytesRead return null, + * this is to ensure that all commands are processed before blocking the CLI task + * **/ - if ((buffer[length - 1] == '\n') || (buffer[length - 1] == '\r') || (bytesRead == 0)) + if (bytesRead == 0) { readDone = true; } @@ -219,25 +230,36 @@ ssize_t streamer_nxp_read(streamer_t * streamer, char * buffer, size_t length) ssize_t streamer_nxp_write(streamer_t * streamer, const char * buffer, size_t length) { uint32_t intMask; - serial_manager_status_t status = kStatus_SerialManager_Error; + serial_manager_status_t status = kStatus_SerialManager_Success; size_t len = 0; - intMask = DisableGlobalIRQ(); - txCount++; - status = SerialManager_WriteNonBlocking((serial_write_handle_t) streamerSerialWriteHandle, - (uint8_t *) (const_cast(buffer)), (uint32_t) length); - EnableGlobalIRQ(intMask); - if (status == kStatus_SerialManager_Success) - { - len = length; - } + /* Mutex lock to ensure the streamer write is accessed by only one task at a time */ + osa_status_t status_osa = OSA_MutexLock(streamerMutex, osaWaitForever_c); - /* Wait for the serial manager task to empty the TX buffer */ - while (txCount) + // If length is 0 there will be an assert in Serial Manager. Some OT functions output 0 bytes, for example + // in SrpServer::Process -> OutputLine(hasSubType ? "" : "(null)"); + if (length > 0) { - OSA_TimeDelay(STREAMER_UART_FLUSH_DELAY_MS); + intMask = DisableGlobalIRQ(); + txCount++; + status = SerialManager_WriteNonBlocking((serial_write_handle_t) streamerSerialWriteHandle, (uint8_t *) buffer, + (uint32_t) length); + EnableGlobalIRQ(intMask); + if (status == kStatus_SerialManager_Success) + { + len = length; + } + + /* Wait for the serial manager task to empty the TX buffer */ + while (txCount) + { + OSA_TimeDelay(STREAMER_UART_FLUSH_DELAY_MS); + } } + status_osa = OSA_MutexUnlock(streamerMutex); + assert(status_osa == KOSA_StatusSuccess); + return len; } diff --git a/src/lwip/BUILD.gn b/src/lwip/BUILD.gn index 98e94def25fb84..6d7e58d8a59fff 100644 --- a/src/lwip/BUILD.gn +++ b/src/lwip/BUILD.gn @@ -30,12 +30,11 @@ if (lwip_platform == "") { assert(lwip_platform == "external" || lwip_platform == "standalone" || lwip_platform == "cc13xx_26xx" || lwip_platform == "cc32xx" || lwip_platform == "nxp" || lwip_platform == "silabs" || - lwip_platform == "k32w0" || lwip_platform == "qpg" || - lwip_platform == "mbed" || lwip_platform == "psoc6" || - lwip_platform == "cyw30739" || lwip_platform == "bl602" || - lwip_platform == "mw320" || lwip_platform == "bl702" || - lwip_platform == "bl702l" || lwip_platform == "mt793x" || - lwip_platform == "asr", + lwip_platform == "qpg" || lwip_platform == "mbed" || + lwip_platform == "psoc6" || lwip_platform == "cyw30739" || + lwip_platform == "bl602" || lwip_platform == "mw320" || + lwip_platform == "bl702" || lwip_platform == "bl702l" || + lwip_platform == "mt793x" || lwip_platform == "asr", "Unsupported lwIP platform: ${lwip_platform}") if (lwip_platform != "external") { @@ -54,8 +53,6 @@ if (lwip_platform == "cc13xx_26xx") { } else if (lwip_platform == "qpg") { import("//build_overrides/qpg_sdk.gni") import("${qpg_sdk_build_root}/qpg_sdk.gni") -} else if (lwip_platform == "k32w0") { - import("//build_overrides/k32w0_sdk.gni") } else if (lwip_platform == "psoc6") { import("//build_overrides/psoc6.gni") } else if (lwip_platform == "cyw30739") { @@ -230,8 +227,6 @@ if (current_os == "zephyr" || current_os == "mbed") { ] } else if (lwip_platform == "standalone") { public_deps += [ "${chip_root}/src/lib/support" ] - } else if (lwip_platform == "k32w0") { - public_deps += [ "${k32w0_sdk_build_root}:k32w0_sdk" ] } else if (lwip_platform == "cyw30739") { public_deps += [ "${cyw30739_sdk_build_root}:cyw30739_sdk" ] } else if (lwip_platform == "bl702") { diff --git a/src/lwip/k32w1/arch/cc.h b/src/lwip/k32w1/arch/cc.h new file mode 100644 index 00000000000000..0dd3a291498a93 --- /dev/null +++ b/src/lwip/k32w1/arch/cc.h @@ -0,0 +1,90 @@ +/* + * + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * This document is the property of Nest. It is considered + * confidential and proprietary information. + * + * This document may not be reproduced or transmitted in any form, + * in whole or in part, without the express written permission of + * Nest. + * + * Description: + * This file defines processor-architecture- and toolchain- + * specific constants and types required for building + * LwIP against FreeRTOS. + * + */ + +#ifndef NL_LWIP_FREERTOS_ARCH_CC_H +#define NL_LWIP_FREERTOS_ARCH_CC_H + +#include +#include +#include +#include +#include +#include +#include + +#if CHIP_CONFIG_MEMORY_MGMT_MALLOC +#include +#endif + +#if __cplusplus +extern "C" { +#endif + +#ifndef LWIP_NOASSERT +#ifdef DEBUG +#define LWIP_PLATFORM_ASSERT(MSG) assert(MSG); +#else +#define LWIP_PLATFORM_ASSERT(MSG) +#endif +#else +#define LWIP_PLATFORM_ASSERT(message) +#endif + +#ifndef BYTE_ORDER +#if defined(__LITTLE_ENDIAN__) +#define BYTE_ORDER LITTLE_ENDIAN +#elif defined(__BIG_ENDIAN__) +#define BYTE_ORDER BIG_ENDIAN +#elif defined(__BYTE_ORDER__) +#define BYTE_ORDER __BYTE_ORDER__ +#endif +#endif // BYTE_ORDER + +#define PACK_STRUCT_STRUCT __attribute__((__packed__)) +#define PACK_STRUCT_FIELD(x) x + +extern void LwIPLog(const char * fmt, ...); +#define LWIP_PLATFORM_DIAG(x) \ + do \ + { \ + LwIPLog x; \ + } while (0) + +// Place LwIP pools into their own subsections of .bss to make it easier to see +// their sizes in the linker map file. +extern uint8_t __attribute__((section(".bss.lwip_ND6_QUEUE"))) memp_memory_ND6_QUEUE_base[]; +extern uint8_t __attribute__((section(".bss.lwip_IP6_REASSDATA"))) memp_memory_IP6_REASSDATA_base[]; +extern uint8_t __attribute__((section(".bss.lwip_RAW_PCB"))) memp_memory_RAW_PCB_base[]; +extern uint8_t __attribute__((section(".bss.lwip_TCP_SEG"))) memp_memory_TCP_SEG_base[]; +extern uint8_t __attribute__((section(".bss.lwip_PBUF_POOL"))) memp_memory_PBUF_POOL_base[]; +extern uint8_t __attribute__((section(".bss.lwip_FRAG_PBUF"))) memp_memory_FRAG_PBUF_base[]; +extern uint8_t __attribute__((section(".bss.lwip_PBUF"))) memp_memory_PBUF_base[]; +extern uint8_t __attribute__((section(".bss.lwip_TCP_PCB_LISTEN"))) memp_memory_TCP_PCB_LISTEN_base[]; +extern uint8_t __attribute__((section(".bss.lwip_REASSDATA"))) memp_memory_REASSDATA_base[]; +extern uint8_t __attribute__((section(".bss.lwip_UDP_PCB"))) memp_memory_UDP_PCB_base[]; +extern uint8_t __attribute__((section(".bss.lwip_MLD6_GROUP"))) memp_memory_MLD6_GROUP_base[]; +extern uint8_t __attribute__((section(".bss.lwip_IGMP_GROUP"))) memp_memory_IGMP_GROUP_base[]; +extern uint8_t __attribute__((section(".bss.lwip_TCP_PCB"))) memp_memory_TCP_PCB_base[]; +extern uint8_t __attribute__((section(".bss.lwip_SYS_TIMEOUT"))) memp_memory_SYS_TIMEOUT_base[]; + +#if __cplusplus +} +#endif + +#endif /* NL_LWIP_FREERTOS_ARCH_CC_H */ diff --git a/src/lwip/k32w1/arch/perf.h b/src/lwip/k32w1/arch/perf.h new file mode 100644 index 00000000000000..bbef2139053135 --- /dev/null +++ b/src/lwip/k32w1/arch/perf.h @@ -0,0 +1,26 @@ +/* + * + * Copyright (c) 2020 Nest Labs, Inc. + * All rights reserved. + * + * This document is the property of Nest. It is considered + * confidential and proprietary information. + * + * This document may not be reproduced or transmitted in any form, + * in whole or in part, without the express written permission of + * Nest. + * + * Description: + * This file defines processor-architecture-specific constants, + * interfaces and types required for LwIP performance + * measurement. + * + */ + +#ifndef NL_LWIP_FREERTOS_ARCH_PERF_H +#define NL_LWIP_FREERTOS_ARCH_PERF_H + +#define PERF_START +#define PERF_STOP(s) + +#endif /* NL_LWIP_FREERTOS_ARCH_PERF_H */ diff --git a/src/lwip/k32w1/lwipopts.h b/src/lwip/k32w1/lwipopts.h new file mode 100644 index 00000000000000..1ecec9f14f0a70 --- /dev/null +++ b/src/lwip/k32w1/lwipopts.h @@ -0,0 +1,172 @@ +/* + * + * Copyright (c) 2020 Nest Labs, Inc. + * 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. + */ + +/** + * @file + * Compile-time configuration for LwIP on K32W platforms using the + * NXP K32W SDK. + * + */ + +#ifndef __LWIPOPTS_H__ +#define __LWIPOPTS_H__ + +#if CHIP_HAVE_CONFIG_H +#include +#endif + +#include + +#define NO_SYS 0 +#define MEM_ALIGNMENT (4) +#define MEMP_NUM_TCP_SEG (TCP_SND_QUEUELEN + 1) +#define LWIP_TIMEVAL_PRIVATE (0) +#define MEM_LIBC_MALLOC (0) +#define LWIP_COMPAT_MUTEX (0) +#define SYS_LIGHTWEIGHT_PROT (1) +#define LWIP_AUTOIP (0) +#define LWIP_DHCP_AUTOIP_COOP (0) +#define LWIP_SOCKET_SET_ERRNO 0 +#define IP_REASS_MAX_PBUFS 0 +#define IP_REASSEMBLY 0 +#define MEMP_NUM_REASSDATA 0 +#define LWIP_SO_RCVTIMEO 0 +#define SO_REUSE (1) +#define LWIP_RANDOMIZE_INITIAL_LOCAL_PORTS (1) +#define LWIP_STATS (0) +#define LWIP_TCPIP_CORE_LOCKING 1 +#define TCP_QUEUE_OOSEQ 0 +#define ARP_QUEUEING (0) + +#define LWIP_SOCKET 0 + +#define LWIP_RAW 0 +#define MEMP_NUM_RAW_PCB 0 +#if INET_CONFIG_ENABLE_TCP_ENDPOINT +#define LWIP_TCP 1 +#else +#define LWIP_TCP 0 +#define MEMP_NUM_TCP_PCB 0 +#endif // INET_CONFIG_ENABLE_TCP_ENDPOINT + +// TODO: verify count +#define MEMP_NUM_UDP_PCB (7) + +#define LWIP_HAVE_LOOPIF (0) + +// TODO: not sure why this is disabled +#define LWIP_NETIF_LOOPBACK (0) + +#define MEMP_NUM_NETCONN (0) + +#define LWIP_IPV4 0 +#define LWIP_IPV6 1 +#define LWIP_ARP (0) +#define LWIP_DNS (0) +#define LWIP_ICMP (0) +#define LWIP_IGMP (0) +#define LWIP_DHCP (0) +#define LWIP_IPV6_REASS (0) +#define LWIP_IPV6_DHCP6 0 +#define LWIP_IPV6_AUTOCONFIG (0) +#define LWIP_IPV6_ROUTER_SUPPORT 0 +#define LWIP_ND6_LISTEN_RA 0 + +#define LWIP_ND6_NUM_NEIGHBORS (0) +#define LWIP_ND6_NUM_DESTINATIONS (0) +#define LWIP_ND6_NUM_PREFIXES (0) +#define LWIP_ND6_NUM_ROUTERS (0) +#define LWIP_ND6_MAX_MULTICAST_SOLICIT (0) +#define LWIP_ND6_MAX_UNICAST_SOLICIT (0) +#define LWIP_ND6_MAX_NEIGHBOR_ADVERTISEMENT (0) +#define LWIP_ND6_TCP_REACHABILITY_HINTS (0) + +#define MEMP_SEPARATE_POOLS (1) +#define LWIP_PBUF_FROM_CUSTOM_POOLS (0) +#define MEMP_USE_CUSTOM_POOLS (0) +#define PBUF_POOL_SIZE (6) +#define PBUF_POOL_BUFSIZE (1280) +#define PBUF_CUSTOM_POOL_IDX_START (MEMP_PBUF_POOL_SMALL) +#define PBUF_CUSTOM_POOL_IDX_END (MEMP_PBUF_POOL_LARGE) + +#define TCP_MSS (1152) +#define TCP_SND_BUF (2 * TCP_MSS) +#define TCP_LISTEN_BACKLOG (1) + +#define ETH_PAD_SIZE (0) +#define SUB_ETHERNET_HEADER_SPACE (0) +#define PBUF_LINK_HLEN (0) + +#define TCPIP_THREAD_STACKSIZE (2048) +#define TCPIP_THREAD_PRIO (3) + +#define NETIF_MAX_HWADDR_LEN 8U + +#define LWIP_IPV6_NUM_ADDRESSES 5 + +#define LWIP_IPV6_ND 0 +#define LWIP_ND6_QUEUEING 0 + +#define LWIP_MULTICAST_PING 0 + +#define TCPIP_MBOX_SIZE 6 +#define DEFAULT_RAW_RECVMBOX_SIZE 6 +#define DEFAULT_UDP_RECVMBOX_SIZE 6 +#define DEFAULT_TCP_RECVMBOX_SIZE 6 + +// TODO: make LWIP_DEBUG conditional on build type + +#ifndef LWIP_DEBUG +#define LWIP_DEBUG 0 +#endif + +#define MEMP_OVERFLOW_CHECK (0) +#define MEMP_SANITY_CHECK (0) +#define MEM_DEBUG (LWIP_DBG_OFF) +#define MEMP_DEBUG (LWIP_DBG_OFF) +#define PBUF_DEBUG (LWIP_DBG_OFF) +#define API_LIB_DEBUG (LWIP_DBG_OFF) +#define API_MSG_DEBUG (LWIP_DBG_OFF) +#define TCPIP_DEBUG (LWIP_DBG_OFF) +#define NETIF_DEBUG (LWIP_DBG_OFF) +#define SOCKETS_DEBUG (LWIP_DBG_OFF) +#define DEMO_DEBUG (LWIP_DBG_OFF) +#define DHCP_DEBUG (LWIP_DBG_OFF) +#define AUTOIP_DEBUG (LWIP_DBG_OFF) +#define ETHARP_DEBUG (LWIP_DBG_OFF) +#define IP_DEBUG (LWIP_DBG_OFF) +#define IP_REASS_DEBUG (LWIP_DBG_OFF) +#define IP6_DEBUG (LWIP_DBG_OFF) +#define RAW_DEBUG (LWIP_DBG_OFF) +#define ICMP_DEBUG (LWIP_DBG_OFF) +#define UDP_DEBUG (LWIP_DBG_OFF) +#define TCP_DEBUG (LWIP_DBG_OFF) +#define TCP_INPUT_DEBUG (LWIP_DBG_OFF) +#define TCP_OUTPUT_DEBUG (LWIP_DBG_OFF) +#define TCP_RTO_DEBUG (LWIP_DBG_OFF) +#define TCP_CWND_DEBUG (LWIP_DBG_OFF) +#define TCP_WND_DEBUG (LWIP_DBG_OFF) +#define TCP_FR_DEBUG (LWIP_DBG_OFF) +#define TCP_QLEN_DEBUG (LWIP_DBG_OFF) +#define TCP_RST_DEBUG (LWIP_DBG_OFF) +#define PPP_DEBUG (LWIP_DBG_OFF) + +#define LWIP_DBG_TYPES_ON \ + (LWIP_DBG_ON | LWIP_DBG_TRACE) /* (LWIP_DBG_ON|LWIP_DBG_TRACE|LWIP_DBG_STATE|LWIP_DBG_FRESH|LWIP_DBG_HALT) */ + +#endif /* __LWIPOPTS_H__ */ diff --git a/src/lwip/k32w1/lwippools.h b/src/lwip/k32w1/lwippools.h new file mode 100644 index 00000000000000..8b137891791fe9 --- /dev/null +++ b/src/lwip/k32w1/lwippools.h @@ -0,0 +1 @@ + diff --git a/src/messaging/tests/BUILD.gn b/src/messaging/tests/BUILD.gn index b6f1dcb02c5ce7..699066ff0b7402 100644 --- a/src/messaging/tests/BUILD.gn +++ b/src/messaging/tests/BUILD.gn @@ -55,7 +55,7 @@ chip_test_suite_using_nltest("tests") { ] if (chip_device_platform != "esp32" && chip_device_platform != "mbed" && - chip_device_platform != "nrfconnect") { + chip_device_platform != "nrfconnect" && chip_device_platform != "nxp") { test_sources += [ "TestExchangeHolder.cpp" ] } diff --git a/src/platform/BUILD.gn b/src/platform/BUILD.gn index 4a0760334fb8cb..654875cde51b49 100644 --- a/src/platform/BUILD.gn +++ b/src/platform/BUILD.gn @@ -141,9 +141,8 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { if (chip_device_platform == "linux" || chip_device_platform == "darwin" || chip_device_platform == "tizen" || chip_device_platform == "android" || - chip_device_platform == "k32w0" || chip_device_platform == "webos" || - chip_device_platform == "bl602" || chip_device_platform == "bl702" || - chip_device_platform == "bl702l") { + chip_device_platform == "webos" || chip_device_platform == "bl602" || + chip_device_platform == "bl702" || chip_device_platform == "bl702l") { defines += [ "CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE=${chip_enable_ble}" ] } @@ -217,9 +216,6 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { } else if (chip_device_platform == "qpg") { device_layer_target_define = "QPG" defines += [ "CHIP_DEVICE_LAYER_TARGET=qpg" ] - } else if (chip_device_platform == "k32w0") { - device_layer_target_define = "K32W" - defines += [ "CHIP_DEVICE_LAYER_TARGET=nxp/k32w/k32w0" ] } else if (chip_device_platform == "nxp") { import("//build_overrides/nxp_sdk.gni") import("${nxp_sdk_build_root}/nxp_sdk.gni") @@ -335,7 +331,6 @@ if (chip_device_platform != "none" && chip_device_platform != "external") { "TIZEN", "NRFCONNECT", "QPG", - "K32W", "NXP", "NXP_ZEPHYR", "TELINK", @@ -538,8 +533,6 @@ if (chip_device_platform != "none") { _platform_target = "silabs/SiWx917" } else if (chip_device_platform == "esp32") { _platform_target = "ESP32" - } else if (chip_device_platform == "k32w0") { - _platform_target = "nxp/k32w/k32w0" } else if (chip_device_platform == "linux") { _platform_target = "Linux" } else if (chip_device_platform == "nrfconnect") { diff --git a/src/platform/device.gni b/src/platform/device.gni index 933b730186ec85..5dc4c812c0c648 100644 --- a/src/platform/device.gni +++ b/src/platform/device.gni @@ -16,7 +16,7 @@ import("//build_overrides/chip.gni") import("${chip_root}/src/ble/ble.gni") declare_args() { - # Device platform layer: cc13x2_26x2, cc13x4_26x4, cc32xx, darwin, efr32, esp32, external, freertos, linux, nrfconnect, k32w0, nxp, qpg, tizen, cyw30739, bl602, mw320, zephyr, beken, openiotsdk, none. + # Device platform layer: cc13x2_26x2, cc13x4_26x4, cc32xx, darwin, efr32, esp32, external, freertos, linux, nrfconnect, nxp, qpg, tizen, cyw30739, bl602, mw320, zephyr, beken, openiotsdk, none. chip_device_platform = "auto" chip_platform_target = "" @@ -51,8 +51,8 @@ declare_args() { chip_device_platform == "linux" || chip_device_platform == "qpg" || chip_device_platform == "cc13x2_26x2" || chip_device_platform == "cc13x4_26x4" || - chip_device_platform == "k32w0" || chip_device_platform == "tizen" || - chip_device_platform == "stm32" || chip_device_platform == "webos" + chip_device_platform == "tizen" || chip_device_platform == "stm32" || + chip_device_platform == "webos" } declare_args() { @@ -149,8 +149,6 @@ if (chip_device_platform == "cc13x2_26x2") { _chip_device_layer = "nrfconnect" } else if (chip_device_platform == "qpg") { _chip_device_layer = "qpg" -} else if (chip_device_platform == "k32w0") { - _chip_device_layer = "nxp/k32w/k32w0" } else if (chip_device_platform == "nxp") { import("//build_overrides/nxp_sdk.gni") import("${nxp_sdk_build_root}/nxp_sdk.gni") @@ -252,7 +250,6 @@ assert( chip_device_platform == "external" || chip_device_platform == "linux" || chip_device_platform == "tizen" || chip_device_platform == "nrfconnect" || chip_device_platform == "nxp" || - chip_device_platform == "k32w0" || chip_device_platform == "nxp_zephyr" || chip_device_platform == "qpg" || chip_device_platform == "telink" || chip_device_platform == "mbed" || chip_device_platform == "psoc6" || chip_device_platform == "android" || diff --git a/src/platform/nxp/common/CHIPNXPPlatformDefaultConfig.h b/src/platform/nxp/common/CHIPNXPPlatformDefaultConfig.h index 6070fbc84dcdb7..9282ffb5fe868e 100644 --- a/src/platform/nxp/common/CHIPNXPPlatformDefaultConfig.h +++ b/src/platform/nxp/common/CHIPNXPPlatformDefaultConfig.h @@ -66,7 +66,7 @@ #endif // CHIP_CONFIG_ERROR_CLASS #ifndef CHIP_CONFIG_SHA256_CONTEXT_SIZE -#define CHIP_CONFIG_SHA256_CONTEXT_SIZE (sizeof(unsigned int) * 76) +#define CHIP_CONFIG_SHA256_CONTEXT_SIZE (sizeof(unsigned int) * 98) #endif // CHIP_CONFIG_SHA256_CONTEXT_SIZE // ==================== Security Adaptations ==================== diff --git a/src/platform/nxp/common/ConnectivityManagerImpl.cpp b/src/platform/nxp/common/ConnectivityManagerImpl.cpp index 36b2ae978d65c8..91311b3f53a8ca 100644 --- a/src/platform/nxp/common/ConnectivityManagerImpl.cpp +++ b/src/platform/nxp/common/ConnectivityManagerImpl.cpp @@ -2,7 +2,7 @@ * * Copyright (c) 2020-2022 Project CHIP Authors * Copyright (c) 2020 Nest Labs, Inc. - * Copyright 2023 NXP + * Copyright 2023-2024 NXP * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,13 +51,18 @@ extern "C" { #include -#include +#if CHIP_DEVICE_CONFIG_ENABLE_THREAD + +#include + +#include "br_rtos_manager.h" +#endif /* CHIP_DEVICE_CONFIG_ENABLE_THREAD */ #endif /* CHIP_DEVICE_CONFIG_ENABLE_WPA */ #if CHIP_DEVICE_CONFIG_ENABLE_THREAD #include -#endif +#endif /* CHIP_DEVICE_CONFIG_ENABLE_THREAD */ using namespace ::chip; using namespace ::chip::Inet; @@ -87,6 +92,10 @@ CHIP_ERROR ConnectivityManagerImpl::_Init() GenericConnectivityManagerImpl_Thread::_Init(); #endif +#if CHIP_DEVICE_CONFIG_ENABLE_WPA + StartWiFiManagement(); +#endif + SuccessOrExit(err); exit: @@ -110,11 +119,34 @@ void ConnectivityManagerImpl::_OnPlatformEvent(const ChipDeviceEvent * event) } else if (event->Type == kPlatformNxpStartWlanConnectEvent) { - if (wlan_add_network(event->Platform.pNetworkDataEvent) == WM_SUCCESS) + bool is_wlan_added = false; + struct wlan_network searchedNetwork = { 0 }; + + /* If network was added before on a previous connection call or other API, do not add it again */ + if (wlan_get_network_byname(event->Platform.pNetworkDataEvent->name, &searchedNetwork) != WM_SUCCESS) + { + if (wlan_add_network(event->Platform.pNetworkDataEvent) == WM_SUCCESS) + { + ChipLogProgress(DeviceLayer, "Added WLAN \"%s\"", event->Platform.pNetworkDataEvent->name); + is_wlan_added = true; + } + } + else + { + is_wlan_added = false; + } + + /* At this point, the network details should be registered in the wlan driver */ + if (is_wlan_added == true) { _SetWiFiStationState(kWiFiStationState_Connecting); ChipLogProgress(DeviceLayer, "WLAN connecting to network.name = \"%s\"", event->Platform.pNetworkDataEvent->name); +#if WIFI_DFS_OPTIMIZATION + /* Skip DFS (Dynamic Frequency Selection) channels during scan, DFS is used to avoid interferences */ + wlan_connect_opt(event->Platform.pNetworkDataEvent->name, true); +#else wlan_connect(event->Platform.pNetworkDataEvent->name); +#endif } if (event->Platform.pNetworkDataEvent != NULL) { @@ -314,6 +346,9 @@ void ConnectivityManagerImpl::UpdateInternetConnectivityState() const ip6_addr_t * addr6; CHIP_ERROR err; ChipDeviceEvent event; +#if CHIP_ENABLE_OPENTHREAD + otIp6Address newIpAddress; +#endif // If the WiFi station is currently in the connected state... if (_IsWiFiStationConnected()) @@ -350,6 +385,11 @@ void ConnectivityManagerImpl::UpdateInternetConnectivityState() { haveIPv6Conn = true; addr6 = netif_ip6_addr(netif, i); +#if CHIP_ENABLE_OPENTHREAD + // We are using ot mDNS sever and need to add IP address to server list + memcpy(&newIpAddress.mFields.m32, addr6->addr, sizeof(Inet::IPAddress)); + otMdnsServerAddAddress(ThreadStackMgrImpl().OTInstance(), &newIpAddress); +#endif break; } } @@ -369,8 +409,6 @@ void ConnectivityManagerImpl::UpdateInternetConnectivityState() if (haveIPv4Conn) { event.InternetConnectivityChange.ipAddress = IPAddress(*addr4); - /* (Re-)start the DNSSD server */ - chip::app::DnssdServer::Instance().StartServer(); } err = PlatformMgr().PostEvent(&event); VerifyOrDie(err == CHIP_NO_ERROR); @@ -386,8 +424,11 @@ void ConnectivityManagerImpl::UpdateInternetConnectivityState() if (haveIPv6Conn) { event.InternetConnectivityChange.ipAddress = IPAddress(*addr6); - /* (Re-)start the DNSSD server */ - chip::app::DnssdServer::Instance().StartServer(); + +#if CHIP_ENABLE_OPENTHREAD + // Start the Border Router services including MDNS Server + StartBrServices(); +#endif } err = PlatformMgr().PostEvent(&event); VerifyOrDie(err == CHIP_NO_ERROR); @@ -435,6 +476,48 @@ void ConnectivityManagerImpl::StartWiFiManagement() chipDie(); } } +#if CHIP_ENABLE_OPENTHREAD +void ConnectivityManagerImpl::StartBrServices() +{ + if (mBorderRouterInit == false) + { + struct netif * extNetIfPtr = static_cast(net_get_mlan_handle()); + struct netif * thrNetIfPtr = ThreadStackMgrImpl().ThreadNetIf(); + otInstance * thrInstancePtr; + + // Initalize internal interface variables, these can be used by other modules like the DNSSD Impl to + // get the underlying IP interface + Inet::InterfaceId tmpExtIf(extNetIfPtr); + Inet::InterfaceId tmpThrIf(thrNetIfPtr); + mExternalNetIf = tmpExtIf; + mThreadNetIf = tmpThrIf; + + // Need to wait for the wifi to be connected because the mlan netif can be !=null but not initialized + // properly. If the thread netif is !=null it means that it was fully initialized + + // Lock OT task + if ((thrNetIfPtr) && (mWiFiStationState == kWiFiStationState_Connected)) + { + mBorderRouterInit = true; + // Check if OT instance is init + thrInstancePtr = ThreadStackMgrImpl().OTInstance(); + + BrInitServices(thrInstancePtr, extNetIfPtr, thrNetIfPtr); + otMdnsServerStart(thrInstancePtr); + } + } +} + +Inet::InterfaceId ConnectivityManagerImpl::GetThreadInterface() +{ + return sInstance.mThreadNetIf; +} + +Inet::InterfaceId ConnectivityManagerImpl::GetExternalInterface() +{ + return sInstance.mExternalNetIf; +} +#endif // CHIP_ENABLE_OPENTHREAD #endif // CHIP_DEVICE_CONFIG_ENABLE_WPA CHIP_ERROR ConnectivityManagerImpl::ProvisionWiFiNetwork(const char * ssid, uint8_t ssidLen, const char * key, uint8_t keyLen) diff --git a/src/platform/nxp/common/ConnectivityManagerImpl.h b/src/platform/nxp/common/ConnectivityManagerImpl.h index 427cbbe8e61cc3..806826daf0f7d3 100644 --- a/src/platform/nxp/common/ConnectivityManagerImpl.h +++ b/src/platform/nxp/common/ConnectivityManagerImpl.h @@ -87,6 +87,11 @@ class ConnectivityManagerImpl final : public ConnectivityManager, #if CHIP_DEVICE_CONFIG_ENABLE_WPA void StartWiFiManagement(); +#if CHIP_ENABLE_OPENTHREAD + Inet::InterfaceId GetExternalInterface(); + Inet::InterfaceId GetThreadInterface(); +#endif + #endif private: @@ -124,6 +129,7 @@ class ConnectivityManagerImpl final : public ConnectivityManager, ConnectivityManager::WiFiStationState mWiFiStationState; ConnectivityManager::WiFiAPMode mWiFiAPMode; uint32_t mWiFiStationReconnectIntervalMS; + bool mBorderRouterInit = false; #if CHIP_DEVICE_CONFIG_ENABLE_WPA enum WiFiEventGroup{ @@ -133,12 +139,20 @@ class ConnectivityManagerImpl final : public ConnectivityManager, BitFlags mFlags; static netif_ext_callback_t sNetifCallback; +#if CHIP_ENABLE_OPENTHREAD + Inet::InterfaceId mThreadNetIf; + Inet::InterfaceId mExternalNetIf; +#endif + static int _WlanEventCallback(enum wlan_event_reason event, void * data); static void _NetifExtCallback(struct netif * netif, netif_nsc_reason_t reason, const netif_ext_callback_args_t * args); void OnStationConnected(void); void OnStationDisconnected(void); void UpdateInternetConnectivityState(void); +#if CHIP_ENABLE_OPENTHREAD + void StartBrServices(void); +#endif /* CHIP_DEVICE_CONFIG_ENABLE_THREAD */ #endif /* CHIP_DEVICE_CONFIG_ENABLE_WPA */ }; @@ -177,7 +191,7 @@ inline ConnectivityManager & ConnectivityMgr(void) * Returns the platform-specific implementation of the ConnectivityManager singleton object. * * Chip applications can use this to gain access to features of the ConnectivityManager - * that are specific to the ESP32 platform. + * that are specific to the NXP platform. */ inline ConnectivityManagerImpl & ConnectivityMgrImpl(void) { diff --git a/src/platform/nxp/common/DiagnosticDataProviderImpl.cpp b/src/platform/nxp/common/DiagnosticDataProviderImpl.cpp index 10e9f8d528b12f..0dd9b52f6a0aba 100644 --- a/src/platform/nxp/common/DiagnosticDataProviderImpl.cpp +++ b/src/platform/nxp/common/DiagnosticDataProviderImpl.cpp @@ -118,7 +118,7 @@ CHIP_ERROR DiagnosticDataProviderImpl::GetNetworkInterfaces(NetworkInterface ** ifp->isOperational = true; ifp->offPremiseServicesReachableIPv4.SetNull(); ifp->offPremiseServicesReachableIPv6.SetNull(); - ifp->type = EMBER_ZCL_INTERFACE_TYPE_ENUM_THREAD; + ifp->type = app::Clusters::GeneralDiagnostics::InterfaceTypeEnum::kThread; ConfigurationMgr().GetPrimary802154MACAddress(ifp->MacAddress); ifp->hardwareAddress = ByteSpan(ifp->MacAddress, kMaxHardwareAddrSize); #elif CHIP_DEVICE_CONFIG_ENABLE_WPA @@ -129,7 +129,7 @@ CHIP_ERROR DiagnosticDataProviderImpl::GetNetworkInterfaces(NetworkInterface ** ifp->isOperational = true; ifp->offPremiseServicesReachableIPv4.SetNull(); ifp->offPremiseServicesReachableIPv6.SetNull(); - ifp->type = EMBER_ZCL_INTERFACE_TYPE_ENUM_WI_FI; + ifp->type = app::Clusters::GeneralDiagnostics::InterfaceTypeEnum::kWiFi; ifp->hardwareAddress = ByteSpan(netif->hwaddr, netif->hwaddr_len); #endif diff --git a/src/platform/nxp/common/DnssdImpl.cpp b/src/platform/nxp/common/DnssdImpl.cpp new file mode 100644 index 00000000000000..8050da137f26b8 --- /dev/null +++ b/src/platform/nxp/common/DnssdImpl.cpp @@ -0,0 +1,797 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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 "lib/dnssd/platform/Dnssd.h" +#include +#include +#include +#include +#include + +#include + +#include + +using namespace ::chip::DeviceLayer; +using namespace chip::DeviceLayer::Internal; + +namespace chip { +namespace Dnssd { + +#define LOCAL_DOMAIN_STRING_SIZE 7 +#define ARPA_DOMAIN_STRING_SIZE 22 +#define MATTER_DNS_TXT_SIZE 128 + +// Support both operational and commissionable discovery, so buffers sizes must be worst case. +static constexpr uint8_t kMaxMdnsServiceTxtEntriesNumber = + std::max(Dnssd::CommissionAdvertisingParameters::kTxtMaxNumber, Dnssd::OperationalAdvertisingParameters::kTxtMaxNumber); +static constexpr size_t kTotalMdnsServiceTxtValueSize = std::max(Dnssd::CommissionAdvertisingParameters::kTxtTotalValueSize, + Dnssd::OperationalAdvertisingParameters::kTxtTotalValueSize); +static constexpr size_t kTotalMdnsServiceTxtKeySize = + std::max(Dnssd::CommissionAdvertisingParameters::kTxtTotalKeySize, Dnssd::OperationalAdvertisingParameters::kTxtTotalKeySize); + +static constexpr size_t kTotalMdnsServiceTxtBufferSize = + kTotalMdnsServiceTxtKeySize + kMaxMdnsServiceTxtEntriesNumber + kTotalMdnsServiceTxtValueSize; + +static const char * GetProtocolString(DnssdServiceProtocol protocol) +{ + return protocol == DnssdServiceProtocol::kDnssdProtocolUdp ? "_udp" : "_tcp"; +} + +struct DnsServiceTxtEntries +{ + uint8_t mBuffer[kTotalMdnsServiceTxtBufferSize]; + Dnssd::TextEntry mTxtEntries[kMaxMdnsServiceTxtEntriesNumber]; +}; + +struct mDnsQueryCtx +{ + void * matterCtx; + chip::Dnssd::DnssdService mMdnsService; + DnsServiceTxtEntries mServiceTxtEntry; + char mServiceType[chip::Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE + 1]; + CHIP_ERROR error; + + mDnsQueryCtx(void * context, CHIP_ERROR aError) + { + matterCtx = context; + error = aError; + } +}; + +static const char * GetProtocolString(DnssdServiceProtocol protocol); + +static void OtBrowseCallback(otError aError, const otDnsBrowseResponse * aResponse, void * aContext); +static void OtServiceCallback(otError aError, const otDnsServiceResponse * aResponse, void * aContext); + +static void DispatchBrowseEmpty(intptr_t context); +static void DispatchBrowse(intptr_t context); +static void DispatchBrowseNoMemory(intptr_t context); + +void DispatchAddressResolve(intptr_t context); +void DispatchResolve(intptr_t context); +void DispatchResolveNoMemory(intptr_t context); + +static DnsBrowseCallback mDnsBrowseCallback; +static DnsResolveCallback mDnsResolveCallback; + +CHIP_ERROR ResolveBySrp(DnssdService * mdnsReq, otInstance * thrInstancePtr, char * instanceName, void * context); +CHIP_ERROR BrowseBySrp(otInstance * thrInstancePtr, char * serviceName, void * context); + +CHIP_ERROR FromSrpCacheToMdnsData(const otSrpServerService * service, const otSrpServerHost * host, + const DnssdService * mdnsQueryReq, chip::Dnssd::DnssdService & mdnsService, + DnsServiceTxtEntries & serviceTxtEntries); + +static bool bBrowseInProgress = false; + +CHIP_ERROR ChipDnssdInit(DnssdAsyncReturnCallback initCallback, DnssdAsyncReturnCallback errorCallback, void * context) +{ + CHIP_ERROR error = CHIP_NO_ERROR; + otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance(); + + uint8_t macBuffer[ConfigurationManager::kPrimaryMACAddressLength]; + MutableByteSpan mac(macBuffer); + char hostname[kHostNameMaxLength + LOCAL_DOMAIN_STRING_SIZE + 1] = ""; + ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetPrimaryMACAddress(mac)); + MakeHostName(hostname, sizeof(hostname), mac); + snprintf(hostname + strlen(hostname), sizeof(hostname), ".local."); + + error = MapOpenThreadError(otMdnsServerSetHostName(thrInstancePtr, hostname)); + + initCallback(context, error); + return error; +} + +void ChipDnssdShutdown() +{ + otMdnsServerStop(ThreadStackMgrImpl().OTInstance()); +} + +CHIP_ERROR ChipDnssdRemoveServices() +{ + otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance(); + + otMdnsServerMarkServiceForRemoval(thrInstancePtr, nullptr, "_matter._tcp.local."); + otMdnsServerMarkServiceForRemoval(thrInstancePtr, nullptr, "_matterc._udp.local."); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ChipDnssdPublishService(const DnssdService * service, DnssdPublishCallback callback, void * context) +{ + ReturnErrorCodeIf(service == nullptr, CHIP_ERROR_INVALID_ARGUMENT); + otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance(); + otError otErr; + otDnsTxtEntry aTxtEntry; + uint32_t txtBufferOffset = 0; + + char fullInstName[Common::kInstanceNameMaxLength + chip::Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE + 1] = + ""; + char serviceType[chip::Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE + 1] = ""; + // secure space for the raw TXT data in the worst-case scenario relevant for Matter: + // each entry consists of txt_entry_size (1B) + txt_entry_key + "=" + txt_entry_data + uint8_t txtBuffer[kMaxMdnsServiceTxtEntriesNumber + kTotalMdnsServiceTxtBufferSize] = { 0 }; + + if ((strcmp(service->mHostName, "") != 0) && (nullptr == otMdnsServerGetHostName(thrInstancePtr))) + { + char hostname[kHostNameMaxLength + LOCAL_DOMAIN_STRING_SIZE + 1] = ""; + snprintf(hostname, sizeof(hostname), "%s.local.", service->mHostName); + otMdnsServerSetHostName(thrInstancePtr, hostname); + } + + snprintf(serviceType, sizeof(serviceType), "%s.%s.local.", service->mType, GetProtocolString(service->mProtocol)); + snprintf(fullInstName, sizeof(fullInstName), "%s.%s", service->mName, serviceType); + + for (uint32_t i = 0; i < service->mTextEntrySize; i++) + { + uint32_t keySize = strlen(service->mTextEntries[i].mKey); + // add TXT entry len, + 1 is for '=' + *(txtBuffer + txtBufferOffset++) = keySize + service->mTextEntries[i].mDataSize + 1; + + // add TXT entry key + memcpy(txtBuffer + txtBufferOffset, service->mTextEntries[i].mKey, keySize); + txtBufferOffset += keySize; + + // add TXT entry value if pointer is not null, if pointer is null it means we have bool value + if (service->mTextEntries[i].mData) + { + *(txtBuffer + txtBufferOffset++) = '='; + memcpy(txtBuffer + txtBufferOffset, service->mTextEntries[i].mData, service->mTextEntries[i].mDataSize); + txtBufferOffset += service->mTextEntries[i].mDataSize; + } + } + aTxtEntry.mKey = nullptr; + aTxtEntry.mValue = txtBuffer; + aTxtEntry.mValueLength = txtBufferOffset; + + otErr = otMdnsServerAddService(thrInstancePtr, fullInstName, serviceType, service->mSubTypes, service->mSubTypeSize, + service->mPort, &aTxtEntry, 1); + // Ignore duplicate error and threat it as error none + if (otErr == OT_ERROR_DUPLICATED) + otErr = OT_ERROR_NONE; + + return MapOpenThreadError(otErr); +} + +CHIP_ERROR ChipDnssdFinalizeServiceUpdate() +{ + otMdnsServerRemoveMarkedServices(ThreadStackMgrImpl().OTInstance()); + return CHIP_NO_ERROR; +} + +CHIP_ERROR ChipDnssdBrowse(const char * type, DnssdServiceProtocol protocol, Inet::IPAddressType addressType, + Inet::InterfaceId interface, DnssdBrowseCallback callback, void * context, intptr_t * browseIdentifier) +{ + *browseIdentifier = reinterpret_cast(nullptr); + CHIP_ERROR error = CHIP_NO_ERROR; + CHIP_ERROR srpBrowseError = CHIP_NO_ERROR; + char serviceType[chip::Dnssd::kDnssdTypeAndProtocolMaxSize + ARPA_DOMAIN_STRING_SIZE + 1] = ""; // +1 for null-terminator + + if (type == nullptr || callback == nullptr) + return CHIP_ERROR_INVALID_ARGUMENT; + + otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance(); + mDnsBrowseCallback = callback; + + mDnsQueryCtx * browseContext = Platform::New(context, CHIP_NO_ERROR); + VerifyOrReturnError(browseContext != nullptr, CHIP_ERROR_NO_MEMORY); + + // First try to browse the service in the SRP cache, use default.service.arpa as domain name + snprintf(serviceType, sizeof(serviceType), "%s.%s.default.service.arpa.", type, GetProtocolString(protocol)); + // After browsing in the SRP cache we will continue with regular mDNS browse + srpBrowseError = BrowseBySrp(thrInstancePtr, serviceType, context); + + // Proceed to generate a mDNS query + snprintf(browseContext->mServiceType, sizeof(browseContext->mServiceType), "%s.%s.local.", type, GetProtocolString(protocol)); + + error = MapOpenThreadError(otMdnsServerBrowse(thrInstancePtr, browseContext->mServiceType, OtBrowseCallback, browseContext)); + + if (CHIP_NO_ERROR == error) + { + bBrowseInProgress = true; + *browseIdentifier = reinterpret_cast(browseContext); + } + else + { + if (srpBrowseError == CHIP_NO_ERROR) + { + // In this case, we need to send a final browse indication to signal the Matter App that there are no more + // browse results coming + browseContext->error = error; + DeviceLayer::PlatformMgr().ScheduleWork(DispatchBrowseEmpty, reinterpret_cast(browseContext)); + } + else + { + Platform::Delete(browseContext); + } + } + return error; +} + +CHIP_ERROR ChipDnssdStopBrowse(intptr_t browseIdentifier) +{ + mDnsQueryCtx * browseContext = reinterpret_cast(browseIdentifier); + otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance(); + otError error = OT_ERROR_INVALID_ARGS; + + // browseContext is only valid when bBrowseInProgress is true. The Matter stack can call this function even with a browseContext + // that has been freed in DispatchBrowseEmpty before. + if ((true == bBrowseInProgress) && (browseContext)) + { + browseContext->error = MapOpenThreadError(otMdnsServerStopQuery(thrInstancePtr, browseContext->mServiceType)); + + // browse context will be freed in DispatchBrowseEmpty + DeviceLayer::PlatformMgr().ScheduleWork(DispatchBrowseEmpty, reinterpret_cast(browseContext)); + } + return MapOpenThreadError(error); +} + +CHIP_ERROR ChipDnssdResolve(DnssdService * browseResult, Inet::InterfaceId interface, DnssdResolveCallback callback, void * context) +{ + ChipError error; + if (browseResult == nullptr || callback == nullptr) + return CHIP_ERROR_INVALID_ARGUMENT; + + otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance(); + mDnsResolveCallback = callback; + + char serviceType[chip::Dnssd::kDnssdTypeAndProtocolMaxSize + ARPA_DOMAIN_STRING_SIZE + 1] = ""; // +1 for null-terminator + char fullInstName[Common::kInstanceNameMaxLength + chip::Dnssd::kDnssdTypeAndProtocolMaxSize + ARPA_DOMAIN_STRING_SIZE + 1] = + ""; + + // First try to find the service in the SRP cache, use default.service.arpa as domain name + snprintf(serviceType, sizeof(serviceType), "%s.%s.default.service.arpa.", browseResult->mType, + GetProtocolString(browseResult->mProtocol)); + snprintf(fullInstName, sizeof(fullInstName), "%s.%s", browseResult->mName, serviceType); + + error = ResolveBySrp(browseResult, thrInstancePtr, fullInstName, context); + if (CHIP_ERROR_NOT_FOUND == error) + { + // If the SRP cache returns not found, proceed to generate a MDNS query + memset(serviceType, 0, sizeof(serviceType)); + memset(fullInstName, 0, sizeof(fullInstName)); + + snprintf(serviceType, sizeof(serviceType), "%s.%s.local.", browseResult->mType, GetProtocolString(browseResult->mProtocol)); + snprintf(fullInstName, sizeof(fullInstName), "%s.%s", browseResult->mName, serviceType); + + return MapOpenThreadError(otMdnsServerResolveService(thrInstancePtr, fullInstName, OtServiceCallback, context)); + } + else + { + return error; + } +} + +CHIP_ERROR BrowseBySrp(otInstance * thrInstancePtr, char * serviceName, void * context) +{ + const otSrpServerHost * host = nullptr; + const otSrpServerService * service = nullptr; + CHIP_ERROR error = CHIP_ERROR_NOT_FOUND; + + while ((host = otSrpServerGetNextHost(thrInstancePtr, host)) != nullptr) + { + service = otSrpServerHostFindNextService(host, service, OT_SRP_SERVER_FLAGS_ANY_TYPE_ACTIVE_SERVICE, serviceName, nullptr); + if (service != nullptr) + { + mDnsQueryCtx * serviceContext; + + serviceContext = Platform::New(context, CHIP_NO_ERROR); + if (serviceContext != nullptr) + { + if (CHIP_NO_ERROR == + FromSrpCacheToMdnsData(service, host, nullptr, serviceContext->mMdnsService, serviceContext->mServiceTxtEntry)) + { + // Set error to CHIP_NO_ERROR to signal that there was at least one service found in the cache + error = CHIP_NO_ERROR; + DeviceLayer::PlatformMgr().ScheduleWork(DispatchBrowse, reinterpret_cast(serviceContext)); + } + else + { + Platform::Delete(serviceContext); + } + } + } + } + return error; +} + +CHIP_ERROR ResolveBySrp(DnssdService * mdnsReq, otInstance * thrInstancePtr, char * instanceName, void * context) +{ + const otSrpServerHost * host = nullptr; + const otSrpServerService * service = nullptr; + CHIP_ERROR error = CHIP_ERROR_NOT_FOUND; + + while ((host = otSrpServerGetNextHost(thrInstancePtr, host)) != nullptr) + { + service = otSrpServerHostFindNextService( + host, service, (OT_SRP_SERVER_SERVICE_FLAG_BASE_TYPE | OT_SRP_SERVER_SERVICE_FLAG_ACTIVE), nullptr, instanceName); + if (service != nullptr) + { + error = CHIP_NO_ERROR; + mDnsQueryCtx * serviceContext; + + serviceContext = Platform::New(context, CHIP_NO_ERROR); + if (serviceContext != nullptr) + { + error = + FromSrpCacheToMdnsData(service, host, mdnsReq, serviceContext->mMdnsService, serviceContext->mServiceTxtEntry); + if (error == CHIP_NO_ERROR) + { + DeviceLayer::PlatformMgr().ScheduleWork(DispatchResolve, reinterpret_cast(serviceContext)); + } + else + { + Platform::Delete(serviceContext); + } + } + else + { + error = CHIP_ERROR_NO_MEMORY; + } + break; + } + } + return error; +} + +CHIP_ERROR FromSrpCacheToMdnsData(const otSrpServerService * service, const otSrpServerHost * host, + const DnssdService * mdnsQueryReq, chip::Dnssd::DnssdService & mdnsService, + DnsServiceTxtEntries & serviceTxtEntries) +{ + const char * tmpName; + const uint8_t * txtStringPtr; + size_t substringSize; + uint8_t addrNum = 0; + uint16_t txtDataLen; + const otIp6Address * ip6AddrPtr = otSrpServerHostGetAddresses(host, &addrNum); + + if (mdnsQueryReq != nullptr) + { + Platform::CopyString(mdnsService.mName, sizeof(mdnsService.mName), mdnsQueryReq->mName); + Platform::CopyString(mdnsService.mType, sizeof(mdnsService.mType), mdnsQueryReq->mType); + mdnsService.mProtocol = mdnsQueryReq->mProtocol; + } + else + { + tmpName = otSrpServerServiceGetInstanceName(service); + // Extract from the .... the part + size_t substringSize = strchr(tmpName, '.') - tmpName; + if (substringSize >= ArraySize(mdnsService.mName)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + Platform::CopyString(mdnsService.mName, substringSize + 1, tmpName); + + // Extract from the .... the part. + tmpName = tmpName + substringSize + 1; + substringSize = strchr(tmpName, '.') - tmpName; + if (substringSize >= ArraySize(mdnsService.mType)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + Platform::CopyString(mdnsService.mType, substringSize + 1, tmpName); + + // Extract from the .... the part. + tmpName = tmpName + substringSize + 1; + substringSize = strchr(tmpName, '.') - tmpName; + if (substringSize >= (chip::Dnssd::kDnssdProtocolTextMaxSize + 1)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + if (strncmp(tmpName, "_udp", substringSize) == 0) + { + mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolUdp; + } + else if (strncmp(tmpName, "_tcp", substringSize) == 0) + { + mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolTcp; + } + else + { + mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolUnknown; + } + } + + // Extract from the .. the part. + tmpName = otSrpServerHostGetFullName(host); + substringSize = strchr(tmpName, '.') - tmpName; + if (substringSize >= ArraySize(mdnsService.mHostName)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + Platform::CopyString(mdnsService.mHostName, substringSize + 1, tmpName); + mdnsService.mPort = otSrpServerServiceGetPort(service); + + // All SRP cache hits come from the Thread Netif + mdnsService.mInterface = ConnectivityManagerImpl().GetThreadInterface(); + + mdnsService.mAddressType = Inet::IPAddressType::kIPv6; + mdnsService.mAddress = MakeOptional(ToIPAddress(*ip6AddrPtr)); + + // Extract TXT record SRP service + txtStringPtr = otSrpServerServiceGetTxtData(service, &txtDataLen); + if (txtDataLen != 0) + { + otDnsTxtEntryIterator iterator; + otDnsInitTxtEntryIterator(&iterator, txtStringPtr, txtDataLen); + + otDnsTxtEntry txtEntry; + chip::FixedBufferAllocator alloc(serviceTxtEntries.mBuffer); + + uint8_t entryIndex = 0; + while ((otDnsGetNextTxtEntry(&iterator, &txtEntry) == OT_ERROR_NONE) && entryIndex < 64) + { + if (txtEntry.mKey == nullptr || txtEntry.mValue == nullptr) + continue; + + serviceTxtEntries.mTxtEntries[entryIndex].mKey = alloc.Clone(txtEntry.mKey); + serviceTxtEntries.mTxtEntries[entryIndex].mData = alloc.Clone(txtEntry.mValue, txtEntry.mValueLength); + serviceTxtEntries.mTxtEntries[entryIndex].mDataSize = txtEntry.mValueLength; + entryIndex++; + } + + ReturnErrorCodeIf(alloc.AnyAllocFailed(), CHIP_ERROR_BUFFER_TOO_SMALL); + + mdnsService.mTextEntries = serviceTxtEntries.mTxtEntries; + mdnsService.mTextEntrySize = entryIndex; + } + else + { + mdnsService.mTextEntrySize = 0; + } + + mdnsService.mSubTypes = nullptr; + mdnsService.mSubTypeSize = 0; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR FromOtDnsResponseToMdnsData(otDnsServiceInfo & serviceInfo, const char * serviceType, + chip::Dnssd::DnssdService & mdnsService, DnsServiceTxtEntries & serviceTxtEntries, + otError error) +{ + char protocol[chip::Dnssd::kDnssdProtocolTextMaxSize + 1]; + + if (strchr(serviceType, '.') == nullptr) + return CHIP_ERROR_INVALID_ARGUMENT; + + // Extract from the ... the part. + size_t substringSize = strchr(serviceType, '.') - serviceType; + if (substringSize >= ArraySize(mdnsService.mType)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + Platform::CopyString(mdnsService.mType, substringSize + 1, serviceType); + + // Extract from the ... the part. + const char * protocolSubstringStart = serviceType + substringSize + 1; + + if (strchr(protocolSubstringStart, '.') == nullptr) + return CHIP_ERROR_INVALID_ARGUMENT; + + substringSize = strchr(protocolSubstringStart, '.') - protocolSubstringStart; + if (substringSize >= ArraySize(protocol)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + Platform::CopyString(protocol, substringSize + 1, protocolSubstringStart); + + if (strncmp(protocol, "_udp", chip::Dnssd::kDnssdProtocolTextMaxSize) == 0) + { + mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolUdp; + } + else if (strncmp(protocol, "_tcp", chip::Dnssd::kDnssdProtocolTextMaxSize) == 0) + { + mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolTcp; + } + else + { + mdnsService.mProtocol = chip::Dnssd::DnssdServiceProtocol::kDnssdProtocolUnknown; + } + + // Check if SRV record was included in DNS response. + if (error != OT_ERROR_NOT_FOUND) + { + if (strchr(serviceInfo.mHostNameBuffer, '.') == nullptr) + return CHIP_ERROR_INVALID_ARGUMENT; + + // Extract from the .. the part. + substringSize = strchr(serviceInfo.mHostNameBuffer, '.') - serviceInfo.mHostNameBuffer; + if (substringSize >= ArraySize(mdnsService.mHostName)) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + Platform::CopyString(mdnsService.mHostName, substringSize + 1, serviceInfo.mHostNameBuffer); + + mdnsService.mPort = serviceInfo.mPort; + } + + // All mDNS replies come from the External Netif + mdnsService.mInterface = ConnectivityManagerImpl().GetExternalInterface(); + + // Check if AAAA record was included in DNS response. + if (!otIp6IsAddressUnspecified(&serviceInfo.mHostAddress)) + { + mdnsService.mAddressType = Inet::IPAddressType::kIPv6; + mdnsService.mAddress = MakeOptional(ToIPAddress(serviceInfo.mHostAddress)); + } + + // Check if TXT record was included in DNS response. + if (serviceInfo.mTxtDataSize != 0) + { + otDnsTxtEntryIterator iterator; + otDnsInitTxtEntryIterator(&iterator, serviceInfo.mTxtData, serviceInfo.mTxtDataSize); + + otDnsTxtEntry txtEntry; + chip::FixedBufferAllocator alloc(serviceTxtEntries.mBuffer); + + uint8_t entryIndex = 0; + while ((otDnsGetNextTxtEntry(&iterator, &txtEntry) == OT_ERROR_NONE) && entryIndex < 64) + { + if (txtEntry.mKey == nullptr || txtEntry.mValue == nullptr) + continue; + + serviceTxtEntries.mTxtEntries[entryIndex].mKey = alloc.Clone(txtEntry.mKey); + serviceTxtEntries.mTxtEntries[entryIndex].mData = alloc.Clone(txtEntry.mValue, txtEntry.mValueLength); + serviceTxtEntries.mTxtEntries[entryIndex].mDataSize = txtEntry.mValueLength; + entryIndex++; + } + + ReturnErrorCodeIf(alloc.AnyAllocFailed(), CHIP_ERROR_BUFFER_TOO_SMALL); + + mdnsService.mTextEntries = serviceTxtEntries.mTxtEntries; + mdnsService.mTextEntrySize = entryIndex; + } + else + { + mdnsService.mTextEntrySize = 0; + } + + return CHIP_NO_ERROR; +} + +void ChipDnssdResolveNoLongerNeeded(const char * instanceName) {} + +CHIP_ERROR ChipDnssdReconfirmRecord(const char * hostname, chip::Inet::IPAddress address, chip::Inet::InterfaceId interface) +{ + return CHIP_ERROR_NOT_IMPLEMENTED; +} + +static void OtBrowseCallback(otError aError, const otDnsBrowseResponse * aResponse, void * aContext) +{ + CHIP_ERROR error; + // type buffer size is kDnssdTypeAndProtocolMaxSize + . + kMaxDomainNameSize + . + termination character + char type[Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE + 3]; + // hostname buffer size is kHostNameMaxLength + . + kMaxDomainNameSize + . + termination character + char hostname[Dnssd::kHostNameMaxLength + LOCAL_DOMAIN_STRING_SIZE + 3]; + // secure space for the raw TXT data in the worst-case scenario relevant for Matter: + // each entry consists of txt_entry_size (1B) + txt_entry_key + "=" + txt_entry_data + uint8_t txtBuffer[kMaxMdnsServiceTxtEntriesNumber + kTotalMdnsServiceTxtBufferSize]; + + mDnsQueryCtx * browseContext = reinterpret_cast(aContext); + otDnsServiceInfo serviceInfo; + uint16_t index = 0; + + /// TODO: check this code, might be remvoed, or if not free browseContext + if (mDnsBrowseCallback == nullptr) + { + ChipLogError(DeviceLayer, "Invalid dns browse callback"); + return; + } + + VerifyOrExit(aError == OT_ERROR_NONE, error = MapOpenThreadError(aError)); + + error = MapOpenThreadError(otDnsBrowseResponseGetServiceName(aResponse, type, sizeof(type))); + + VerifyOrExit(error == CHIP_NO_ERROR, ); + + char serviceName[Dnssd::Common::kInstanceNameMaxLength + 1]; + while (otDnsBrowseResponseGetServiceInstance(aResponse, index, serviceName, sizeof(serviceName)) == OT_ERROR_NONE) + { + serviceInfo.mHostNameBuffer = hostname; + serviceInfo.mHostNameBufferSize = sizeof(hostname); + serviceInfo.mTxtData = txtBuffer; + serviceInfo.mTxtDataSize = sizeof(txtBuffer); + + otError err = otDnsBrowseResponseGetServiceInfo(aResponse, serviceName, &serviceInfo); + error = MapOpenThreadError(err); + + VerifyOrExit(err == OT_ERROR_NOT_FOUND || err == OT_ERROR_NONE, ); + + mDnsQueryCtx * tmpContext = Platform::New(browseContext->matterCtx, CHIP_NO_ERROR); + + VerifyOrExit(tmpContext != nullptr, error = CHIP_ERROR_NO_MEMORY); + + error = FromOtDnsResponseToMdnsData(serviceInfo, type, tmpContext->mMdnsService, tmpContext->mServiceTxtEntry, err); + if (CHIP_NO_ERROR == error) + { + // Invoke callback for every service one by one instead of for the whole + // list due to large memory size needed to allocate on stack. + static_assert(ArraySize(tmpContext->mMdnsService.mName) >= ArraySize(serviceName), + "The target buffer must be big enough"); + Platform::CopyString(tmpContext->mMdnsService.mName, serviceName); + DeviceLayer::PlatformMgr().ScheduleWork(DispatchBrowse, reinterpret_cast(tmpContext)); + } + else + { + Platform::Delete(tmpContext); + } + index++; + } + +exit: + // Invoke callback to notify about end-of-browse when OT_ERROR_RESPONSE_TIMEOUT is received, otherwise ignore errors + if (aError == OT_ERROR_RESPONSE_TIMEOUT) + { + DeviceLayer::PlatformMgr().ScheduleWork(DispatchBrowseEmpty, reinterpret_cast(browseContext)); + } +} +static void OtServiceCallback(otError aError, const otDnsServiceResponse * aResponse, void * aContext) +{ + CHIP_ERROR error; + otError otErr; + otDnsServiceInfo serviceInfo; + mDnsQueryCtx * serviceContext; + bool bStopQuery = false; + + // If error is timeout we don't need to inform the Matter app and we can just exit + VerifyOrReturn(aError != OT_ERROR_RESPONSE_TIMEOUT, ); + + bStopQuery = true; + serviceContext = Platform::New(aContext, MapOpenThreadError(aError)); + VerifyOrExit(serviceContext != nullptr, error = CHIP_ERROR_NO_MEMORY); + + // type buffer size is kDnssdTypeAndProtocolMaxSize + . + kMaxDomainNameSize + . + termination character + char type[Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE + 3]; + // hostname buffer size is kHostNameMaxLength + . + kMaxDomainNameSize + . + termination character + char hostname[Dnssd::kHostNameMaxLength + LOCAL_DOMAIN_STRING_SIZE + 3]; + // secure space for the raw TXT data in the worst-case scenario relevant for Matter: + // each entry consists of txt_entry_size (1B) + txt_entry_key + "=" + txt_entry_data + uint8_t txtBuffer[kMaxMdnsServiceTxtEntriesNumber + kTotalMdnsServiceTxtBufferSize]; + + if (mDnsResolveCallback == nullptr) + { + ChipLogError(DeviceLayer, "Invalid dns resolve callback"); + return; + } + + VerifyOrExit(aError == OT_ERROR_NONE, error = MapOpenThreadError(aError)); + + error = MapOpenThreadError(otDnsServiceResponseGetServiceName(aResponse, serviceContext->mMdnsService.mName, + sizeof(serviceContext->mMdnsService.mName), type, sizeof(type))); + + VerifyOrExit(error == CHIP_NO_ERROR, ); + + serviceInfo.mHostNameBuffer = hostname; + serviceInfo.mHostNameBufferSize = sizeof(hostname); + serviceInfo.mTxtData = txtBuffer; + serviceInfo.mTxtDataSize = sizeof(txtBuffer); + + otErr = otDnsServiceResponseGetServiceInfo(aResponse, &serviceInfo); + error = MapOpenThreadError(otErr); + + VerifyOrExit(error == CHIP_NO_ERROR, ); + + error = FromOtDnsResponseToMdnsData(serviceInfo, type, serviceContext->mMdnsService, serviceContext->mServiceTxtEntry, otErr); + +exit: + if (serviceContext == nullptr) + { + DeviceLayer::PlatformMgr().ScheduleWork(DispatchResolveNoMemory, reinterpret_cast(aContext)); + return; + } + + serviceContext->error = error; + + // If IPv6 address in unspecified (AAAA record not present), send additional DNS query to obtain IPv6 address. + if (otIp6IsAddressUnspecified(&serviceInfo.mHostAddress)) + { + DeviceLayer::PlatformMgr().ScheduleWork(DispatchAddressResolve, reinterpret_cast(serviceContext)); + } + else + { + DeviceLayer::PlatformMgr().ScheduleWork(DispatchResolve, reinterpret_cast(serviceContext)); + } + + if (bStopQuery) + { + char fullInstName[Common::kInstanceNameMaxLength + chip::Dnssd::kDnssdTypeAndProtocolMaxSize + LOCAL_DOMAIN_STRING_SIZE + + 1] = ""; + snprintf(fullInstName, sizeof(fullInstName), "%s.%s", serviceContext->mMdnsService.mName, type); + + otInstance * thrInstancePtr = ThreadStackMgrImpl().OTInstance(); + otMdnsServerStopQuery(thrInstancePtr, fullInstName); + } +} + +void DispatchBrowseEmpty(intptr_t context) +{ + auto * browseContext = reinterpret_cast(context); + mDnsBrowseCallback(browseContext->matterCtx, nullptr, 0, true, browseContext->error); + Platform::Delete(browseContext); + bBrowseInProgress = false; +} + +void DispatchBrowse(intptr_t context) +{ + auto * browseContext = reinterpret_cast(context); + mDnsBrowseCallback(browseContext->matterCtx, &browseContext->mMdnsService, 1, false, browseContext->error); + Platform::Delete(browseContext); +} + +void DispatchBrowseNoMemory(intptr_t context) +{ + mDnsBrowseCallback(reinterpret_cast(context), nullptr, 0, true, CHIP_ERROR_NO_MEMORY); +} + +void DispatchAddressResolve(intptr_t context) +{ + CHIP_ERROR error = CHIP_ERROR_NO_MEMORY; // ResolveAddress(context, OnDnsAddressResolveResult); + + // In case of address resolve failure, fill the error code field and dispatch method to end resolve process. + if (error != CHIP_NO_ERROR) + { + mDnsQueryCtx * resolveContext = reinterpret_cast(context); + resolveContext->error = error; + + DeviceLayer::PlatformMgr().ScheduleWork(DispatchResolve, reinterpret_cast(resolveContext)); + } +} + +void DispatchResolve(intptr_t context) +{ + mDnsQueryCtx * resolveContext = reinterpret_cast(context); + Dnssd::DnssdService & service = resolveContext->mMdnsService; + Span ipAddrs; + + if (service.mAddress.HasValue()) + { + ipAddrs = Span(&service.mAddress.Value(), 1); + } + + mDnsResolveCallback(resolveContext->matterCtx, &service, ipAddrs, resolveContext->error); + Platform::Delete(resolveContext); +} + +void DispatchResolveNoMemory(intptr_t context) +{ + Span ipAddrs; + mDnsResolveCallback(reinterpret_cast(context), nullptr, ipAddrs, CHIP_ERROR_NO_MEMORY); +} + +} // namespace Dnssd +} // namespace chip diff --git a/src/platform/nxp/common/KeyValueStoreManagerImpl.cpp b/src/platform/nxp/common/KeyValueStoreManagerImpl.cpp index 8b704841374d21..af2598ca2be188 100644 --- a/src/platform/nxp/common/KeyValueStoreManagerImpl.cpp +++ b/src/platform/nxp/common/KeyValueStoreManagerImpl.cpp @@ -46,6 +46,15 @@ CHIP_ERROR KeyValueStoreManagerImpl::_Get(const char * key, void * value, size_t err = chip::DeviceLayer::Internal::NXPConfig::ReadConfigValueBin(key, (uint8_t *) value, value_size, read_bytes); + if (err == CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND) + { + ChipLogError(DeviceLayer, "KVS, key not found!"); + } + else if (err != CHIP_NO_ERROR) + { + ChipLogError(DeviceLayer, "KVS, failed to read key!"); + } + *read_bytes_size = read_bytes; exit: @@ -63,6 +72,9 @@ CHIP_ERROR KeyValueStoreManagerImpl::_Put(const char * key, const void * value, err = chip::DeviceLayer::Internal::NXPConfig::WriteConfigValueBin(key, (uint8_t *) value, value_size); + if (err != CHIP_NO_ERROR) + ChipLogError(DeviceLayer, "KVS, failed to save key!"); + exit: ConvertError(err); return err; @@ -78,6 +90,9 @@ CHIP_ERROR KeyValueStoreManagerImpl::_Delete(const char * key) err = chip::DeviceLayer::Internal::NXPConfig::ClearConfigValue(key); + if (err != CHIP_NO_ERROR) + ChipLogError(DeviceLayer, "KVS, failed to delete key!"); + exit: ConvertError(err); return err; diff --git a/src/platform/nxp/common/NXPConfig.cpp b/src/platform/nxp/common/NXPConfig.cpp index fef73e8a31a34e..cc6f8d51ec1256 100644 --- a/src/platform/nxp/common/NXPConfig.cpp +++ b/src/platform/nxp/common/NXPConfig.cpp @@ -37,13 +37,23 @@ #define CHIP_PLAT_SAVE_NVM_DATA_ON_IDLE 1 #endif -#if CHIP_DEVICE_CONFIG_ENABLE_THREAD -#include "ot_platform_common.h" +#define BUFFER_LOG_SIZE 256 + +/* + * If the developer has specified a size for integer keys RAM buffer + * partition, use it. Othewise use the default. + */ +#ifndef CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_INT +#define CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_INT (4 * 2048) #endif -#define BUFFER_LOG_SIZE 256 -#define CHIP_CONFIG_RAM_BUFFER_KEY_INT_SIZE 4 * 2048 -#define CHIP_CONFIG_RAM_BUFFER_KEY_STRING_SIZE 4 * 5000 +/* + * If the developer has specified a size for string keys RAM buffer + * partition, use it. Othewise use the default. + */ +#ifndef CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_STRING +#define CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_STRING (4 * 5000) +#endif #ifndef NVM_ID_CHIP_CONFIG_DATA_KEY_INT #define NVM_ID_CHIP_CONFIG_DATA_KEY_INT 0xf104 @@ -74,21 +84,21 @@ typedef struct { uint16_t chipConfigRamBufferLen; uint16_t padding; - uint8_t chipConfigRamBuffer[CHIP_CONFIG_RAM_BUFFER_KEY_INT_SIZE]; + uint8_t chipConfigRamBuffer[CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_INT]; } ChipConfigRamStructKeyInt; typedef struct { uint16_t chipConfigRamBufferLen; uint16_t padding; - uint8_t chipConfigRamBuffer[CHIP_CONFIG_RAM_BUFFER_KEY_STRING_SIZE]; + uint8_t chipConfigRamBuffer[CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_STRING]; } ChipConfigRamStructKeyString; /* File system containing only integer keys */ static ChipConfigRamStructKeyInt chipConfigRamStructKeyInt; static ramBufferDescriptor ramDescrKeyInt = { .ramBufferLen = &chipConfigRamStructKeyInt.chipConfigRamBufferLen, - .ramBufferMaxLen = CHIP_CONFIG_RAM_BUFFER_KEY_INT_SIZE, + .ramBufferMaxLen = CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_INT, .pRamBuffer = &chipConfigRamStructKeyInt.chipConfigRamBuffer[0], }; @@ -96,7 +106,7 @@ static ramBufferDescriptor ramDescrKeyInt = { static ChipConfigRamStructKeyString chipConfigRamStructKeyString; static ramBufferDescriptor ramDescrKeyString = { .ramBufferLen = &chipConfigRamStructKeyString.chipConfigRamBufferLen, - .ramBufferMaxLen = CHIP_CONFIG_RAM_BUFFER_KEY_STRING_SIZE, + .ramBufferMaxLen = CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_STRING, .pRamBuffer = &chipConfigRamStructKeyString.chipConfigRamBuffer[0], }; @@ -109,8 +119,8 @@ NVM_RegisterDataSet((void *) &chipConfigRamStructKeyString, 1, sizeof(chipConfig NVM_ID_CHIP_CONFIG_DATA_KEY_STRING, gNVM_MirroredInRam_c); #elif (CHIP_PLAT_NVM_SUPPORT == CHIP_PLAT_LITTLEFS) -const char mt_key_int_file_name[] = "mt_key_int"; -const char mt_key_str_file_name[] = "mt_key_str"; +const char * mt_key_int_file_name = "mt_key_int"; +const char * mt_key_str_file_name = "mt_key_str"; #if CHIP_PLAT_SAVE_NVM_DATA_ON_IDLE static bool mt_key_int_save_in_flash = false; static bool mt_key_str_save_in_flash = false; @@ -140,7 +150,7 @@ int NXPConfig::SaveIntKeysToFS(void) err_len = ramStorageSavetoFlash(mt_key_int_file_name, &chipConfigRamStructKeyInt.chipConfigRamBuffer[0], chipConfigRamStructKeyInt.chipConfigRamBufferLen); - assert(err_len <= CHIP_CONFIG_RAM_BUFFER_KEY_INT_SIZE); + assert(err_len <= CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_INT); assert(err_len >= 0); #endif @@ -156,7 +166,7 @@ int NXPConfig::SaveStringKeysToFS(void) int err_len; #if (CHIP_PLAT_NVM_SUPPORT == CHIP_PLAT_NVM_FWK) err_len = -1; - NvSaveOnIdle(&chipConfigRamStructKeyInt, false); + NvSaveOnIdle(&chipConfigRamStructKeyString, false); #elif (CHIP_PLAT_NVM_SUPPORT == CHIP_PLAT_LITTLEFS) err_len = -2; @@ -168,7 +178,7 @@ int NXPConfig::SaveStringKeysToFS(void) err_len = ramStorageSavetoFlash(mt_key_str_file_name, &chipConfigRamStructKeyString.chipConfigRamBuffer[0], chipConfigRamStructKeyString.chipConfigRamBufferLen); - assert(err_len <= CHIP_CONFIG_RAM_BUFFER_KEY_STRING_SIZE); + assert(err_len <= CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_STRING); assert(err_len >= 0); #endif @@ -207,14 +217,14 @@ CHIP_ERROR NXPConfig::Init() #elif (CHIP_PLAT_NVM_SUPPORT == CHIP_PLAT_LITTLEFS) /* Try to load the ot dataset in RAM */ err_len = ramStorageReadFromFlash(mt_key_int_file_name, &chipConfigRamStructKeyInt.chipConfigRamBuffer[0], - CHIP_CONFIG_RAM_BUFFER_KEY_INT_SIZE); - assert(err_len <= CHIP_CONFIG_RAM_BUFFER_KEY_INT_SIZE); + CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_INT); + assert(err_len <= CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_INT); assert(err_len >= 0); chipConfigRamStructKeyInt.chipConfigRamBufferLen = (uint16_t) err_len; err_len = ramStorageReadFromFlash(mt_key_str_file_name, &chipConfigRamStructKeyString.chipConfigRamBuffer[0], - CHIP_CONFIG_RAM_BUFFER_KEY_STRING_SIZE); - assert(err_len <= CHIP_CONFIG_RAM_BUFFER_KEY_STRING_SIZE); + CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_STRING); + assert(err_len <= CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_STRING); assert(err_len >= 0); chipConfigRamStructKeyString.chipConfigRamBufferLen = (uint16_t) err_len; @@ -240,7 +250,9 @@ CHIP_ERROR NXPConfig::ReadConfigValue(Key key, bool & val) SuccessOrExit(err = MapRamStorageStatus(status)); val = tempVal; +#if (DEBUG_NVM > 0) ChipLogProgress(DeviceLayer, "ReadConfigValue bool = %u", val); +#endif exit: return err; @@ -258,7 +270,9 @@ CHIP_ERROR NXPConfig::ReadConfigValue(Key key, uint32_t & val) SuccessOrExit(err = MapRamStorageStatus(status)); val = tempVal; +#if (DEBUG_NVM > 0) ChipLogProgress(DeviceLayer, "ReadConfigValue uint32_t = %lu", val); +#endif exit: return err; @@ -276,7 +290,9 @@ CHIP_ERROR NXPConfig::ReadConfigValue(Key key, uint64_t & val) SuccessOrExit(err = MapRamStorageStatus(status)); val = tempVal; +#if (DEBUG_NVM > 0) ChipLogProgress(DeviceLayer, "ReadConfigValue uint64_t = " ChipLogFormatX64, ChipLogValueX64(val)); +#endif exit: return err; @@ -292,7 +308,9 @@ CHIP_ERROR NXPConfig::ReadConfigValueStr(Key key, char * buf, size_t bufSize, si status = ramStorageGet(&ramDescrKeyInt, (uint8_t *) &key, sizeof(Key), 0, (uint8_t *) buf, &sizeToRead); SuccessOrExit(err = MapRamStorageStatus(status)); outLen = sizeToRead; +#if (DEBUG_NVM > 0) ChipLogProgress(DeviceLayer, "ReadConfigValueStr lenRead = %u", outLen); +#endif exit: return err; @@ -313,7 +331,9 @@ CHIP_ERROR NXPConfig::ReadConfigValueBin(const char * keyString, uint8_t * buf, status = ramStorageGet(&ramDescrKeyString, (const uint8_t *) keyString, strlen(keyString), 0, (uint8_t *) buf, &sizeToRead); SuccessOrExit(err = MapRamStorageStatus(status)); outLen = sizeToRead; +#if (DEBUG_NVM > 0) ChipLogProgress(DeviceLayer, "ReadConfigValueStr lenRead = %u", outLen); +#endif exit: return err; @@ -339,7 +359,9 @@ CHIP_ERROR NXPConfig::WriteConfigValue(Key key, bool val) (void) err_len; DBG_PRINTF("WriteConfigValue: MT write %d\r\n", err_len); +#if (DEBUG_NVM > 0) ChipLogProgress(DeviceLayer, "WriteConfigValue done"); +#endif exit: return err; @@ -359,7 +381,9 @@ CHIP_ERROR NXPConfig::WriteConfigValue(Key key, uint32_t val) (void) err_len; DBG_PRINTF("WriteConfigValue: MT write %d\r\n", err_len); +#if (DEBUG_NVM > 0) ChipLogProgress(DeviceLayer, "WriteConfigValue done"); +#endif exit: return err; @@ -379,7 +403,9 @@ CHIP_ERROR NXPConfig::WriteConfigValue(Key key, uint64_t val) (void) err_len; DBG_PRINTF("WriteConfigValue64: MT write %d\r\n", err_len); +#if (DEBUG_NVM > 0) ChipLogProgress(DeviceLayer, "WriteConfigValue done"); +#endif exit: return err; @@ -404,7 +430,9 @@ CHIP_ERROR NXPConfig::WriteConfigValueStr(Key key, const char * str, size_t strL (void) err_len; DBG_PRINTF("WriteConfigValueStr: MT write %d\r\n", err_len); +#if (DEBUG_NVM > 0) ChipLogProgress(DeviceLayer, "WriteConfigValue done"); +#endif exit: return err; @@ -429,7 +457,9 @@ CHIP_ERROR NXPConfig::WriteConfigValueBin(const char * keyString, const uint8_t (void) err_len; DBG_PRINTF("WriteConfigValueBin: MT write %d\r\n", err_len); +#if (DEBUG_NVM > 0) ChipLogProgress(DeviceLayer, "WriteConfigValue done"); +#endif exit: return err; @@ -499,11 +529,26 @@ CHIP_ERROR NXPConfig::FactoryResetConfig(void) ClearConfigValue(key); } + // Clear RebootCount, TotalOperationalHours, UpTime counters during factory reset + for (Key key = kMinConfigKey_ChipCounter; key <= (kMinConfigKey_ChipCounter + 3); key++) + { + ClearConfigValue(key); + } + /* Reset the key string file system as it contains on data that needs to be erased when doing a factoryreset */ FLib_MemSet((void *) &chipConfigRamStructKeyString, 0, sizeof(chipConfigRamStructKeyString)); +#if (CHIP_PLAT_NVM_SUPPORT == CHIP_PLAT_NVM_FWK) + /* + * Save to flash now. System is restarting and there is no more time to + * wait for the idle task to save the data. + */ + NvSyncSave(&chipConfigRamStructKeyString, false); + NvSyncSave(&chipConfigRamStructKeyInt, false); +#else SaveStringKeysToFS(); SaveIntKeysToFS(); +#endif DBG_PRINTF("FactoryResetConfig done\r\n"); return CHIP_NO_ERROR; @@ -569,12 +614,14 @@ void NXPConfig::RunSystemIdleTask(void) err_len = ramStorageSavetoFlash(mt_key_int_file_name, &chipConfigRamStructKeyInt.chipConfigRamBuffer[0], chipConfigRamStructKeyInt.chipConfigRamBufferLen); - assert(err_len <= CHIP_CONFIG_RAM_BUFFER_KEY_INT_SIZE); + assert(err_len <= CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_INT); assert(err_len >= 0); INFO_PRINTF("int mt write %d bytes / %d \r\n", err_len, chipConfigRamStructKeyInt.chipConfigRamBufferLen); #if 0 - int len = ramStorageReadFromFlash(mt_key_int_file_name, &chipConfigRamStructKeyInt.chipConfigRamBuffer[0], CHIP_CONFIG_RAM_BUFFER_KEY_INT_SIZE); + int len = ramStorageReadFromFlash(mt_key_int_file_name, + &chipConfigRamStructKeyInt.chipConfigRamBuffer[0], + CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_INT); INFO_PRINTF("mt read after write %d\r\n", len); #endif } @@ -591,12 +638,14 @@ void NXPConfig::RunSystemIdleTask(void) err_len = ramStorageSavetoFlash(mt_key_str_file_name, &chipConfigRamStructKeyString.chipConfigRamBuffer[0], chipConfigRamStructKeyString.chipConfigRamBufferLen); - assert(err_len <= CHIP_CONFIG_RAM_BUFFER_KEY_STRING_SIZE); + assert(err_len <= CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_STRING); assert(err_len >= 0); INFO_PRINTF("str mt write %d bytes / %d \r\n", err_len, chipConfigRamStructKeyString.chipConfigRamBufferLen); #if 0 - int len = ramStorageReadFromFlash(mt_key_str_file_name, &chipConfigRamStructKeyString.chipConfigRamBuffer[0], CHIP_CONFIG_RAM_BUFFER_KEY_STRING_SIZE); + int len = ramStorageReadFromFlash(mt_key_str_file_name, + &chipConfigRamStructKeyString.chipConfigRamBuffer[0], + CONFIG_CHIP_NVM_RAMBUFFER_SIZE_KEY_STRING); INFO_PRINTF("mt read after write %d\r\n", len); #endif } diff --git a/src/platform/nxp/common/NXPConfigKS.cpp b/src/platform/nxp/common/NXPConfigKS.cpp new file mode 100644 index 00000000000000..f7dd6bdc972919 --- /dev/null +++ b/src/platform/nxp/common/NXPConfigKS.cpp @@ -0,0 +1,494 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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. + */ + +/** + * @file + * Utilities for accessing persisted device configuration on + * platforms based on the NXP SDK. + */ +/* this file behaves like a config.h, comes first */ +#include + +#include "NXPConfig.h" + +#include "FreeRTOS.h" +#include "FunctionLib.h" +#include "board.h" +#include +#include + +#include "fwk_file_cache.h" +#include "fwk_key_storage.h" +#include "fwk_lfs_mflash.h" + +#if defined(DEBUG_NVM) && (DEBUG_NVM == 2) +#include "fsl_debug_console.h" +#define DBG_PRINTF PRINTF +#define INFO_PRINTF PRINTF + +#elif defined(DEBUG_NVM) && (DEBUG_NVM == 1) +#include "fsl_debug_console.h" +#define DBG_PRINTF PRINTF +#define INFO_PRINTF(...) + +#else +#define DBG_PRINTF(...) +#define INFO_PRINTF(...) +#endif + +/* Temporary namespace for integer and string keys */ +#define NS_INT "_fki" +#define NS_STR "_fks" + +/* Set to 1 if you want to see the statistics about the keys and their length */ +#define ENABLE_KEYS_STATS 0 + +/* Size of the ram memory section for the KS configuration */ +/* following the study of the size of the Matter key storage files, +we defined that a buffer size equal to 2265 is necessary. We decided to add a mark-up, bringing the buffer size to 5k to be safe */ +#ifndef KS_MATTER_SCRATCH_AREA_SIZE_MAX +#define KS_MATTER_SCRATCH_AREA_SIZE_MAX 5 * 1024 +#endif + +static uint8_t mem_section[KS_MATTER_SCRATCH_AREA_SIZE_MAX]; +static ks_config_t ks_config = { + .size = KS_MATTER_SCRATCH_AREA_SIZE_MAX, .KS_name = "KSconf", .mem_p = mem_section, + //.mem_p = NULL, +}; +static void * ks_handle_p = NULL; + +static bool isInitialized = false; + +#if (ENABLE_KEYS_STATS == 1) +typedef struct +{ + uint16_t writtenKeys_str; + uint16_t deletedKeys_str; + uint16_t writtenKeys_int; + uint16_t deletedKeys_int; +} ks_keys_stats_t; +static ks_keys_stats_t keys_stats = { 0, 0, 0, 0 }; +#endif + +namespace chip { +namespace DeviceLayer { +namespace Internal { + +CHIP_ERROR NXPConfig::Init() +{ + if (!isInitialized) + { + ks_handle_p = KS_Init(&ks_config); + isInitialized = true; + } + + DBG_PRINTF("Init"); + return CHIP_NO_ERROR; +} + +CHIP_ERROR NXPConfig::ReadConfigValue(Key key, bool & val) +{ + CHIP_ERROR err; + ks_error_t status; + int req_len; + int outLen; + bool tempVal; + + req_len = sizeof(bool); + outLen = 0; + tempVal = false; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_GetKeyInt(ks_handle_p, (int) key, (char *) NS_INT, (void *) &tempVal, req_len, &outLen); + SuccessOrExit(err = MapKeyStorageStatus(status)); + val = tempVal; +#if (DEBUG_NVM > 0) + ChipLogProgress(DeviceLayer, "ReadConfigValue bool = %u", val); +#endif + +exit: + return err; +} + +CHIP_ERROR NXPConfig::ReadConfigValue(Key key, uint32_t & val) +{ + CHIP_ERROR err; + ks_error_t status; + int req_len; + int outLen; + uint32_t tempVal; + + req_len = sizeof(uint32_t); + outLen = 0; + tempVal = 0; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_GetKeyInt(ks_handle_p, (int) key, (char *) NS_INT, (void *) &tempVal, req_len, &outLen); + SuccessOrExit(err = MapKeyStorageStatus(status)); + val = tempVal; +#if (DEBUG_NVM > 0) + ChipLogProgress(DeviceLayer, "ReadConfigValue uint32_t = %lu", val); +#endif + +exit: + return err; +} + +CHIP_ERROR NXPConfig::ReadConfigValue(Key key, uint64_t & val) +{ + CHIP_ERROR err; + ks_error_t status; + int req_len; + int outLen; + uint64_t tempVal; + + req_len = sizeof(uint64_t); + outLen = 0; + tempVal = 0; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_GetKeyInt(ks_handle_p, (int) key, (char *) NS_INT, (void *) &tempVal, req_len, &outLen); + SuccessOrExit(err = MapKeyStorageStatus(status)); + val = tempVal; +#if (DEBUG_NVM > 0) + ChipLogProgress(DeviceLayer, "ReadConfigValue uint64_t = " ChipLogFormatX64, ChipLogValueX64(val)); +#endif + +exit: + return err; +} + +CHIP_ERROR NXPConfig::ReadConfigValueStr(Key key, char * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR err; + ks_error_t status; + uint32_t sizeToRead; + + sizeToRead = bufSize; + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_GetKeyInt(ks_handle_p, (int) key, (char *) NS_INT, (void *) buf, (int) bufSize, (int *) &sizeToRead); + SuccessOrExit(err = MapKeyStorageStatus(status)); + outLen = sizeToRead; +#if (DEBUG_NVM > 0) + ChipLogProgress(DeviceLayer, "ReadConfigValueStr bufSize = %u, lenRead = %u", bufSize, outLen); +#endif + +exit: + return err; +} + +CHIP_ERROR NXPConfig::ReadConfigValueBin(Key key, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + return ReadConfigValueStr(key, (char *) buf, bufSize, outLen); +} + +CHIP_ERROR NXPConfig::ReadConfigValueBin(const char * keyString, uint8_t * buf, size_t bufSize, size_t & outLen) +{ + CHIP_ERROR err; + ks_error_t status; + uint32_t sizeToRead; + + sizeToRead = bufSize; + VerifyOrExit(keyString != NULL, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_GetKeyString(ks_handle_p, (char *) keyString, strlen(keyString) + 1, (char *) NS_STR, (void *) buf, (int) bufSize, + (int *) &sizeToRead); // +1 to add end \0 char + SuccessOrExit(err = MapKeyStorageStatus(status)); + outLen = sizeToRead; +#if (DEBUG_NVM > 0) + ChipLogProgress(DeviceLayer, "ReadConfigValueStr lenRead = %u", outLen); +#endif + +exit: + return err; +} + +CHIP_ERROR NXPConfig::ReadConfigValueCounter(uint8_t counterIdx, uint32_t & val) +{ + Key key = kMinConfigKey_ChipCounter + counterIdx; + return ReadConfigValue(key, val); +} + +CHIP_ERROR NXPConfig::WriteConfigValue(Key key, bool val) +{ + CHIP_ERROR err; + ks_error_t status; + int valSize; + + valSize = sizeof(bool); + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_SetKeyInt(ks_handle_p, (int) key, (char *) NS_INT, (void *) &val, valSize); + SuccessOrExit(err = MapKeyStorageStatus(status)); + + DBG_PRINTF("WriteConfigValue: MT write \r\n"); + +#if (DEBUG_NVM > 0) + ChipLogProgress(DeviceLayer, "WriteConfigValue done"); +#endif + +#if (ENABLE_KEYS_STATS == 1) + keys_stats.writtenKeys_int++; + ChipLogProgress(DeviceLayer, "Data len: %u. Integer keys written until now: %u.", valSize, keys_stats.writtenKeys_int); +#endif + +exit: + return err; +} + +CHIP_ERROR NXPConfig::WriteConfigValue(Key key, uint32_t val) +{ + CHIP_ERROR err; + ks_error_t status; + int valSize; + + valSize = sizeof(uint32_t); + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_SetKeyInt(ks_handle_p, (int) key, (char *) NS_INT, (void *) &val, valSize); + SuccessOrExit(err = MapKeyStorageStatus(status)); + + DBG_PRINTF("WriteConfigValue: MT write \r\n"); + +#if (DEBUG_NVM > 0) + ChipLogProgress(DeviceLayer, "WriteConfigValue done"); +#endif + +#if (ENABLE_KEYS_STATS == 1) + keys_stats.writtenKeys_int++; + ChipLogProgress(DeviceLayer, "Data len: %u. Integer keys written until now: %u.", valSize, keys_stats.writtenKeys_int); +#endif + +exit: + return err; +} + +CHIP_ERROR NXPConfig::WriteConfigValue(Key key, uint64_t val) +{ + CHIP_ERROR err; + ks_error_t status; + int valSize; + + valSize = sizeof(uint64_t); + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_SetKeyInt(ks_handle_p, (int) key, (char *) NS_INT, (void *) &val, valSize); + SuccessOrExit(err = MapKeyStorageStatus(status)); + + DBG_PRINTF("WriteConfigValue64: MT write \r\n"); + +#if (DEBUG_NVM > 0) + ChipLogProgress(DeviceLayer, "WriteConfigValue done"); +#endif + +#if (ENABLE_KEYS_STATS == 1) + keys_stats.writtenKeys_int++; + ChipLogProgress(DeviceLayer, "Data len: %u. Integer keys written until now: %u.", valSize, keys_stats.writtenKeys_int); +#endif + +exit: + return err; +} + +CHIP_ERROR NXPConfig::WriteConfigValueStr(Key key, const char * str) +{ + return WriteConfigValueStr(key, str, (str != NULL) ? strlen(str) : 0); +} + +CHIP_ERROR NXPConfig::WriteConfigValueStr(Key key, const char * str, size_t strLen) +{ + CHIP_ERROR err; + ks_error_t status; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_SetKeyInt(ks_handle_p, (int) key, (char *) NS_INT, (void *) str, strLen); + SuccessOrExit(err = MapKeyStorageStatus(status)); + + DBG_PRINTF("WriteConfigValueStr: MT write \r\n"); + +#if (DEBUG_NVM > 0) + ChipLogProgress(DeviceLayer, "WriteConfigValue done"); +#endif + +#if (ENABLE_KEYS_STATS == 1) + keys_stats.writtenKeys_int++; + ChipLogProgress(DeviceLayer, "Data len: %u. Integer keys written until now: %u.", strLen, keys_stats.writtenKeys_int); +#endif + +exit: + return err; +} + +CHIP_ERROR NXPConfig::WriteConfigValueBin(Key key, const uint8_t * data, size_t dataLen) +{ + return WriteConfigValueStr(key, (char *) data, dataLen); +} + +CHIP_ERROR NXPConfig::WriteConfigValueBin(const char * keyString, const uint8_t * data, size_t dataLen) +{ + CHIP_ERROR err; + ks_error_t status; + int keyLen; + + keyLen = (int) strlen(keyString) + 1; // +1 to add end \0 char + VerifyOrExit(keyString != NULL, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_SetKeyString(ks_handle_p, (char *) keyString, keyLen, (char *) NS_STR, (void *) data, (int) dataLen); + SuccessOrExit(err = MapKeyStorageStatus(status)); + + DBG_PRINTF("WriteConfigValueBin: MT write \r\n"); + +#if (DEBUG_NVM > 0) + ChipLogProgress(DeviceLayer, "WriteConfigValue done"); +#endif + +#if (ENABLE_KEYS_STATS == 1) + keys_stats.writtenKeys_str++; + ChipLogProgress(DeviceLayer, "Data len: %u. Key len: %u. String keys written until now: %u.", dataLen, keyLen, + keys_stats.writtenKeys_str); +#endif + +exit: + return err; +} + +CHIP_ERROR NXPConfig::WriteConfigValueCounter(uint8_t counterIdx, uint32_t val) +{ + Key key = kMinConfigKey_ChipCounter + counterIdx; + return WriteConfigValue(key, val); +} + +CHIP_ERROR NXPConfig::ClearConfigValue(Key key) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ks_error_t status; + + VerifyOrExit(ValidConfigKey(key), err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_DeleteKeyInt(ks_handle_p, (int) key, (char *) NS_INT); + SuccessOrExit(err = MapKeyStorageStatus(status)); + + DBG_PRINTF("ClearConfigValue: MT write \r\n"); + +#if (ENABLE_KEYS_STATS == 1) + keys_stats.deletedKeys_int++; + ChipLogProgress(DeviceLayer, "Integer keys deleted until now: %u.", keys_stats.deletedKeys_int); +#endif + +exit: + return err; +} + +CHIP_ERROR NXPConfig::ClearConfigValue(const char * keyString) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + ks_error_t status; + int keyLen; + + keyLen = strlen(keyString) + 1; // +1 to add end \0 char + VerifyOrExit(keyString != NULL, err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND); // Verify key id. + status = KS_DeleteKeyString(ks_handle_p, (char *) keyString, keyLen, (char *) NS_STR); + SuccessOrExit(err = MapKeyStorageStatus(status)); + + DBG_PRINTF("WriteConfigValueBin: MT write \r\n"); + +#if (ENABLE_KEYS_STATS == 1) + keys_stats.deletedKeys_str++; + ChipLogProgress(DeviceLayer, "String keys deleted until now: %u.", keys_stats.deletedKeys_str); +#endif + +exit: + return err; +} + +bool NXPConfig::ConfigValueExists(Key key) +{ + ks_error_t status; + bool found; + void * readValue_p; + int outLen; + int bufSize; + + found = false; + readValue_p = NULL; + outLen = 0; + /* Max number of bytes read when getting a value */ + bufSize = 256; + + if (ValidConfigKey(key)) + { + /* Get the first occurence */ + status = KS_GetKeyInt(ks_handle_p, (int) key, (char *) NS_INT, readValue_p, bufSize, &outLen); + found = (status == KS_ERROR_NONE && outLen != 0); + } + return found; +} + +CHIP_ERROR NXPConfig::FactoryResetConfig(void) +{ + /*for (Key key = kMinConfigKey_ChipConfig; key <= kMaxConfigKey_ChipConfig; key++) + { + ClearConfigValue(key); + }*/ + + KS_Reset(ks_handle_p); + + DBG_PRINTF("FactoryResetConfig done\r\n"); + + return CHIP_NO_ERROR; +} + +bool NXPConfig::ValidConfigKey(Key key) +{ + // Returns true if the key is in the valid CHIP Config PDM key range. + + if ((key >= kMinConfigKey_ChipFactory) && (key <= kMaxConfigKey_KVS)) + { + return true; + } + + return false; +} + +CHIP_ERROR NXPConfig::MapKeyStorageStatus(ks_error_t ksStatus) +{ + CHIP_ERROR err; + + switch (ksStatus) + { + case KS_ERROR_NONE: + err = CHIP_NO_ERROR; + break; + case KS_ERROR_BUF_TOO_SMALL: + err = CHIP_ERROR_BUFFER_TOO_SMALL; + break; + default: /* KS_ERROR_KEY_NOT_FOUND */ + err = CHIP_DEVICE_ERROR_CONFIG_NOT_FOUND; + break; + } + + return err; +} + +void NXPConfig::RunConfigUnitTest(void) {} + +void NXPConfig::RunSystemIdleTask(void) +{ + if (isInitialized) + { + FC_Process(); + INFO_PRINTF("str mt write \r\n"); + } +} + +} // namespace Internal +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/common/NetworkCommissioningDriver.h b/src/platform/nxp/common/NetworkCommissioningDriver.h index 4e7c9f0292e47b..5757771476e057 100644 --- a/src/platform/nxp/common/NetworkCommissioningDriver.h +++ b/src/platform/nxp/common/NetworkCommissioningDriver.h @@ -72,6 +72,10 @@ class NXPWiFiDriver final : public WiFiDriver Status ReorderNetwork(ByteSpan networkId, uint8_t index, MutableCharSpan & outDebugText) override; void ConnectNetwork(ByteSpan networkId, ConnectCallback * callback) override; + /* Can be used to disconnect from WiFi network. + */ + int DisconnectNetwork(); + /* Returns the network SSID. User needs to allocate a buffer of size >= DeviceLayer::Internal::kMaxWiFiSSIDLength. * ssid - pointer to the returned SSID */ @@ -82,6 +86,9 @@ class NXPWiFiDriver final : public WiFiDriver */ Status GetNetworkPassword(char * credentials); + /* Returns all supported WiFi bands */ + uint32_t GetSupportedWiFiBandsMask() const override; + // WiFiDriver Status AddOrUpdateNetwork(ByteSpan ssid, ByteSpan credentials, MutableCharSpan & outDebugText, uint8_t & outNetworkIndex) override; diff --git a/src/platform/nxp/common/NetworkCommissioningWiFiDriver.cpp b/src/platform/nxp/common/NetworkCommissioningWiFiDriver.cpp index 7863b511b227c2..d697c75d1042d7 100644 --- a/src/platform/nxp/common/NetworkCommissioningWiFiDriver.cpp +++ b/src/platform/nxp/common/NetworkCommissioningWiFiDriver.cpp @@ -69,7 +69,7 @@ CHIP_ERROR NXPWiFiDriver::Init(NetworkStatusChangeCallback * networkStatusChange if (err != CHIP_NO_ERROR) { ChipLogProgress(DeviceLayer, "WiFi network SSID not retrieved from persisted storage: %" CHIP_ERROR_FORMAT, err.Format()); - return CHIP_NO_ERROR; + return err; } err = PersistedStorage::KeyValueStoreMgr().Get(kWiFiCredentialsKeyName, mSavedNetwork.credentials, @@ -78,7 +78,7 @@ CHIP_ERROR NXPWiFiDriver::Init(NetworkStatusChangeCallback * networkStatusChange { ChipLogProgress(DeviceLayer, "WiFi network credentials not retrieved from persisted storage: %" CHIP_ERROR_FORMAT, err.Format()); - return CHIP_NO_ERROR; + return err; } mSavedNetwork.credentialsLen = credentialsLen; @@ -90,7 +90,7 @@ CHIP_ERROR NXPWiFiDriver::Init(NetworkStatusChangeCallback * networkStatusChange mpStatusChangeCallback = networkStatusChangeCallback; // Connect to saved network - ConnectWiFiNetwork(mSavedNetwork.ssid, ssidLen, mSavedNetwork.credentials, credentialsLen); + err = ConnectWiFiNetwork(mSavedNetwork.ssid, ssidLen, mSavedNetwork.credentials, credentialsLen); return err; } @@ -193,6 +193,8 @@ CHIP_ERROR NXPWiFiDriver::ConnectWiFiNetwork(const char * ssid, uint8_t ssidLen, void NXPWiFiDriver::OnConnectWiFiNetwork(Status commissioningError, CharSpan debugText, int32_t connectStatus) { + CommitConfiguration(); + if (mpConnectCallback != nullptr) { mpConnectCallback->OnResult(commissioningError, debugText, connectStatus); @@ -236,6 +238,29 @@ void NXPWiFiDriver::ConnectNetwork(ByteSpan networkId, ConnectCallback * callbac } } +int NXPWiFiDriver::DisconnectNetwork(void) +{ + int ret = 0; + + if (ConnectivityMgrImpl().IsWiFiStationConnected()) + { + ChipLogProgress(NetworkProvisioning, "Disconnecting from WiFi network."); + + ret = wlan_disconnect(); + + if (ret != WM_SUCCESS) + { + ChipLogError(NetworkProvisioning, "Failed to disconnect from network with error: %u", (uint8_t) ret); + } + } + else + { + ChipLogError(NetworkProvisioning, "Error: WiFi not connected!"); + } + + return ret; +} + CHIP_ERROR NXPWiFiDriver::StartScanWiFiNetworks(ByteSpan ssid) { wlan_scan_params_v2_t wlan_scan_param; @@ -385,6 +410,15 @@ void NXPWiFiDriver::ScanNetworks(ByteSpan ssid, WiFiDriver::ScanCallback * callb } } +uint32_t NXPWiFiDriver::GetSupportedWiFiBandsMask() const +{ + uint32_t bands = static_cast(1UL << chip::to_underlying(WiFiBandEnum::k2g4)); +#ifdef CONFIG_5GHz_SUPPORT + bands |= (1UL << chip::to_underlying(WiFiBandEnum::k5g)); +#endif + return bands; +} + static CHIP_ERROR GetConnectedNetwork(Network & network) { struct wlan_network wlan_network; diff --git a/src/platform/nxp/common/factory_data/FactoryDataProvider.cpp b/src/platform/nxp/common/factory_data/FactoryDataProvider.cpp index bdaaaae1d120b8..b60d7e49c0ad81 100644 --- a/src/platform/nxp/common/factory_data/FactoryDataProvider.cpp +++ b/src/platform/nxp/common/factory_data/FactoryDataProvider.cpp @@ -21,20 +21,6 @@ #include #include -#ifndef FACTORY_DATA_PROVIDER_ENABLE_TESTS -#define FACTORY_DATA_PROVIDER_RUN_TESTS 0 -#endif - -#if FACTORY_DATA_PROVIDER_RUN_TESTS -#include -#include -#include -#include -#include -#include -#include -#endif - #include "FactoryDataProvider.h" #include @@ -315,42 +301,28 @@ CHIP_ERROR FactoryDataProvider::GetRotatingDeviceIdUniqueId(MutableByteSpan & un return err; } -void FactoryDataProvider::FactoryDataProviderRunTests(void) +CHIP_ERROR FactoryDataProvider::GetProductFinish(app::Clusters::BasicInformation::ProductFinishEnum * finish) { -#if FACTORY_DATA_PROVIDER_RUN_TESTS - static const ByteSpan kExpectedDacPublicKey = DevelopmentCerts::kDacPublicKey; - constexpr uint8_t kExampleDigest[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x10, 0x11, 0x12, - 0x13, 0x14, 0x15, 0x16, 0x17, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, - 0x26, 0x27, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37 }; - - // Sign using the example attestation private key - P256ECDSASignature da_signature; - MutableByteSpan out_sig_span(da_signature.Bytes(), da_signature.Capacity()); - CHIP_ERROR err = SignWithDeviceAttestationKey(ByteSpan{ kExampleDigest }, out_sig_span); - assert(err == CHIP_NO_ERROR); - - assert(out_sig_span.size() == kP256_ECDSA_Signature_Length_Raw); - da_signature.SetLength(out_sig_span.size()); - - // Get DAC from the provider - uint8_t dac_cert_buf[kMaxDERCertLength]; - MutableByteSpan dac_cert_span(dac_cert_buf); - - memset(dac_cert_span.data(), 0, dac_cert_span.size()); - err = GetDeviceAttestationCert(dac_cert_span); - assert(err == CHIP_NO_ERROR); - - // Extract public key from DAC, prior to signature verification - P256PublicKey dac_public_key; - err = ExtractPubkeyFromX509Cert(dac_cert_span, dac_public_key); - assert(err == CHIP_NO_ERROR); - assert(dac_public_key.Length() == kExpectedDacPublicKey.size()); - assert(0 == memcmp(dac_public_key.ConstBytes(), kExpectedDacPublicKey.data(), kExpectedDacPublicKey.size())); - - // Verify round trip signature - err = dac_public_key.ECDSA_validate_msg_signature(&kExampleDigest[0], sizeof(kExampleDigest), da_signature); - assert(err == CHIP_NO_ERROR); -#endif + uint8_t productFinish; + uint16_t length = 0; + auto err = SearchForId(FactoryDataId::kProductFinish, &productFinish, sizeof(productFinish), length); + ReturnErrorCodeIf(err != CHIP_NO_ERROR, CHIP_ERROR_NOT_IMPLEMENTED); + + *finish = static_cast(productFinish); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR FactoryDataProvider::GetProductPrimaryColor(app::Clusters::BasicInformation::ColorEnum * primaryColor) +{ + uint8_t color; + uint16_t length = 0; + auto err = SearchForId(FactoryDataId::kProductPrimaryColor, &color, sizeof(color), length); + ReturnErrorCodeIf(err != CHIP_NO_ERROR, CHIP_ERROR_NOT_IMPLEMENTED); + + *primaryColor = static_cast(color); + + return CHIP_NO_ERROR; } } // namespace DeviceLayer diff --git a/src/platform/nxp/common/factory_data/FactoryDataProvider.h b/src/platform/nxp/common/factory_data/FactoryDataProvider.h index 47f85fa6ccc4b2..b06089270a7355 100644 --- a/src/platform/nxp/common/factory_data/FactoryDataProvider.h +++ b/src/platform/nxp/common/factory_data/FactoryDataProvider.h @@ -70,6 +70,8 @@ class FactoryDataProvider : public chip::Credentials::DeviceAttestationCredentia kPartNumber, kProductURL, kProductLabel, + kProductFinish, + kProductPrimaryColor, kMaxId }; @@ -105,8 +107,8 @@ class FactoryDataProvider : public chip::Credentials::DeviceAttestationCredentia CHIP_ERROR GetHardwareVersion(uint16_t & hardwareVersion) override; CHIP_ERROR GetHardwareVersionString(char * buf, size_t bufSize) override; CHIP_ERROR GetRotatingDeviceIdUniqueId(MutableByteSpan & uniqueIdSpan) override; - - void FactoryDataProviderRunTests(void); + CHIP_ERROR GetProductFinish(app::Clusters::BasicInformation::ProductFinishEnum * finish) override; + CHIP_ERROR GetProductPrimaryColor(app::Clusters::BasicInformation::ColorEnum * primaryColor) override; protected: // Methods to be implemented by impl class delegate. diff --git a/src/platform/nxp/k32w/common/BLEManagerCommon.cpp b/src/platform/nxp/k32w/common/BLEManagerCommon.cpp index 40f9bc5c7d7abf..b532ddef7f207e 100644 --- a/src/platform/nxp/k32w/common/BLEManagerCommon.cpp +++ b/src/platform/nxp/k32w/common/BLEManagerCommon.cpp @@ -135,7 +135,7 @@ const ChipBleUUID ChipUUID_CHIPoBLEChar_TX = { { 0x18, 0xEE, 0x2E, 0xF5, 0x26, 0 static bool bleAppStopInProgress; #endif -BLEManagerCommon * sImplInstance; +BLEManagerCommon * sImplInstance = nullptr; } // namespace @@ -1085,7 +1085,7 @@ void BLEManagerCommon::blekw_generic_cb(gapGenericEvent_t * pGenericEvent) /* Call BLE Conn Manager */ BleConnManager_GenericEvent(pGenericEvent); - if (sImplInstance->callbackDelegate.gapCallback) + if (sImplInstance && sImplInstance->callbackDelegate.gapCallback) { sImplInstance->callbackDelegate.gapCallback(pGenericEvent); } @@ -1240,7 +1240,7 @@ void BLEManagerCommon::blekw_stop_connection_timeout(void) void BLEManagerCommon::blekw_gatt_server_cb(deviceId_t deviceId, gattServerEvent_t * pServerEvent) { - if (sImplInstance->callbackDelegate.gattCallback) + if (sImplInstance && sImplInstance->callbackDelegate.gattCallback) { sImplInstance->callbackDelegate.gattCallback(deviceId, pServerEvent); } diff --git a/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h b/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h index 33c68807ca7af9..bcb9ecdd82d58c 100644 --- a/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h +++ b/src/platform/nxp/k32w/common/CHIPDevicePlatformRamStorageConfig.h @@ -21,6 +21,8 @@ * Configuration of RAM storage metadata: key IDs and NVM IDs. */ +#pragma once + /* Base key IDs used when creating new keys for RAM storage instances. */ /** * @def kKeyId_Factory @@ -111,11 +113,35 @@ * KVS buffer can become quite big, so this PDM * id is used as base id for subsequent PDM ids * used to store data in chunks of PDM page size. + * This will use the extended search feature, so + * subsequent PDM ids should not be used. */ #ifndef kNvmId_KvsValues #define kNvmId_KvsValues (uint16_t) 0x6001 #endif +/** + * @def kNvmId_KvsSubscription + * + * PDM ID used for KVS subscription RAM storage. + * It will store both keys and values for those keys. + */ +#ifndef kNvmId_KvsSubscription +#define kNvmId_KvsSubscription (uint16_t) 0x6100 +#endif + +/** + * @def kNvmId_KvsGroups + * + * PDM ID used for KVS groups RAM storage. + * It will store both keys and values for those keys. + * This will use the extended search feature, so + * subsequent PDM ids should not be used. + */ +#ifndef kNvmId_KvsGroups +#define kNvmId_KvsGroups (uint16_t) 0x6200 +#endif + /** * @def kNvmId_OTConfigData * @@ -145,14 +171,3 @@ #define kNvmId_FactoryDataBackup (uint16_t) 0x7000 #endif #endif // CONFIG_CHIP_LOAD_REAL_FACTORY_DATA - -/** - * @def kKVS_RamBufferSize - * - * Size of KVS values RAM storage buffer. - * This value is empirical, based on some minimal resource requirements tests. - * Applications should overwrite this value if necessary. - */ -#ifndef kKVS_RamBufferSize -#define kKVS_RamBufferSize (12 * 1024) -#endif diff --git a/src/platform/nxp/k32w/common/FactoryDataProvider.cpp b/src/platform/nxp/k32w/common/FactoryDataProvider.cpp index 1770cf22c659f0..dd79b020c881ea 100644 --- a/src/platform/nxp/k32w/common/FactoryDataProvider.cpp +++ b/src/platform/nxp/k32w/common/FactoryDataProvider.cpp @@ -39,6 +39,10 @@ namespace DeviceLayer { static constexpr size_t kSpake2pSerializedVerifier_MaxBase64Len = BASE64_ENCODED_LEN(chip::Crypto::kSpake2p_VerifierSerialized_Length) + 1; static constexpr size_t kSpake2pSalt_MaxBase64Len = BASE64_ENCODED_LEN(chip::Crypto::kSpake2p_Max_PBKDF_Salt_Length) + 1; +/* Secure subsystem private key blob size is 32 + 24 = 56. + * DAC private key may be used to store an SSS exported blob instead of the private key. + */ +static constexpr size_t kDacPrivateKey_MaxLen = Crypto::kP256_PrivateKey_Length + 24; uint32_t FactoryDataProvider::kFactoryDataStart = (uint32_t) __MATTER_FACTORY_DATA_START; uint32_t FactoryDataProvider::kFactoryDataSize = (uint32_t) __MATTER_FACTORY_DATA_SIZE; diff --git a/src/platform/nxp/k32w/common/FactoryDataProvider.h b/src/platform/nxp/k32w/common/FactoryDataProvider.h index 2bad3e7934482f..03ccf370f7641c 100644 --- a/src/platform/nxp/k32w/common/FactoryDataProvider.h +++ b/src/platform/nxp/k32w/common/FactoryDataProvider.h @@ -25,6 +25,8 @@ #include "CHIPPlatformConfig.h" +#include + /* Grab symbol for the base address from the linker file. */ extern uint32_t __MATTER_FACTORY_DATA_START[]; extern uint32_t __MATTER_FACTORY_DATA_SIZE[]; diff --git a/src/platform/nxp/k32w/common/K32W_OTA_README.md b/src/platform/nxp/k32w/common/K32W_OTA_README.md index 63079e0ce3c81e..0c9715b4610ff8 100644 --- a/src/platform/nxp/k32w/common/K32W_OTA_README.md +++ b/src/platform/nxp/k32w/common/K32W_OTA_README.md @@ -48,7 +48,6 @@ public: CHIP_ERROR Process(ByteSpan & block); void RegisterDescriptorCallback(ProcessDescriptor callback); - protected: virtual CHIP_ERROR ProcessInternal(ByteSpan & block) = 0; }; diff --git a/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp b/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp index 591d082ea992f7..c927ce65675035 100644 --- a/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp +++ b/src/platform/nxp/k32w/common/OTAImageProcessorImpl.cpp @@ -179,11 +179,11 @@ CHIP_ERROR OTAImageProcessorImpl::SelectProcessor(ByteSpan & block) auto pair = mProcessorMap.find(header.tag); if (pair == mProcessorMap.end()) { - ChipLogError(SoftwareUpdate, "There is no registered processor for tag: %lu", header.tag); + ChipLogError(SoftwareUpdate, "There is no registered processor for tag: %" PRIu32, header.tag); return CHIP_OTA_PROCESSOR_NOT_REGISTERED; } - ChipLogDetail(SoftwareUpdate, "Selected processor with tag: %lu", pair->first); + ChipLogDetail(SoftwareUpdate, "Selected processor with tag: %ld", pair->first); mCurrentProcessor = pair->second; mCurrentProcessor->SetLength(header.length); mCurrentProcessor->SetWasSelected(true); @@ -196,7 +196,7 @@ CHIP_ERROR OTAImageProcessorImpl::RegisterProcessor(uint32_t tag, OTATlvProcesso auto pair = mProcessorMap.find(tag); if (pair != mProcessorMap.end()) { - ChipLogError(SoftwareUpdate, "A processor for tag %lu is already registered.", tag); + ChipLogError(SoftwareUpdate, "A processor for tag %" PRIu32 " is already registered.", tag); return CHIP_OTA_PROCESSOR_ALREADY_REGISTERED; } @@ -298,8 +298,8 @@ CHIP_ERROR OTAImageProcessorImpl::ConfirmCurrentImage() ReturnErrorOnFailure(DeviceLayer::ConfigurationMgr().GetSoftwareVersion(currentVersion)); if (currentVersion != targetVersion) { - ChipLogError(SoftwareUpdate, "Current sw version %lu is different than the expected sw version = %lu", currentVersion, - targetVersion); + ChipLogError(SoftwareUpdate, "Current sw version %" PRIu32 " is different than the expected sw version = %" PRIu32, + currentVersion, targetVersion); return CHIP_ERROR_INCORRECT_STATE; } @@ -384,17 +384,14 @@ void OTAImageProcessorImpl::HandleApply(intptr_t context) imageProcessor->mAccumulator.Clear(); ConfigurationManagerImpl().StoreSoftwareUpdateCompleted(); + PlatformMgr().HandleServerShuttingDown(); // Set the necessary information to inform the SSBL that a new image is available // and trigger the actual device reboot after some time, to take into account // queued actions, e.g. sending events to a subscription SystemLayer().StartTimer( chip::System::Clock::Milliseconds32(CHIP_DEVICE_LAYER_OTA_REBOOT_DELAY), - [](chip::System::Layer *, void *) { - PlatformMgr().HandleServerShuttingDown(); - OtaHookReset(); - }, - nullptr); + [](chip::System::Layer *, void *) { OtaHookReset(); }, nullptr); } CHIP_ERROR OTAImageProcessorImpl::ReleaseBlock() diff --git a/src/platform/nxp/k32w/k32w0/BUILD.gn b/src/platform/nxp/k32w/k32w0/BUILD.gn index a58609f28ef539..9d1ef45db9fdb5 100644 --- a/src/platform/nxp/k32w/k32w0/BUILD.gn +++ b/src/platform/nxp/k32w/k32w0/BUILD.gn @@ -13,18 +13,19 @@ # limitations under the License. import("//build_overrides/chip.gni") -import("//build_overrides/k32w0_sdk.gni") +import("//build_overrides/nxp_sdk.gni") import("${chip_root}/src/platform/device.gni") -import("${k32w0_sdk_build_root}/k32w0_sdk.gni") +import("${nxp_sdk_build_root}/${nxp_sdk_name}/${nxp_sdk_name}.gni") -assert(chip_device_platform == "k32w0") +assert(chip_device_platform == "nxp") +assert(nxp_platform == "k32w/k32w0") if (chip_enable_openthread) { import("//build_overrides/openthread.gni") } -static_library("k32w0") { +static_library("nxp_platform") { sources = [ "../../../SingletonConfigurationManager.cpp", "../common/BLEManagerCommon.cpp", @@ -60,6 +61,7 @@ static_library("k32w0") { "${chip_root}/src/credentials/examples/DeviceAttestationCredsExample.h", "${chip_root}/src/credentials/examples/ExampleDACs.h", "${chip_root}/src/credentials/examples/ExamplePAI.h", + "${chip_root}/src/platform/nxp/k32w/k32w0/BLEManagerImpl.h", ] if (chip_with_factory_data == 1) { @@ -119,8 +121,8 @@ static_library("k32w0") { sources += [ "${chip_root}/src/platform/nxp/k32w/k32w0/crypto/CHIPCryptoPALNXPUltrafastP256.cpp" ] public_deps += [ - "${chip_root}/third_party/nxp/k32w0_sdk:k32w0_sdk", "${mbedtls_root}:mbedtls", + "${nxp_sdk_build_root}/${nxp_sdk_name}:nxp_sdk", ] } } diff --git a/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h b/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h index a584e0c79afbbb..faa283ffbe41dc 100644 --- a/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h +++ b/src/platform/nxp/k32w/k32w0/CHIPDevicePlatformConfig.h @@ -115,6 +115,25 @@ #define CHIP_DEVICE_LAYER_OTA_REBOOT_DELAY 3000 #endif // CHIP_DEVICE_LAYER_OTA_REBOOT_DELAY +/** + * @def CONFIG_CHIP_K32W0_KVS_MOVE_KEYS_TO_SPECIFIC_STORAGE + * + * When enabled, the application can opt to move some keys from + * current KVS storage to a dedicated storage, based on the key name. + * + * Currently, the mechanism supports moving keys and values for: + * - subscriptions + * - groups + * + * Note: the system is meant to ensure backwards compatibility. This should only + * be used once, but, if this feature is still enabled after the first conversion, + * the corresponding keys will not be found in KVS default storage. This does + * not affect the functionality, but it introduces a delay in the initialization. + */ +#ifndef CONFIG_CHIP_K32W0_KVS_MOVE_KEYS_TO_SPECIFIC_STORAGE +#define CONFIG_CHIP_K32W0_KVS_MOVE_KEYS_TO_SPECIFIC_STORAGE 0 +#endif // CONFIG_CHIP_K32W0_KVS_MOVE_KEYS_TO_SPECIFIC_STORAGE + /** * @def CONFIG_CHIP_K32W0_OTA_FACTORY_DATA_PROCESSOR * @@ -144,7 +163,7 @@ #endif // CHIP_DEVICE_CONFIG_THREAD_TASK_STACK_SIZE // Max size of event queue -#define CHIP_DEVICE_CONFIG_MAX_EVENT_QUEUE_SIZE 75 +#define CHIP_DEVICE_CONFIG_MAX_EVENT_QUEUE_SIZE 25 #ifndef CHIP_DEVICE_CONFIG_BLE_APP_TASK_NAME #define CHIP_DEVICE_CONFIG_BLE_APP_TASK_NAME "BLE App Task" diff --git a/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.cpp b/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.cpp index 0cf81fa9bf1749..4d378cb01cbe1f 100644 --- a/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.cpp +++ b/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.cpp @@ -37,11 +37,54 @@ namespace PersistedStorage { constexpr size_t kMaxNumberOfKeys = 200; constexpr size_t kMaxKeyValueBytes = 255; -Internal::RamStorage KeyValueStoreManagerImpl::sKeysStorage = { kNvmId_KvsKeys }; -Internal::RamStorage KeyValueStoreManagerImpl::sValuesStorage = { kNvmId_KvsValues }; +Internal::RamStorage KeyValueStoreManagerImpl::sKeysStorage = { kNvmId_KvsKeys }; +Internal::RamStorage KeyValueStoreManagerImpl::sValuesStorage = { kNvmId_KvsValues }; +Internal::RamStorage KeyValueStoreManagerImpl::sSubscriptionStorage = { kNvmId_KvsSubscription }; +Internal::RamStorage KeyValueStoreManagerImpl::sGroupsStorage = { kNvmId_KvsGroups }; KeyValueStoreManagerImpl KeyValueStoreManagerImpl::sInstance; +#if CONFIG_CHIP_K32W0_KVS_MOVE_KEYS_TO_SPECIFIC_STORAGE +static CHIP_ERROR MoveKeysAndValues(); +#endif // CONFIG_CHIP_K32W0_KVS_MOVE_KEYS_TO_SPECIFIC_STORAGE + +static inline bool IsKeyRelatedToGroups(const char * key) +{ + std::string _key(key); + if (_key.find("f/") != 0) + return false; + + return (_key.find("/g") != std::string::npos) || (_key.find("/k/") != std::string::npos); +} + +static inline bool IsKeyRelatedToSubscriptions(const char * key) +{ + std::string _key(key); + return _key.find("g/su") == 0; +} + +static Internal::RamStorage * GetValStorage(const char * key) +{ + Internal::RamStorage * storage = nullptr; + + storage = IsKeyRelatedToSubscriptions(key) ? &KeyValueStoreManagerImpl::sSubscriptionStorage + : &KeyValueStoreManagerImpl::sValuesStorage; + storage = IsKeyRelatedToGroups(key) ? &KeyValueStoreManagerImpl::sGroupsStorage : storage; + + return storage; +} + +static Internal::RamStorage * GetKeyStorage(const char * key) +{ + Internal::RamStorage * storage = nullptr; + + storage = IsKeyRelatedToSubscriptions(key) ? &KeyValueStoreManagerImpl::sSubscriptionStorage + : &KeyValueStoreManagerImpl::sKeysStorage; + storage = IsKeyRelatedToGroups(key) ? &KeyValueStoreManagerImpl::sGroupsStorage : storage; + + return storage; +} + uint16_t GetStringKeyId(const char * key, uint16_t * freeId) { CHIP_ERROR err = CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; @@ -54,7 +97,7 @@ uint16_t GetStringKeyId(const char * key, uint16_t * freeId) { uint16_t keyStringSize = kMaxKeyValueBytes; pdmInternalId = Internal::RamStorageKey::GetInternalId(kKeyId_KvsKeys, keyId); - err = KeyValueStoreManagerImpl::sKeysStorage.Read(pdmInternalId, 0, (uint8_t *) keyString, &keyStringSize); + err = GetKeyStorage(key)->Read(pdmInternalId, 0, (uint8_t *) keyString, &keyStringSize); if (err == CHIP_NO_ERROR) { @@ -76,18 +119,37 @@ uint16_t GetStringKeyId(const char * key, uint16_t * freeId) CHIP_ERROR KeyValueStoreManagerImpl::Init() { CHIP_ERROR err = CHIP_NO_ERROR; - err = sKeysStorage.Init(Internal::RamStorage::kRamBufferInitialSize); + + err = sKeysStorage.Init(Internal::RamStorage::kRamBufferInitialSize); if (err != CHIP_NO_ERROR) { ChipLogProgress(DeviceLayer, "Cannot init KVS keys storage with id: %d. Error: %s", kNvmId_KvsKeys, ErrorStr(err)); } - // Set values storage to a big RAM buffer size as a temporary fix for TC-RR-1.1. - err = sValuesStorage.Init(kKVS_RamBufferSize, true); + + err = sValuesStorage.Init(Internal::RamStorage::kRamBufferInitialSize, true); if (err != CHIP_NO_ERROR) { ChipLogProgress(DeviceLayer, "Cannot init KVS values storage with id: %d. Error: %s", kNvmId_KvsValues, ErrorStr(err)); } + err = sSubscriptionStorage.Init(Internal::RamStorage::kRamBufferInitialSize); + if (err != CHIP_NO_ERROR) + { + ChipLogProgress(DeviceLayer, "Cannot init KVS subscription storage with id: %d. Error: %s", kNvmId_KvsSubscription, + ErrorStr(err)); + } + + err = sGroupsStorage.Init(Internal::RamStorage::kRamBufferInitialSize, true); + if (err != CHIP_NO_ERROR) + { + ChipLogProgress(DeviceLayer, "Cannot init KVS groups storage with id: %d. Error: %s", kNvmId_KvsGroups, ErrorStr(err)); + } + +#if CONFIG_CHIP_K32W0_KVS_MOVE_KEYS_TO_SPECIFIC_STORAGE + ChipLogProgress(DeviceLayer, "Moving some keys to dedicated storage"); + MoveKeysAndValues(); +#endif /* CONFIG_CHIP_K32W0_KVS_MOVE_KEYS_TO_SPECIFIC_STORAGE */ + return err; } @@ -105,10 +167,10 @@ CHIP_ERROR KeyValueStoreManagerImpl::_Get(const char * key, void * value, size_t if (keyId < kMaxNumberOfKeys) { - // This is the ID of the actual data + /* Use kKeyId_KvsValues as base key id for all keys. */ pdmInternalId = Internal::RamStorageKey::GetInternalId(kKeyId_KvsValues, keyId); ChipLogProgress(DeviceLayer, "KVS, get the value of Matter key [%s] with PDM id: %i", key, pdmInternalId); - err = KeyValueStoreManagerImpl::sValuesStorage.Read(pdmInternalId, 0, (uint8_t *) value, &valueSize); + err = GetValStorage(key)->Read(pdmInternalId, 0, (uint8_t *) value, &valueSize); *read_bytes_size = valueSize; } else @@ -140,11 +202,11 @@ CHIP_ERROR KeyValueStoreManagerImpl::_Put(const char * key, const void * value, keyId = freeKeyId; } + /* Use kKeyId_KvsValues as base key id for all keys. */ pdmInternalId = Internal::RamStorageKey::GetInternalId(kKeyId_KvsValues, keyId); ChipLogProgress(DeviceLayer, "KVS, save in flash the value of the Matter key [%s] with PDM id: %i", key, pdmInternalId); - err = sValuesStorage.Write(pdmInternalId, (uint8_t *) value, value_size); - + err = GetValStorage(key)->Write(pdmInternalId, (uint8_t *) value, value_size); /* save the 'key' in flash such that it can be retrieved later on */ if (err == CHIP_NO_ERROR) { @@ -154,7 +216,7 @@ CHIP_ERROR KeyValueStoreManagerImpl::_Put(const char * key, const void * value, ChipLogProgress(DeviceLayer, "KVS, save in flash the Matter key [%s] with PDM id: %i and length %d", key, pdmInternalId, strlen(key) + 1); - err = sKeysStorage.Write(pdmInternalId, (uint8_t *) key, strlen(key) + 1); + err = GetKeyStorage(key)->Write(pdmInternalId, (uint8_t *) key, strlen(key) + 1); if (err != CHIP_NO_ERROR) { ChipLogProgress(DeviceLayer, "KVS, Error while saving in flash the Matter key [%s] with PDM id: %i", key, @@ -188,16 +250,17 @@ CHIP_ERROR KeyValueStoreManagerImpl::_Delete(const char * key) pdmInternalId = Internal::RamStorageKey::GetInternalId(kKeyId_KvsKeys, keyId); ChipLogProgress(DeviceLayer, "KVS, delete from flash the Matter key [%s] with PDM id: %i", key, pdmInternalId); - err = sKeysStorage.Delete(pdmInternalId, -1); + err = GetKeyStorage(key)->Delete(pdmInternalId, -1); /* also delete the 'key string' from flash */ if (err == CHIP_NO_ERROR) { + /* Use kKeyId_KvsValues as base key id for all keys. */ pdmInternalId = Internal::RamStorageKey::GetInternalId(kKeyId_KvsValues, keyId); ChipLogProgress(DeviceLayer, "KVS, delete from flash the value of the Matter key [%s] with PDM id: %i", key, pdmInternalId); - err = sValuesStorage.Delete(pdmInternalId, -1); + err = GetValStorage(key)->Delete(pdmInternalId, -1); if (err != CHIP_NO_ERROR) { ChipLogProgress(DeviceLayer, @@ -220,6 +283,8 @@ void KeyValueStoreManagerImpl::FactoryResetStorage(void) { sKeysStorage.OnFactoryReset(); sValuesStorage.OnFactoryReset(); + sSubscriptionStorage.OnFactoryReset(); + sGroupsStorage.OnFactoryReset(); } void KeyValueStoreManagerImpl::ConvertError(CHIP_ERROR & err) @@ -230,6 +295,54 @@ void KeyValueStoreManagerImpl::ConvertError(CHIP_ERROR & err) } } +#if CONFIG_CHIP_K32W0_KVS_MOVE_KEYS_TO_SPECIFIC_STORAGE +static CHIP_ERROR MoveToDedicatedStorage(const char * key, uint8_t * buffer, uint16_t len, uint16_t keyId, uint16_t valId) +{ + ReturnErrorOnFailure(GetKeyStorage(key)->Write(keyId, (uint8_t *) key, strlen(key) + 1)); + + ReturnErrorOnFailure(KeyValueStoreManagerImpl::sValuesStorage.Read(valId, 0, buffer, &len)); + ReturnErrorOnFailure(GetValStorage(key)->Write(valId, buffer, len)); + + ReturnErrorOnFailure(KeyValueStoreManagerImpl::sKeysStorage.Delete(keyId, -1)); + ReturnErrorOnFailure(KeyValueStoreManagerImpl::sValuesStorage.Delete(valId, -1)); + + return CHIP_NO_ERROR; +} + +static CHIP_ERROR MoveKeysAndValues() +{ + uint16_t len = 1024; // Should be enough for Matter keys + uint16_t keyId = 0; + uint16_t valId = 0; + uint16_t keySize = kMaxKeyValueBytes; + char key[kMaxKeyValueBytes] = { 0 }; + Platform::ScopedMemoryBuffer buffer; + + buffer.Calloc(len); + ReturnErrorCodeIf(buffer.Get() == nullptr, CHIP_ERROR_NO_MEMORY); + + for (uint8_t id = 0; id < kMaxNumberOfKeys; id++) + { + keySize = kMaxKeyValueBytes; + keyId = Internal::RamStorageKey::GetInternalId(kKeyId_KvsKeys, id); + valId = Internal::RamStorageKey::GetInternalId(kKeyId_KvsValues, id); + + auto err = KeyValueStoreManagerImpl::sKeysStorage.Read(keyId, 0, (uint8_t *) key, &keySize); + if (err == CHIP_NO_ERROR) + { + if (!IsKeyRelatedToGroups(key) && !IsKeyRelatedToSubscriptions(key)) + continue; + + err = MoveToDedicatedStorage(key, buffer.Get(), len, keyId, valId); + VerifyOrDo(err != CHIP_NO_ERROR, ChipLogProgress(DeviceLayer, "Key [%s] was moved successfully", key)); + VerifyOrDo(err == CHIP_NO_ERROR, ChipLogProgress(DeviceLayer, "Error in moving key [%s] to dedicated storage", key)); + } + } + + return CHIP_NO_ERROR; +} +#endif // CONFIG_CHIP_K32W0_KVS_MOVE_KEYS_TO_SPECIFIC_STORAGE + } // namespace PersistedStorage } // namespace DeviceLayer } // namespace chip diff --git a/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.h b/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.h index a83a29b8a2e3ef..718b92df6beb39 100644 --- a/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.h +++ b/src/platform/nxp/k32w/k32w0/KeyValueStoreManagerImpl.h @@ -37,6 +37,10 @@ class KeyValueStoreManagerImpl final : public KeyValueStoreManager static Internal::RamStorage sKeysStorage; /* Storage for KVS values. Cleared during factory reset. */ static Internal::RamStorage sValuesStorage; + /* Storage for KVS subscription keys and values. Cleared during factory reset. */ + static Internal::RamStorage sSubscriptionStorage; + /* Storage for KVS groups keys and values. Cleared during factory reset. */ + static Internal::RamStorage sGroupsStorage; // Allow the KeyValueStoreManager interface class to delegate method calls to // the implementation methods provided by this class. diff --git a/src/platform/nxp/k32w/k32w0/Logging.cpp b/src/platform/nxp/k32w/k32w0/Logging.cpp index 537ceed1181694..5fc5b64d4404d9 100644 --- a/src/platform/nxp/k32w/k32w0/Logging.cpp +++ b/src/platform/nxp/k32w/k32w0/Logging.cpp @@ -27,7 +27,6 @@ static constexpr uint8_t category_max_len_bytes = 3; #include #endif // CHIP_DEVICE_CONFIG_ENABLE_THREAD -static bool isLogInitialized; extern "C" uint32_t otPlatAlarmMilliGetNow(void); namespace chip { @@ -105,12 +104,6 @@ void ENFORCE_FORMAT(1, 0) GenericLog(const char * format, va_list arg, const cha char formattedMsg[CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE - 1] = { 0 }; size_t prefixLen, writtenLen; - if (!isLogInitialized) - { - isLogInitialized = true; - otPlatUartEnable(); - } - /* Prefix is composed of [Time Reference][Debug String][Module Name String] */ FillPrefix(formattedMsg, CHIP_CONFIG_LOG_MESSAGE_MAX_SIZE - 1, module, category); prefixLen = strlen(formattedMsg); diff --git a/src/platform/nxp/k32w/k32w0/ThreadStackManagerImpl.cpp b/src/platform/nxp/k32w/k32w0/ThreadStackManagerImpl.cpp index 0435d168a99305..c74c2a9af4d907 100644 --- a/src/platform/nxp/k32w/k32w0/ThreadStackManagerImpl.cpp +++ b/src/platform/nxp/k32w/k32w0/ThreadStackManagerImpl.cpp @@ -72,10 +72,8 @@ void ThreadStackManagerImpl::ProcessThreadActivity() * by doing this, we avoid allocating a new stack for short-lived * BLE processing (e.g.: only during Matter commissioning) */ -#if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE auto * bleManager = &chip::DeviceLayer::Internal::BLEMgrImpl(); bleManager->DoBleProcessing(); -#endif #if defined(chip_with_low_power) && (chip_with_low_power == 1) if (isThreadInitialized()) diff --git a/src/platform/nxp/k32w/k32w0/args.gni b/src/platform/nxp/k32w/k32w0/args.gni index 59a41ae6931585..404daaa3e916db 100644 --- a/src/platform/nxp/k32w/k32w0/args.gni +++ b/src/platform/nxp/k32w/k32w0/args.gni @@ -13,10 +13,14 @@ # limitations under the License. import("//build_overrides/chip.gni") -import("//build_overrides/k32w0_sdk.gni") +import("//build_overrides/nxp_sdk.gni") import("//build_overrides/openthread.gni") -chip_device_platform = "k32w0" +nxp_platform = "k32w/k32w0" +nxp_sdk_name = "k32w0_sdk" +nxp_device_layer = "nxp/${nxp_platform}" +nxp_use_lwip = false +nxp_use_mbedtls_port = false if (getenv("NXP_K32W0_SDK_ROOT") == "") { k32w0_sdk_root = "${chip_root}/third_party/nxp/k32w0_sdk/repo/core" @@ -24,7 +28,21 @@ if (getenv("NXP_K32W0_SDK_ROOT") == "") { k32w0_sdk_root = getenv("NXP_K32W0_SDK_ROOT") } -lwip_platform = "k32w0" +# ARM architecture flags will be set based on NXP board. +arm_platform_config = "${nxp_sdk_build_root}/${nxp_sdk_name}/nxp_arm.gni" + +chip_device_platform = "nxp" + +chip_device_project_config_include = "" +chip_project_config_include = "" +chip_inet_project_config_include = "" +chip_system_project_config_include = "" +chip_ble_project_config_include = "" +chip_project_config_include_dirs = + [ "${chip_root}/examples/platform/${nxp_platform}/app/project_include" ] + +chip_enable_openthread = true +chip_openthread_ftd = false chip_inet_config_enable_ipv4 = false @@ -47,7 +65,7 @@ chip_mdns = "platform" chip_system_config_use_open_thread_inet_endpoints = true chip_with_lwip = false -mbedtls_target = "${chip_root}/third_party/nxp/k32w0_sdk:mbedtls" +mbedtls_target = "${nxp_sdk_build_root}/${nxp_sdk_name}:mbedtls" openthread_external_mbedtls = mbedtls_target openthread_project_core_config_file = "OpenThreadConfig.h" diff --git a/src/platform/nxp/k32w/k32w1/BUILD.gn b/src/platform/nxp/k32w/k32w1/BUILD.gn index bb3e74dc034527..53a4b4dce10509 100644 --- a/src/platform/nxp/k32w/k32w1/BUILD.gn +++ b/src/platform/nxp/k32w/k32w1/BUILD.gn @@ -30,6 +30,8 @@ if (chip_crypto == "platform") { } static_library("nxp_platform") { + defines = [ "CHIP_DEVICE_K32W1=1" ] + sources = [ "../../../SingletonConfigurationManager.cpp", "../common/BLEManagerCommon.cpp", @@ -65,7 +67,7 @@ static_library("nxp_platform") { "${chip_root}/src/credentials/examples/ExampleDACs.h", "${chip_root}/src/credentials/examples/ExamplePAI.h", "${chip_root}/src/platform/nxp/k32w/k32w1/BLEManagerImpl.h", - "${chip_root}/src/platform/nxp/k32w/k32w1/BLEManagerImpl.h", + "${chip_root}/src/platform/nxp/k32w/k32w1/DefaultTestEventTriggerDelegate.h", "${chip_root}/src/platform/nxp/k32w/k32w1/SMU2Manager.h", ] @@ -107,7 +109,7 @@ static_library("nxp_platform") { ] if (chip_convert_dac_private_key == 1) { - defines = [ "CHIP_DEVICE_CONFIG_SECURE_DAC_PRIVATE_KEY=1" ] + defines += [ "CHIP_DEVICE_CONFIG_SECURE_DAC_PRIVATE_KEY=1" ] } } diff --git a/src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.cpp b/src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.cpp index 212d0b5ea39fa6..3a3412eb031d36 100644 --- a/src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.cpp +++ b/src/platform/nxp/k32w/k32w1/DiagnosticDataProviderImpl.cpp @@ -99,8 +99,7 @@ CHIP_ERROR DiagnosticDataProviderImpl::GetThreadMetrics(ThreadMetrics ** threadM { ThreadMetrics * thread = (ThreadMetrics *) pvPortMalloc(sizeof(ThreadMetrics)); - strncpy(thread->NameBuf, taskStatusArray[x].pcTaskName, kMaxThreadNameLength - 1); - thread->NameBuf[kMaxThreadNameLength] = '\0'; + Platform::CopyString(thread->NameBuf, taskStatusArray[x].pcTaskName); thread->name.Emplace(CharSpan::fromCharString(thread->NameBuf)); thread->id = taskStatusArray[x].xTaskNumber; diff --git a/src/platform/nxp/k32w/k32w1/FactoryDataProviderImpl.cpp b/src/platform/nxp/k32w/k32w1/FactoryDataProviderImpl.cpp index 10ae6d0dd4ab05..06741ef770dcc4 100644 --- a/src/platform/nxp/k32w/k32w1/FactoryDataProviderImpl.cpp +++ b/src/platform/nxp/k32w/k32w1/FactoryDataProviderImpl.cpp @@ -36,18 +36,11 @@ FactoryDataProviderImpl::~FactoryDataProviderImpl() CHIP_ERROR FactoryDataProviderImpl::Init() { CHIP_ERROR error = CHIP_NO_ERROR; - uint32_t sum = 0; #if CHIP_DEVICE_CONFIG_ENABLE_SSS_API_TEST SSS_RunApiTest(); #endif - if (sum > kFactoryDataSize) - { - ChipLogError(DeviceLayer, "Max size of factory data: %lu is bigger than reserved factory data size: %lu", sum, - kFactoryDataSize); - } - error = Validate(); if (error != CHIP_NO_ERROR) { diff --git a/src/platform/nxp/k32w/k32w1/K32W1Config.cpp b/src/platform/nxp/k32w/k32w1/K32W1Config.cpp index 489d6363d8f4e9..2019fce5811eba 100644 --- a/src/platform/nxp/k32w/k32w1/K32W1Config.cpp +++ b/src/platform/nxp/k32w/k32w1/K32W1Config.cpp @@ -35,10 +35,6 @@ #include #endif -#if (defined(K32W_LOG_ENABLED) && (K32W_LOG_ENABLED > 0)) -// #include "fsl_component_log.h" -// #include "fsl_component_log_backend_debugconsole.h" -#endif #include "FreeRTOS.h" #include "FunctionLib.h" #include "NVM_Interface.h" diff --git a/src/platform/nxp/rt/rw61x/BUILD.gn b/src/platform/nxp/rt/rw61x/BUILD.gn index 0631c8ccd19c4e..3fca99ff0fb7c9 100644 --- a/src/platform/nxp/rt/rw61x/BUILD.gn +++ b/src/platform/nxp/rt/rw61x/BUILD.gn @@ -33,8 +33,20 @@ config("nxp_platform_config") { ".", "../../common/factory_data", ] - defines += [ "CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA=1" ] - defines += [ "EXTERNAL_FACTORY_DATA_PROVIDER_IMPL_HEADER=\"platform/nxp/common/factory_data/FactoryDataProviderFwkImpl.h\"" ] + defines += [ + "CONFIG_CHIP_PLAT_LOAD_REAL_FACTORY_DATA=1", + "CONFIG_CHIP_ENCRYPTED_FACTORY_DATA=1", + ] + + if (chip_enable_secure_dac_private_key_storage == 1) { + defines += [ "EXTERNAL_FACTORY_DATA_PROVIDER_IMPL_HEADER=\"platform/nxp/rt/rw61x/FactoryDataProviderImpl.h\"" ] + } else { + defines += [ "EXTERNAL_FACTORY_DATA_PROVIDER_IMPL_HEADER=\"platform/nxp/common/factory_data/FactoryDataProviderFwkImpl.h\"" ] + } + } + if (chip_enable_wifi && chip_enable_openthread) { + # Disable thread nwk commissioning instance on endpoint 0, when OTBR is enabled + defines += [ "_NO_NETWORK_COMMISSIONING_DRIVER_" ] } } @@ -68,12 +80,14 @@ static_library("nxp_platform") { "../../common/ram_storage.c", "../../common/ram_storage.h", ] - } else { + } else if (rt_nvm_component == "key_storage") { sources += [ "../../common/NXPConfigKS.cpp" ] } public_deps = [ "${chip_root}/src/platform:platform_base" ] + deps += [ "${chip_root}/src/platform/logging:headers" ] + if (chip_enable_ble) { sources += [ # Adding random file which defines the function sys_csrand_get which is called by BLEManagerImpl from Zephyr @@ -98,7 +112,9 @@ static_library("nxp_platform") { "../../common/NetworkCommissioningDriver.h", "../../common/NetworkCommissioningWiFiDriver.cpp", ] + defines += [ "WIFI_DFS_OPTIMIZATION=1" ] } + if (chip_enable_ota_requestor) { sources += [ "../../common/OTAImageProcessorImpl.cpp", @@ -116,13 +132,18 @@ static_library("nxp_platform") { ] deps += [ "${chip_root}/third_party/openthread:openthread" ] + public_deps += [ "${chip_root}/third_party/openthread:openthread-platform" ] if (chip_mdns == "platform") { - sources += [ - "../../../OpenThread/DnssdImpl.cpp", - "../../../OpenThread/OpenThreadDnssdImpl.cpp", - "../../../OpenThread/OpenThreadDnssdImpl.h", - ] + if (chip_enable_wifi) { + sources += [ "../../common/DnssdImpl.cpp" ] + } else { + sources += [ + "../../../OpenThread/DnssdImpl.cpp", + "../../../OpenThread/OpenThreadDnssdImpl.cpp", + "../../../OpenThread/OpenThreadDnssdImpl.h", + ] + } deps += [ "${chip_root}/src/lib/dnssd:platform_header" ] } } @@ -131,9 +152,27 @@ static_library("nxp_platform") { sources += [ "../../common/factory_data/FactoryDataProvider.cpp", "../../common/factory_data/FactoryDataProvider.h", - "../../common/factory_data/FactoryDataProviderFwkImpl.cpp", - "../../common/factory_data/FactoryDataProviderFwkImpl.h", ] + if (chip_enable_secure_dac_private_key_storage == 1) { + sources += [ + # TO BE MOVED TO THE SDK + "ELSFactoryData.c", + "ELSFactoryData.h", + "FactoryDataProviderImpl.cpp", + "FactoryDataProviderImpl.h", + ] + } else { + sources += [ + "../../common/factory_data/FactoryDataProviderFwkImpl.cpp", + "../../common/factory_data/FactoryDataProviderFwkImpl.h", + ] + } + + deps += [ "${chip_root}/src/credentials:credentials_header" ] + } + + if (chip_convert_dac_private_key == 1) { + defines += [ "CHIP_DEVICE_CONFIG_SECURE_DAC_PRIVATE_KEY=1" ] } deps += [ "${nxp_sdk_build_root}:nxp_sdk" ] diff --git a/src/platform/nxp/rt/rw61x/CHIPPlatformConfig.h b/src/platform/nxp/rt/rw61x/CHIPPlatformConfig.h index eedd1101802b3f..caa7e32a9e3000 100644 --- a/src/platform/nxp/rt/rw61x/CHIPPlatformConfig.h +++ b/src/platform/nxp/rt/rw61x/CHIPPlatformConfig.h @@ -32,6 +32,11 @@ /* Default NXP platform adaptations are used */ +/* In src/crypto/CHIPCryptoPALmbedTLS.cpp we verify kMAX_Hash_SHA256_Context_Size >= sizeof(mbedtls_sha256_context) + * sizeof(mbedtls_sha256_context) is 392 bytes with els_pkc mbedtls port + */ +#define CHIP_CONFIG_SHA256_CONTEXT_SIZE (sizeof(unsigned int) * 98) + // ==================== Security Adaptations ==================== /* Default NXP Platform security adaptations are used */ diff --git a/src/platform/nxp/rt/rw61x/ELSFactoryData.c b/src/platform/nxp/rt/rw61x/ELSFactoryData.c new file mode 100644 index 00000000000000..a440c42569ffbd --- /dev/null +++ b/src/platform/nxp/rt/rw61x/ELSFactoryData.c @@ -0,0 +1,453 @@ +/* + * Copyright 2023 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "ELSFactoryData.h" + +uint8_t * append_u32(uint8_t * pos, uint32_t val) +{ + *pos++ = 4; + *pos++ = (val >> 24) & 0xFF; + *pos++ = (val >> 16) & 0xFF; + *pos++ = (val >> 8) & 0xFF; + *pos++ = val & 0xFF; + return pos; +} + +uint8_t * append_u16(uint8_t * pos, uint32_t val) +{ + *pos++ = 2; + *pos++ = (val >> 8) & 0xFF; + *pos++ = val & 0xFF; + return pos; +} + +void write_uint32_msb_first(uint8_t * pos, uint32_t data) +{ + pos[0] = ((data) >> 24) & 0xFF; + pos[1] = ((data) >> 16) & 0xFF; + pos[2] = ((data) >> 8) & 0xFF; + pos[3] = ((data) >> 0) & 0xFF; +} + +void printf_buffer(const char * name, const unsigned char * buffer, size_t size) +{ +#define PP_BYTES_PER_LINE (32) + char line_buffer[PP_BYTES_PER_LINE * 2 + 2]; + const unsigned char * pos = buffer; + size_t remaining = size; + while (remaining > 0) + { + size_t block_size = remaining > PP_BYTES_PER_LINE ? PP_BYTES_PER_LINE : remaining; + uint32_t len = 0; + for (size_t i = 0; i < block_size; i++) + { + line_buffer[len++] = nibble_to_char[((*pos) & 0xf0) >> 4]; + line_buffer[len++] = nibble_to_char[(*pos++) & 0x0f]; + } + line_buffer[len++] = '\n'; + line_buffer[len++] = '\0'; + PRINTF("%s (%p): %s", name, pos, line_buffer); + remaining -= block_size; + } +} + +uint32_t get_required_keyslots(mcuxClEls_KeyProp_t prop) +{ + return prop.bits.ksize == MCUXCLELS_KEYPROPERTY_KEY_SIZE_128 ? 1U : 2U; +} + +bool els_is_active_keyslot(mcuxClEls_KeyIndex_t keyIdx) +{ + mcuxClEls_KeyProp_t key_properties; + key_properties.word.value = ((const volatile uint32_t *) (&ELS->ELS_KS0))[keyIdx]; + return key_properties.bits.kactv; +} + +status_t els_enable() +{ + PLOG_INFO("Enabling ELS..."); + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_Enable_Async()); + + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Enable_Async) != token) || (MCUXCLELS_STATUS_OK_WAIT != result)) + { + PLOG_ERROR("mcuxClEls_Enable_Async failed: 0x%08x", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_WaitForOperation(MCUXCLELS_ERROR_FLAGS_CLEAR)); + + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PLOG_ERROR("mcuxClEls_WaitForOperation failed: 0x%08x", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + return STATUS_SUCCESS; +} + +status_t els_get_key_properties(mcuxClEls_KeyIndex_t key_index, mcuxClEls_KeyProp_t * key_properties) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_GetKeyProperties(key_index, key_properties)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_GetKeyProperties) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PLOG_ERROR("mcuxClEls_GetKeyProperties failed: 0x%08lx", result); + return STATUS_ERROR_GENERIC; + } + + MCUX_CSSL_FP_FUNCTION_CALL_END(); + return STATUS_SUCCESS; +} + +mcuxClEls_KeyIndex_t els_get_free_keyslot(uint32_t required_keyslots) +{ + for (mcuxClEls_KeyIndex_t keyIdx = 0U; keyIdx <= (MCUXCLELS_KEY_SLOTS - required_keyslots); keyIdx++) + { + bool is_valid_keyslot = true; + for (uint32_t i = 0U; i < required_keyslots; i++) + { + if (els_is_active_keyslot(keyIdx + i)) + { + is_valid_keyslot = false; + break; + } + } + + if (is_valid_keyslot) + { + return keyIdx; + } + } + return MCUXCLELS_KEY_SLOTS; +} + +status_t els_derive_key(mcuxClEls_KeyIndex_t src_key_index, mcuxClEls_KeyProp_t key_prop, const uint8_t * dd, + mcuxClEls_KeyIndex_t * dst_key_index) +{ + uint32_t required_keyslots = get_required_keyslots(key_prop); + + *dst_key_index = els_get_free_keyslot(required_keyslots); + + if (!(*dst_key_index < MCUXCLELS_KEY_SLOTS)) + { + PLOG_ERROR("no free keyslot found"); + return STATUS_ERROR_GENERIC; + } + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_Ckdf_Sp800108_Async(src_key_index, *dst_key_index, key_prop, dd)); + + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Ckdf_Sp800108_Async) != token) || (MCUXCLELS_STATUS_OK_WAIT != result)) + { + PLOG_ERROR("mcuxClEls_Ckdf_Sp800108_Async failed: 0x%08x", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_WaitForOperation(MCUXCLELS_ERROR_FLAGS_CLEAR)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PLOG_ERROR("mcuxClEls_WaitForOperation failed: 0x%08x", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + return STATUS_SUCCESS; +} + +status_t els_delete_key(mcuxClEls_KeyIndex_t key_index) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_KeyDelete_Async(key_index)); + + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_KeyDelete_Async) != token) || (MCUXCLELS_STATUS_OK_WAIT != result)) + { + PLOG_ERROR("mcuxClEls_KeyDelete_Async failed: 0x%08x", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_WaitForOperation(MCUXCLELS_ERROR_FLAGS_CLEAR)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PLOG_ERROR("mcuxClEls_WaitForOperation failed: 0x%08x", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + return STATUS_SUCCESS; +} + +status_t els_import_key(const uint8_t * wrapped_key, size_t wrapped_key_size, mcuxClEls_KeyProp_t key_prop, + mcuxClEls_KeyIndex_t unwrap_key_index, mcuxClEls_KeyIndex_t * dst_key_index) +{ + uint32_t required_keyslots = get_required_keyslots(key_prop); + *dst_key_index = els_get_free_keyslot(required_keyslots); + + if (!(*dst_key_index < MCUXCLELS_KEY_SLOTS)) + { + PLOG_ERROR("no free keyslot found"); + return STATUS_ERROR_GENERIC; + } + + mcuxClEls_KeyImportOption_t options; + options.bits.kfmt = MCUXCLELS_KEYIMPORT_KFMT_RFC3394; + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN( + result, token, mcuxClEls_KeyImport_Async(options, wrapped_key, wrapped_key_size, unwrap_key_index, *dst_key_index)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_KeyImport_Async) != token) || (MCUXCLELS_STATUS_OK_WAIT != result)) + { + PLOG_ERROR("mcuxClEls_KeyImport_Async failed: 0x%08lx", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_WaitForOperation(MCUXCLELS_ERROR_FLAGS_CLEAR)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PLOG_ERROR("mcuxClEls_WaitForOperation failed: 0x%08lx", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + return STATUS_SUCCESS; +} + +status_t els_keygen(mcuxClEls_KeyIndex_t key_index, uint8_t * public_key, size_t * public_key_size) +{ + status_t status = STATUS_SUCCESS; + + mcuxClEls_EccKeyGenOption_t key_gen_options; + key_gen_options.word.value = 0u; + key_gen_options.bits.kgsign = MCUXCLELS_ECC_PUBLICKEY_SIGN_DISABLE; + key_gen_options.bits.kgsrc = MCUXCLELS_ECC_OUTPUTKEY_DETERMINISTIC; + key_gen_options.bits.skip_pbk = MCUXCLELS_ECC_GEN_PUBLIC_KEY; + + mcuxClEls_KeyProp_t key_properties; + status = els_get_key_properties(key_index, &key_properties); + STATUS_SUCCESS_OR_EXIT_MSG("get_key_properties failed: 0x%08x", status); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN( + result, token, + mcuxClEls_EccKeyGen_Async(key_gen_options, (mcuxClEls_KeyIndex_t) 0, key_index, key_properties, NULL, &public_key[0])); + + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_EccKeyGen_Async) != token) || (MCUXCLELS_STATUS_OK_WAIT != result)) + { + PRINTF("Css_EccKeyGen_Async failed: 0x%08lx\r\n", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_WaitForOperation(MCUXCLELS_ERROR_FLAGS_CLEAR)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PRINTF("Css_EccKeyGen_Async WaitForOperation failed: 0x%08lx\r\n", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); +exit: + return status; +} + +status_t calculate_psa_import_blob_cmac(uint8_t * psa_import_blob, size_t psa_import_blob_length_before_mac, + size_t psa_import_blob_size) +{ + status_t status = STATUS_SUCCESS; + mcuxClEls_KeyIndex_t mac_key_index = MCUXCLELS_KEY_SLOTS; + + assert(psa_import_blob_size >= psa_import_blob_length_before_mac + AES_BLOCK_SIZE); + + uint8_t * pos = &psa_import_blob[psa_import_blob_length_before_mac]; + uint8_t mac[AES_BLOCK_SIZE] = { 0 }; + uint32_t missing_bytes_to_fill_block = AES_BLOCK_SIZE - (psa_import_blob_length_before_mac % AES_BLOCK_SIZE); + + mcuxClEls_CmacOption_t cmac_options = { 0U }; + cmac_options.bits.initialize = MCUXCLELS_CMAC_INITIALIZE_ENABLE; + cmac_options.bits.finalize = MCUXCLELS_CMAC_FINALIZE_ENABLE; + cmac_options.bits.extkey = MCUXCLELS_CMAC_EXTERNAL_KEY_DISABLE; + // ELS needs us to pad the message, it does not do that itself :-( + if (missing_bytes_to_fill_block != 0) + { + memset(pos, 0, missing_bytes_to_fill_block); + *pos = 0x80; + } + + PLOG_INFO("Deriving cmac key for integrity protection on key blob..."); + status = els_derive_key(DIE_INT_MK_SK_INDEX, mac_key_prop, ckdf_derivation_data_mac, &mac_key_index); + STATUS_SUCCESS_OR_EXIT_MSG("derive_key failed: 0x%08x", status); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN( + result, token, + mcuxClEls_Cmac_Async(cmac_options, mac_key_index, NULL, 0, psa_import_blob, psa_import_blob_length_before_mac, mac)); + + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_Cmac_Async) != token) || (MCUXCLELS_STATUS_OK_WAIT != result)) + { + PLOG_ERROR("mcuxClEls_Cmac_Async failed: 0x%08x", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_WaitForOperation(MCUXCLELS_ERROR_FLAGS_CLEAR)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PLOG_ERROR("mcuxClEls_WaitForOperation failed: 0x%08x\n", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + status = els_delete_key(mac_key_index); + STATUS_SUCCESS_OR_EXIT_MSG("delete_key failed: 0x%08x", status); + + memcpy(pos, mac, sizeof(mac)); +exit: + return status; +} + +status_t create_psa_import_blob(const uint8_t * els_key_blob, size_t els_key_blob_size, const psa_key_attributes_t * attributes, + uint8_t * output, size_t * output_size) +{ + assert(els_key_blob_size <= 48); + assert(sizeof(key_blob_magic) < 0x80); + + status_t status = STATUS_SUCCESS; + // clang-format off + size_t required_output_size = 0 + + 2 + sizeof(key_blob_magic) + + 2 + 4 // key_id + + 2 + 4 // algorithms + + 2 + 4 // usage + + 2 + 2 // type + + 2 + 4 // bits + + 2 + 4 // lifetime + + 2 + 4 // device lifecycle + + 2 + 4 // wrapping key id + + 2 + 4 // wrapping algorithm + + 2 + 4 // signing key id + + 2 + 4 // signing algorithm + + 2 + els_key_blob_size // key blob from S50 + + 2 + AES_BLOCK_SIZE; // CMAC + // clang-format on + + if (*output_size < required_output_size) + { + PLOG_ERROR("key blob buffer too small"); + return STATUS_ERROR_GENERIC; + } + *output_size = required_output_size; + + uint32_t key_id = psa_get_key_id(attributes); + uint32_t key_alg = psa_get_key_algorithm(attributes); + uint32_t key_usage = psa_get_key_usage_flags(attributes); + uint16_t key_type = psa_get_key_type(attributes); + uint32_t key_bits = psa_get_key_bits(attributes); + uint32_t key_lifetime = psa_get_key_lifetime(attributes); + uint32_t device_lifecycle = 0x1; // 0x01: OPEN, 0x02: CLOSED, 0x04: CLOSED_LOCKED + uint32_t wrapping_key_id = NXP_DIE_INT_IMPORT_KEK_SK; + uint32_t signing_key_id = NXP_DIE_INT_IMPORT_AUTH_SK; + + PLOG_INFO("Creating key blob..."); + uint8_t * pos = output; + + *pos++ = 0x40; + *pos++ = sizeof(key_blob_magic); + memcpy(pos, key_blob_magic, sizeof(key_blob_magic)); + pos += sizeof(key_blob_magic); + + *pos++ = 0x41; + pos = append_u32(pos, key_id); + + *pos++ = 0x42; + pos = append_u32(pos, key_alg); + + *pos++ = 0x43; + pos = append_u32(pos, key_usage); + + *pos++ = 0x44; + pos = append_u16(pos, key_type); + + *pos++ = 0x45; + pos = append_u32(pos, key_bits); + + *pos++ = 0x46; + pos = append_u32(pos, key_lifetime); + + *pos++ = 0x47; + pos = append_u32(pos, device_lifecycle); + + *pos++ = 0x50; + pos = append_u32(pos, wrapping_key_id); + + *pos++ = 0x51; + pos = append_u32(pos, 0x01); // ELS RFC3394 wrapping + + *pos++ = 0x53; + pos = append_u32(pos, signing_key_id); + + *pos++ = 0x54; + pos = append_u32(pos, 0x01); // CMAC + + *pos++ = 0x55; + *pos++ = els_key_blob_size; + memcpy(pos, els_key_blob, els_key_blob_size); + pos += els_key_blob_size; + + // signature + *pos++ = 0x5E; + *pos++ = AES_BLOCK_SIZE; + size_t psa_import_blob_length_before_mac = pos - output; + + status = calculate_psa_import_blob_cmac(output, psa_import_blob_length_before_mac, *output_size); + return status; +} + +status_t import_die_int_wrapped_key_into_els(const uint8_t * wrapped_key, size_t wrapped_key_size, + mcuxClEls_KeyProp_t key_properties, mcuxClEls_KeyIndex_t * index_output) +{ + status_t status = STATUS_SUCCESS; + mcuxClEls_KeyIndex_t index_unwrap = MCUXCLELS_KEY_SLOTS; + + PLOG_INFO("Deriving wrapping key for import of die_int wrapped key on ELS..."); + status = els_derive_key(DIE_INT_MK_SK_INDEX, wrap_out_key_prop, ckdf_derivation_data_wrap_out, &index_unwrap); + STATUS_SUCCESS_OR_EXIT_MSG("derive_key failed: 0x%08x", status); + + status = els_import_key(wrapped_key, wrapped_key_size, key_properties, index_unwrap, index_output); + STATUS_SUCCESS_OR_EXIT_MSG("import_wrapped_key failed: 0x%08x", status); + + status = els_delete_key(index_unwrap); + STATUS_SUCCESS_OR_EXIT_MSG("delete_key failed: 0x%08x", status); + index_unwrap = MCUXCLELS_KEY_SLOTS; + +exit: + if (index_unwrap < MCUXCLELS_KEY_SLOTS) + { + (void) els_delete_key(index_unwrap); + } + return status; +} + +status_t ELS_sign_hash(uint8_t * digest, mcuxClEls_EccByte_t * ecc_signature, mcuxClEls_EccSignOption_t * sign_options, + mcuxClEls_KeyIndex_t key_index) +{ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, + mcuxClEls_EccSign_Async( // Perform signature generation. + *sign_options, // Set the prepared configuration. + key_index, // Set index of private key in keystore. + digest, NULL, (size_t) 0U, // Pre-hashed data to sign. Note that inputLength parameter is + // ignored since pre-hashed data has a fixed length. + ecc_signature // Output buffer, which the operation will write the signature to. + )); + PLOG_DEBUG_BUFFER("mcuxClEls_EccSign_Async ecc_signature", ecc_signature, MCUXCLELS_ECC_SIGNATURE_SIZE); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_EccSign_Async) != token) || (MCUXCLELS_STATUS_OK_WAIT != result)) + { + PLOG_ERROR("mcuxClEls_EccSign_Async failed. token: 0x%08x, result: 0x%08x", token, result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_WaitForOperation(MCUXCLELS_ERROR_FLAGS_CLEAR)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PLOG_ERROR("mcuxClEls_WaitForOperation failed. token: 0x%08x, result: 0x%08x", token, result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + return STATUS_SUCCESS; +} diff --git a/src/platform/nxp/rt/rw61x/ELSFactoryData.h b/src/platform/nxp/rt/rw61x/ELSFactoryData.h new file mode 100644 index 00000000000000..0ac259a66a4158 --- /dev/null +++ b/src/platform/nxp/rt/rw61x/ELSFactoryData.h @@ -0,0 +1,247 @@ +/* + * Copyright 2023 NXP + * All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef __ELS_FACTORY_DATA_H__ +#define __ELS_FACTORY_DATA_H__ + +#include +#include + +#include "fsl_common.h" +#include "mbedtls/bignum.h" +#include "mbedtls/ecp.h" +#include "mcuxClAes.h" +#include "mcuxClEls_Cipher.h" +#include "mcuxClEls_Cmac.h" +#include "mcuxClEls_Ecc.h" +#include "mcuxClEls_Hash.h" +#include "mcuxClEls_Kdf.h" +#include "mcuxClEls_KeyManagement.h" +#include "mcuxClEls_Rng.h" +#include "mcuxClEls_Types.h" +#include "mcuxClHash_Constants.h" + +#include "psa/crypto.h" +#include "psa/crypto_values.h" + +#include "mbedtls/ecdh.h" +#include "mbedtls/entropy.h" +#include "mbedtls/nist_kw.h" + +#define BLOCK_SIZE_16_BYTES 16 +#define SHA256_OUTPUT_SIZE 32 +#define HASH_ID 0xCE47BA5E +#define HASH_LEN 4 +#define CBC_INITIAL_VECTOR_SIZE 16 + +#define STATUS_SUCCESS 0 +#define STATUS_ERROR_GENERIC 1 + +#define AES_BLOCK_SIZE 16U +#define DIE_INT_MK_SK_INDEX 0U + +#define ELS_BLOB_METADATA_SIZE 8 +#define MAX_ELS_KEY_SIZE 32 +#define ELS_WRAP_OVERHEAD 8 + +#if FACTORY_DATA_PROVIDER_LOG +#define PLOG_ERROR(...) \ + for (;;) \ + { \ + PRINTF("ERROR "); \ + PRINTF(__VA_ARGS__); \ + PRINTF(" (%s:%d)\n", __FILE__, __LINE__); \ + break; \ + } +#else +#define PLOG_ERROR(...) +#endif + +#if FACTORY_DATA_PROVIDER_LOG +#define PLOG_INFO(...) \ + for (;;) \ + { \ + PRINTF("INFO "); \ + PRINTF(__VA_ARGS__); \ + PRINTF("\n"); \ + break; \ + } +#else +#define PLOG_INFO(...) +#endif + +#if FACTORY_DATA_PROVIDER_LOG +#define PLOG_DEBUG(...) \ + for (;;) \ + { \ + PRINTF("DEBUG "); \ + PRINTF(__VA_ARGS__); \ + PRINTF("\n"); \ + break; \ + } +#else +#define PLOG_DEBUG(...) +#endif + +#if FACTORY_DATA_PROVIDER_LOG +#define PLOG_DEBUG_BUFFER(...) printf_buffer(__VA_ARGS__) +#else +#define PLOG_DEBUG_BUFFER(...) +#endif + +#define RET_MBEDTLS_SUCCESS_OR_EXIT_MSG(MSG, ...) \ + if (0 != ret) \ + { \ + status = STATUS_ERROR_GENERIC; \ + PLOG_ERROR(MSG, __VA_ARGS__); \ + goto exit; \ + } + +#define STATUS_SUCCESS_OR_EXIT_MSG(MSG, ...) \ + if (STATUS_SUCCESS != status) \ + { \ + PLOG_ERROR(MSG, __VA_ARGS__); \ + goto exit; \ + } + +// common flags +#define PSA_KEY_LOCATION_NXP_FLAG 0x400000U +#define PSA_KEY_LOCATION_EL2GO_FLAG 0x200000U +#define PSA_KEY_LOCATION_S50_FLAG 0x000001U +#define PSA_KEY_LOCATION_COMMON_FLAG \ + (PSA_KEY_LOCATION_VENDOR_FLAG | PSA_KEY_LOCATION_NXP_FLAG | PSA_KEY_LOCATION_EL2GO_FLAG | PSA_KEY_LOCATION_S50_FLAG) + +// key/data +#define PSA_KEY_LOCATION_KEY_FLAG 0x000000 +#define PSA_KEY_LOCATION_DATA_FLAG 0x008000 + +// blob/encrypted +#define PSA_KEY_LOCATION_BLOB_STORAGE_FLAG 0x000000 +#define PSA_KEY_LOCATION_ENC_STORAGE_FLAG 0x000100 +#define PSA_KEY_LOCATION_TEMP_STORAGE_FLAG 0x000200 +#define PSA_KEY_LOCATION_KEY_GEN_STORAGE_FLAG 0x000300 + +#define PSA_KEY_LOCATION_S50_BLOB_STORAGE \ + ((PSA_KEY_LOCATION_COMMON_FLAG | PSA_KEY_LOCATION_BLOB_STORAGE_FLAG | PSA_KEY_LOCATION_KEY_FLAG)) +#define MCUXCLPSADRIVER_IS_S50_BLOB_STORAGE(location) ((location) == PSA_KEY_LOCATION_S50_BLOB_STORAGE) +#define PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION(persistence, location) ((location) << 8 | (persistence)) + +#define NXP_DIE_INT_IMPORT_KEK_SK 0x7FFF817CU +#define NXP_DIE_INT_IMPORT_AUTH_SK 0x7FFF817EU + +const mcuxClEls_KeyProp_t keypair_prop = { .bits = { + .ksize = MCUXCLELS_KEYPROPERTY_KEY_SIZE_256, + .upprot_priv = MCUXCLELS_KEYPROPERTY_PRIVILEGED_TRUE, + .upprot_sec = MCUXCLELS_KEYPROPERTY_SECURE_TRUE, + + } }; + +const mcuxClEls_KeyProp_t shared_secret_prop = { + .bits = + { + .ksize = MCUXCLELS_KEYPROPERTY_KEY_SIZE_128, + .uckdf = MCUXCLELS_KEYPROPERTY_CKDF_TRUE, + .upprot_priv = MCUXCLELS_KEYPROPERTY_PRIVILEGED_TRUE, + .upprot_sec = MCUXCLELS_KEYPROPERTY_SECURE_TRUE, + + + }, +}; + +const mcuxClEls_KeyProp_t wrap_in_key_prop = { + .bits = + { + .ksize = MCUXCLELS_KEYPROPERTY_KEY_SIZE_128, + .kactv = MCUXCLELS_KEYPROPERTY_ACTIVE_TRUE, + .ukuok = MCUXCLELS_KEYPROPERTY_KUOK_TRUE, + .upprot_priv = MCUXCLELS_KEYPROPERTY_PRIVILEGED_TRUE, + .upprot_sec = MCUXCLELS_KEYPROPERTY_SECURE_TRUE, + + }, +}; + +const uint8_t ckdf_derivation_data_wrap_in[12] = { + 0xc8, 0xac, 0x48, 0x88, 0xa6, 0x1b, 0x3d, 0x9b, 0x56, 0xa9, 0x75, 0xe7, +}; + +const mcuxClEls_KeyProp_t wrap_out_key_prop = { + .bits = + { + .ksize = MCUXCLELS_KEYPROPERTY_KEY_SIZE_256, + .kactv = MCUXCLELS_KEYPROPERTY_ACTIVE_TRUE, + .ukwk = MCUXCLELS_KEYPROPERTY_KWK_TRUE, + .upprot_priv = MCUXCLELS_KEYPROPERTY_PRIVILEGED_TRUE, + .upprot_sec = MCUXCLELS_KEYPROPERTY_SECURE_TRUE, + + + }, +}; + +const uint8_t ckdf_derivation_data_wrap_out[12] = { + 0x4e, 0x5f, 0x0a, 0x1c, 0x43, 0x37, 0x2c, 0xd0, 0x54, 0x8e, 0x46, 0xc9, +}; + +const mcuxClEls_KeyProp_t mac_key_prop = { + .bits = + { + .ksize = MCUXCLELS_KEYPROPERTY_KEY_SIZE_256, + .kactv = MCUXCLELS_KEYPROPERTY_ACTIVE_TRUE, + .ucmac = MCUXCLELS_KEYPROPERTY_CMAC_TRUE, + .upprot_priv = MCUXCLELS_KEYPROPERTY_PRIVILEGED_TRUE, + .upprot_sec = MCUXCLELS_KEYPROPERTY_SECURE_TRUE, + }, +}; + +const uint8_t ckdf_derivation_data_mac[12] = { + 0xea, 0x93, 0x05, 0x7a, 0x50, 0xb6, 0x4d, 0x58, 0x0a, 0xe6, 0x6b, 0x57, +}; + +const uint8_t import_die_int_ecdh_sk[32] = { + 0x82, 0x9b, 0xb4, 0x4a, 0x3b, 0x6d, 0x73, 0x35, 0x09, 0x5e, 0xd9, 0x8d, 0xf6, 0x09, 0x89, 0x98, + 0xac, 0x63, 0xab, 0x4e, 0x4e, 0x78, 0xf6, 0x0a, 0x70, 0xea, 0x64, 0x92, 0xd4, 0xfc, 0xe4, 0x92, +}; + +const uint8_t import_die_int_ecdh_pk[64] = { + 0x8c, 0xe2, 0x3a, 0x89, 0xe7, 0xc5, 0xe9, 0xb1, 0x3e, 0x89, 0xed, 0xdb, 0x69, 0xb9, 0x22, 0xf8, + 0xc2, 0x8f, 0x5d, 0xcc, 0x59, 0x3e, 0x5f, 0x7b, 0x6e, 0x5a, 0x6c, 0xb3, 0x62, 0xc0, 0x17, 0x8a, + 0x2f, 0xda, 0xe8, 0x72, 0x67, 0x7b, 0xdf, 0xfe, 0xdb, 0x4a, 0x6e, 0x39, 0x2a, 0x1b, 0xae, 0xf8, + 0x88, 0x8f, 0xc5, 0x11, 0xc3, 0x67, 0x85, 0x5a, 0xc5, 0x54, 0xbb, 0xeb, 0x19, 0xf6, 0x52, 0x66, +}; + +const uint8_t key_blob_magic[7] = { 'k', 'e', 'y', 'b', 'l', 'o', 'b' }; + +const size_t s50_blob_size = 100; + +const char nibble_to_char[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', +}; + +uint8_t * append_u32(uint8_t * pos, uint32_t val); +uint8_t * append_u16(uint8_t * pos, uint32_t val); +void write_uint32_msb_first(uint8_t * pos, uint32_t data); +void printf_buffer(const char * name, const unsigned char * buffer, size_t size); +uint32_t get_required_keyslots(mcuxClEls_KeyProp_t prop); +bool els_is_active_keyslot(mcuxClEls_KeyIndex_t keyIdx); +status_t els_enable(); +status_t els_get_key_properties(mcuxClEls_KeyIndex_t key_index, mcuxClEls_KeyProp_t * key_properties); +mcuxClEls_KeyIndex_t els_get_free_keyslot(uint32_t required_keyslots); +status_t els_derive_key(mcuxClEls_KeyIndex_t src_key_index, mcuxClEls_KeyProp_t key_prop, const uint8_t * dd, + mcuxClEls_KeyIndex_t * dst_key_index); +status_t els_delete_key(mcuxClEls_KeyIndex_t key_index); +status_t els_import_key(const uint8_t * wrapped_key, size_t wrapped_key_size, mcuxClEls_KeyProp_t key_prop, + mcuxClEls_KeyIndex_t unwrap_key_index, mcuxClEls_KeyIndex_t * dst_key_index); +status_t els_keygen(mcuxClEls_KeyIndex_t key_index, uint8_t * public_key, size_t * public_key_size); +status_t calculate_psa_import_blob_cmac(uint8_t * psa_import_blob, size_t psa_import_blob_length_before_mac, + size_t psa_import_blob_size); +status_t create_psa_import_blob(const uint8_t * els_key_blob, size_t els_key_blob_size, const psa_key_attributes_t * attributes, + uint8_t * output, size_t * output_size); +status_t import_die_int_wrapped_key_into_els(const uint8_t * wrapped_key, size_t wrapped_key_size, + mcuxClEls_KeyProp_t key_properties, mcuxClEls_KeyIndex_t * index_output); +status_t ELS_sign_hash(uint8_t * digest, mcuxClEls_EccByte_t * ecc_signature, mcuxClEls_EccSignOption_t * sign_options, + mcuxClEls_KeyIndex_t key_index); + +#endif diff --git a/src/platform/nxp/rt/rw61x/FactoryDataProviderImpl.cpp b/src/platform/nxp/rt/rw61x/FactoryDataProviderImpl.cpp new file mode 100644 index 00000000000000..299a018fabd092 --- /dev/null +++ b/src/platform/nxp/rt/rw61x/FactoryDataProviderImpl.cpp @@ -0,0 +1,900 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright 2023 NXP + * + * 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 "FactoryDataProviderImpl.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +#include "ELSFactoryData.h" +#include "mflash_drv.h" + +#if CHIP_DEVICE_CONFIG_SECURE_DAC_PRIVATE_KEY +#include "fsl_adapter_flash.h" +#endif + +#ifndef FACTORY_DATA_PROVIDER_RUN_TESTS +#define FACTORY_DATA_PROVIDER_RUN_TESTS 0 +#endif + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#ifndef FACTORY_DATA_PROVIDER_LOG +#define FACTORY_DATA_PROVIDER_LOG 0 +#endif + +#if FACTORY_DATA_PROVIDER_LOG +#include "fsl_debug_console.h" +#define FACTORY_DATA_PROVIDER_PRINTF(...) \ + PRINTF("[%s] ", __FUNCTION__); \ + PRINTF(__VA_ARGS__); \ + PRINTF("\n\r"); +#else +#define FACTORY_DATA_PROVIDER_PRINTF(...) +#endif + +/* Grab symbol for the base address from the linker file. */ +extern uint32_t __FACTORY_DATA_START_OFFSET[]; +extern uint32_t __FACTORY_DATA_SIZE[]; + +using namespace ::chip::Credentials; +using namespace ::chip::Crypto; + +namespace chip { +namespace DeviceLayer { + +FactoryDataProviderImpl FactoryDataProviderImpl::sInstance; + +CHIP_ERROR FactoryDataProviderImpl::SearchForId(uint8_t searchedType, uint8_t * pBuf, size_t bufLength, uint16_t & length, + uint32_t * contentAddr) +{ + CHIP_ERROR err = CHIP_ERROR_NOT_FOUND; + uint8_t type = 0; + uint32_t index = 0; + uint8_t * addrContent = NULL; + uint8_t * factoryDataAddress = &factoryDataRamBuffer[0]; + uint32_t factoryDataSize = sizeof(factoryDataRamBuffer); + uint16_t currentLen = 0; + + while (index < factoryDataSize) + { + /* Read the type */ + memcpy((uint8_t *) &type, factoryDataAddress + index, sizeof(type)); + index += sizeof(type); + + /* Read the len */ + memcpy((uint8_t *) ¤tLen, factoryDataAddress + index, sizeof(currentLen)); + index += sizeof(currentLen); + + /* Check if the type gotten is the expected one */ + if (searchedType == type) + { + FACTORY_DATA_PROVIDER_PRINTF("type = %d, currentLen = %d, bufLength =%d", type, currentLen, bufLength); + /* If pBuf is null it means that we only want to know if the Type has been found */ + if (pBuf != NULL) + { + /* If the buffer given is too small, fill only the available space */ + if (bufLength < currentLen) + { + currentLen = bufLength; + } + memcpy((uint8_t *) pBuf, factoryDataAddress + index, currentLen); + } + length = currentLen; + if (contentAddr != NULL) + { + *contentAddr = (uint32_t) factoryDataAddress + index; + } + err = CHIP_NO_ERROR; + break; + } + else if (type == 0) + { + /* No more type available , break the loop */ + break; + } + else + { + /* Jump to next data */ + index += currentLen; + } + } + + return err; +} + +CHIP_ERROR FactoryDataProviderImpl::SignWithDacKey(const ByteSpan & digestToSign, MutableByteSpan & outSignBuffer) +{ + uint8_t els_key_blob[MAX_ELS_KEY_SIZE + ELS_BLOB_METADATA_SIZE + ELS_WRAP_OVERHEAD]; + uint16_t keySize = 0; + uint32_t keyAddr; + status_t status = STATUS_SUCCESS; + size_t els_key_blob_size = sizeof(els_key_blob); + uint8_t digest[kSHA256_Hash_Length]; + uint8_t public_key[64] = { 0 }; + size_t public_key_size = sizeof(public_key); + mcuxClEls_KeyIndex_t key_index = MCUXCLELS_KEY_SLOTS; + + mcuxClEls_KeyProp_t plain_key_properties = { + .word = { .value = MCUXCLELS_KEYPROPERTY_VALUE_SECURE | MCUXCLELS_KEYPROPERTY_VALUE_PRIVILEGED | + MCUXCLELS_KEYPROPERTY_VALUE_KEY_SIZE_256 | MCUXCLELS_KEYPROPERTY_VALUE_KGSRC } + }; + + mcuxClEls_EccSignOption_t sign_options = { 0 }; + mcuxClEls_EccByte_t ecc_signature[MCUXCLELS_ECC_SIGNATURE_SIZE]; + uint8_t psa_import_blob[256]; + size_t psa_import_blob_size = sizeof(psa_import_blob); + + /* Search key ID FactoryDataId::kDacPrivateKeyId */ + ReturnErrorOnFailure(SearchForId(FactoryDataId::kDacPrivateKeyId, NULL, 0, keySize, &keyAddr)); + memcpy(els_key_blob, (uint8_t *) keyAddr, keySize); + + PLOG_DEBUG_BUFFER("els_key_blob", els_key_blob, els_key_blob_size); + + /* Calculate message HASH to sign */ + memset(&digest[0], 0, sizeof(digest)); + ReturnErrorOnFailure(Hash_SHA256(digestToSign.data(), digestToSign.size(), &digest[0])); + + PLOG_DEBUG_BUFFER("digestToSign", digestToSign.data(), digestToSign.size()); + + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH); + psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attributes, 256); + psa_set_key_lifetime( + &attributes, + PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION(PSA_KEY_LIFETIME_PERSISTENT, PSA_KEY_LOCATION_S50_BLOB_STORAGE)); + psa_set_key_id(&attributes, 0x3E000021); + + /*To satisfy PSA APIs and to bind key attributes on PSA level to the key, a TLV structure is created + containing the els_key_blob and metadata. That TLV structure gets a CMAC. This structure is quasi + identical to what EdgeLock 2GO is creating. Note that the TLV structure is not used below but + required in case a blob shall be imported into TF-M for RW61x. */ + status = create_psa_import_blob(els_key_blob, els_key_blob_size, &attributes, psa_import_blob, &psa_import_blob_size); + STATUS_SUCCESS_OR_EXIT_MSG("export_key_from_els failed: 0x%08x", status); + PLOG_DEBUG_BUFFER("psa_import_blob", psa_import_blob, psa_import_blob_size); + + /* Import blob DAC key into SE50 (reserved key slot) */ + status = import_die_int_wrapped_key_into_els(els_key_blob, els_key_blob_size, plain_key_properties, &key_index); + STATUS_SUCCESS_OR_EXIT_MSG("import_die_int_wrapped_key_into_els failed: 0x%08x", status); + + /* For ECC keys that were created from plain key material, there is the + neceessity to convert them to a key. Converting to a key also yields the public key. + The conversion can be done either before re-wrapping (when importing the plain key) + or after (when importing the blob).*/ + status = els_keygen(key_index, &public_key[0], &public_key_size); + STATUS_SUCCESS_OR_EXIT_MSG("els_keygen failed: 0x%08x", status); + + /* The key is usable for signing. */ + PLOG_DEBUG_BUFFER("public_key", public_key, public_key_size); + + /* ECC sign message hash with the key index slot reserved during the blob importation */ + ELS_sign_hash(digest, ecc_signature, &sign_options, key_index); + + /* Delete SE50 key with the index slot reserved during the blob importation (free key slot) */ + els_delete_key(key_index); + + /* Generate MutableByteSpan with ECC signature and ECC signature size */ + return CopySpanToMutableSpan(ByteSpan{ ecc_signature, MCUXCLELS_ECC_SIGNATURE_SIZE }, outSignBuffer); +exit: + els_delete_key(key_index); + return CHIP_ERROR_INVALID_SIGNATURE; +} + +CHIP_ERROR FactoryDataProviderImpl::Init(void) +{ +#if FACTORY_DATA_PROVIDER_RUN_TESTS + unittest(); +#else + els_enable(); + + uint16_t len; + status_t status; + uint32_t factoryDataAddress = (uint32_t) __FACTORY_DATA_START_OFFSET; + uint32_t factoryDataSize = (uint32_t) __FACTORY_DATA_SIZE; + uint32_t hashId; + uint8_t currentBlock[BLOCK_SIZE_16_BYTES]; + uint8_t calculatedHash[SHA256_OUTPUT_SIZE]; + uint16_t i; + CHIP_ERROR res; + + /* Init mflash */ + status = mflash_drv_init(); + + if (status != kStatus_Success || factoryDataSize > sizeof(factoryDataRamBuffer)) + return CHIP_ERROR_INTERNAL; + + /* Read hash id saved in flash */ + if (mflash_drv_read(factoryDataAddress, (uint32_t *) &mHeader, sizeof(mHeader)) != kStatus_Success) + { + return CHIP_ERROR_INTERNAL; + } + + if (mHeader.hashId != HASH_ID) + { + return CHIP_ERROR_NOT_FOUND; + } + + /* Update address to start after hash id to read size of factory data */ + factoryDataAddress += sizeof(mHeader); + + /* Load the buffer into RAM by reading each 16 bytes blocks */ + for (i = 0; i < (factoryDataSize / BLOCK_SIZE_16_BYTES); i++) + { + if (mflash_drv_read(factoryDataAddress + i * BLOCK_SIZE_16_BYTES, (uint32_t *) ¤tBlock[0], sizeof(currentBlock)) != + kStatus_Success) + { + return CHIP_ERROR_INTERNAL; + } + + /* Store the block unencrypted */ + memcpy(&factoryDataRamBuffer[i * BLOCK_SIZE_16_BYTES], ¤tBlock[0], sizeof(currentBlock)); + } + + /* Calculate SHA256 value over the factory data and compare with stored value */ + res = Hash256(&factoryDataRamBuffer[0], mHeader.size, &calculatedHash[0]); + + if (res != CHIP_NO_ERROR) + return res; + + if (memcmp(&calculatedHash[0], &mHeader.hash[0], HASH_LEN) != 0) + { + return CHIP_ERROR_NOT_FOUND; + } + + ReturnErrorOnFailure(SearchForId(FactoryDataId::kVerifierId, NULL, 0, len)); + FACTORY_DATA_PROVIDER_PRINTF("[%d] len = %d", FactoryDataId::kVerifierId, len); + ReturnErrorOnFailure(SearchForId(FactoryDataId::kSaltId, NULL, 0, len)); + FACTORY_DATA_PROVIDER_PRINTF("[%d] len = %d", FactoryDataId::kSaltId, len); + ReturnErrorOnFailure(SearchForId(FactoryDataId::kIcId, NULL, 0, len)); + FACTORY_DATA_PROVIDER_PRINTF("[%d] len = %d", FactoryDataId::kIcId, len); + ReturnErrorOnFailure(SearchForId(FactoryDataId::kDacPrivateKeyId, NULL, 0, len)); + FACTORY_DATA_PROVIDER_PRINTF("[%d] len = %d", FactoryDataId::kDacPrivateKeyId, len); + ReturnErrorOnFailure(SearchForId(FactoryDataId::kDacCertificateId, NULL, 0, len)); + FACTORY_DATA_PROVIDER_PRINTF("[%d] len = %d", FactoryDataId::kDacCertificateId, len); + ReturnErrorOnFailure(SearchForId(FactoryDataId::kPaiCertificateId, NULL, 0, len)); + FACTORY_DATA_PROVIDER_PRINTF("[%d] len = %d", FactoryDataId::kPaiCertificateId, len); + ReturnErrorOnFailure(SearchForId(FactoryDataId::kDiscriminatorId, NULL, 0, len)); + +#if CHIP_DEVICE_CONFIG_SECURE_DAC_PRIVATE_KEY + ReturnErrorOnFailure(ELS_ConvertDacKey()); +#endif +#endif + return CHIP_NO_ERROR; +} + +CHIP_ERROR FactoryDataProviderImpl::Hash256(const uint8_t * input, size_t inputSize, uint8_t * output) +{ + CHIP_ERROR res; + res = Hash_SHA256(input, inputSize, output); + + return res; +} + +#if CHIP_DEVICE_CONFIG_SECURE_DAC_PRIVATE_KEY +static inline uint32_t els_get_key_size(mcuxClEls_KeyIndex_t keyIdx) +{ + mcuxClEls_KeyProp_t key_properties; + key_properties.word.value = ((const volatile uint32_t *) (&ELS->ELS_KS0))[keyIdx]; + return (key_properties.bits.ksize == MCUXCLELS_KEYPROPERTY_KEY_SIZE_256) ? (256U / 8U) : (128U / 8U); +} + +static status_t els_export_key(mcuxClEls_KeyIndex_t src_key_index, mcuxClEls_KeyIndex_t wrap_key_index, uint8_t * els_key_out_blob, + size_t * els_key_out_blob_size) + +{ + uint32_t key_size = els_get_key_size(src_key_index); + uint32_t required_blob_size = ELS_BLOB_METADATA_SIZE + key_size + ELS_WRAP_OVERHEAD; + assert(required_blob_size <= *els_key_out_blob_size); + + *els_key_out_blob_size = required_blob_size; + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_KeyExport_Async(wrap_key_index, src_key_index, els_key_out_blob)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_KeyExport_Async) != token) || (MCUXCLELS_STATUS_OK_WAIT != result)) + { + PLOG_ERROR("mcuxClEls_KeyExport_Async failed: 0x%08lx", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_WaitForOperation(MCUXCLELS_ERROR_FLAGS_CLEAR)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PLOG_ERROR("mcuxClEls_WaitForOperation failed: 0x%08lx", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + return STATUS_SUCCESS; +} + +static status_t export_key_from_els(mcuxClEls_KeyIndex_t key_index, uint8_t * output, size_t * output_size) +{ + assert(output != NULL); + status_t status = STATUS_SUCCESS; + + mcuxClEls_KeyIndex_t key_wrap_out_index = MCUXCLELS_KEY_SLOTS; + PLOG_INFO("Deriving wrapping key for export on ELS..."); + status = els_derive_key(DIE_INT_MK_SK_INDEX, wrap_out_key_prop, ckdf_derivation_data_wrap_out, &key_wrap_out_index); + STATUS_SUCCESS_OR_EXIT_MSG("derive_key failed: 0x%08x", status); + + PLOG_INFO("Exporting/wrapping key..."); + status = els_export_key(key_index, key_wrap_out_index, output, output_size); + STATUS_SUCCESS_OR_EXIT_MSG("export_key failed: 0x%08x", status); + + status = els_delete_key(key_wrap_out_index); + STATUS_SUCCESS_OR_EXIT_MSG("delete_key failed: 0x%08x", status); + key_wrap_out_index = MCUXCLELS_KEY_SLOTS; +exit: + return status; +} + +CHIP_ERROR FactoryDataProviderImpl::ELS_ConvertDacKey() +{ + size_t blobSize = MAX_ELS_KEY_SIZE + ELS_BLOB_METADATA_SIZE + ELS_WRAP_OVERHEAD; + size_t newSize = sizeof(Header) + mHeader.size + (ELS_BLOB_METADATA_SIZE + ELS_WRAP_OVERHEAD); + uint8_t blob[Crypto::kP256_PrivateKey_Length + (ELS_BLOB_METADATA_SIZE + ELS_WRAP_OVERHEAD)] = { 0 }; + uint32_t KeyAddr; + uint32_t factoryDataAddress = (uint32_t) __FACTORY_DATA_START_OFFSET; + uint32_t factoryDataSize = (uint32_t) __FACTORY_DATA_SIZE; + uint8_t * data = static_cast(chip::Platform::MemoryAlloc(newSize)); + + VerifyOrReturnError(factoryDataRamBuffer != nullptr, CHIP_ERROR_INTERNAL); + + /* Import pain DAC key and generate the blob */ + ReturnErrorOnFailure(ELS_ExportBlob(blob, &blobSize, KeyAddr)); + ChipLogError(DeviceLayer, "SSS: extracted blob from DAC private key"); + + /* Read all factory data */ + hal_flash_status_t status = + HAL_FlashRead(factoryDataAddress + MFLASH_BASE_ADDRESS, newSize - (ELS_BLOB_METADATA_SIZE + ELS_WRAP_OVERHEAD), data); + VerifyOrReturnError(status == kStatus_HAL_Flash_Success, CHIP_ERROR_INTERNAL); + ChipLogError(DeviceLayer, "SSS: cached factory data in RAM"); + + /* Replace private plain DAC key by the blob into factory data RAM buffer (the blob length is higher then the plain key length) + */ + ReturnErrorOnFailure(ReplaceWithBlob(data, blob, blobSize, KeyAddr)); + ChipLogError(DeviceLayer, "SSS: replaced DAC private key with secured blob"); + PLOG_DEBUG_BUFFER("ReplaceWithBlob", data, newSize); + + /* Erase flash factory data sectors */ + status = HAL_FlashEraseSector(factoryDataAddress + MFLASH_BASE_ADDRESS, factoryDataSize); + VerifyOrReturnError(status == kStatus_HAL_Flash_Success, CHIP_ERROR_INTERNAL); + /* Write new factory data into flash */ + status = HAL_FlashProgramUnaligned(factoryDataAddress + MFLASH_BASE_ADDRESS, newSize, data); + VerifyOrReturnError(status == kStatus_HAL_Flash_Success, CHIP_ERROR_INTERNAL); + ChipLogError(DeviceLayer, "SSS: updated factory data"); + + return CHIP_NO_ERROR; +} + +static status_t els_generate_keypair(mcuxClEls_KeyIndex_t * dst_key_index, uint8_t * public_key, size_t * public_key_size) +{ + if (*public_key_size < 64) + { + PLOG_ERROR("insufficient space for public key"); + return STATUS_ERROR_GENERIC; + } + + mcuxClEls_EccKeyGenOption_t options = { 0 }; + options.bits.kgsrc = MCUXCLELS_ECC_OUTPUTKEY_RANDOM; + options.bits.kgtypedh = MCUXCLELS_ECC_OUTPUTKEY_KEYEXCHANGE; + + uint32_t keypair_required_keyslots = get_required_keyslots(keypair_prop); + *dst_key_index = (mcuxClEls_KeyIndex_t) els_get_free_keyslot(keypair_required_keyslots); + + if (!(*dst_key_index < MCUXCLELS_KEY_SLOTS)) + { + PLOG_ERROR("no free keyslot found"); + return STATUS_ERROR_GENERIC; + } + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN( + result, token, + mcuxClEls_EccKeyGen_Async(options, (mcuxClEls_KeyIndex_t) 0U, *dst_key_index, keypair_prop, NULL, public_key)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_EccKeyGen_Async) != token) || (MCUXCLELS_STATUS_OK_WAIT != result)) + { + PLOG_ERROR("mcuxClEls_EccKeyGen_Async failed: 0x%08x", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_WaitForOperation(MCUXCLELS_ERROR_FLAGS_CLEAR)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PLOG_ERROR("mcuxClEls_WaitForOperation failed: 0x%08x", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + *public_key_size = 64; + return STATUS_SUCCESS; +} + +static status_t els_get_random(unsigned char * out, size_t out_size) +{ + /* Get random IV for sector metadata encryption. */ + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClCss_Rng_DrbgRequest_Async(out, out_size)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClCss_Rng_DrbgRequest_Async) != token) || (MCUXCLELS_STATUS_OK_WAIT != result)) + { + PRINTF("mcuxClCss_Rng_DrbgRequest_Async failed: 0x%08lx\r\n", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClCss_WaitForOperation(MCUXCLCSS_ERROR_FLAGS_CLEAR)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PRINTF("Css_EccKeyGen_Async WaitForOperation failed: 0x%08lx\r\n", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + return STATUS_SUCCESS; +} + +static int get_random_mbedtls_callback(void * ctx, unsigned char * out, size_t out_size) +{ + status_t status = els_get_random(out, out_size); + if (status != STATUS_SUCCESS) + { + return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED; + } + return 0; +} + +static status_t host_perform_key_agreement(const uint8_t * public_key, size_t public_key_size, uint8_t * shared_secret, + size_t * shared_secret_size) +{ + assert(public_key != NULL); + assert(public_key_size == 64); + assert(shared_secret != NULL); + assert(*shared_secret_size >= 32); + + status_t status = STATUS_SUCCESS; + + int ret = 0; + mbedtls_ecp_group grp; + mbedtls_ecp_point qB; + mbedtls_mpi dA, zA; + mbedtls_ecp_group_init(&grp); + mbedtls_ecp_point_init(&qB); + mbedtls_mpi_init(&dA); + mbedtls_mpi_init(&zA); + + unsigned char strbuf[128] = { 0 }; + size_t strlen = sizeof(strbuf); + + uint8_t public_key_compressed[65] = { 0 }; + public_key_compressed[0] = 0x04; + + *shared_secret_size = 32; + ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1); + RET_MBEDTLS_SUCCESS_OR_EXIT_MSG("mbedtls_ecp_group_load failed: 0x%08x", ret); + + ret = mbedtls_mpi_read_binary(&dA, import_die_int_ecdh_sk, sizeof(import_die_int_ecdh_sk)); + RET_MBEDTLS_SUCCESS_OR_EXIT_MSG("mbedtls_mpi_read_binary failed: 0x%08x", ret); + + memcpy(&public_key_compressed[1], public_key, public_key_size); + + ret = mbedtls_ecp_point_read_binary(&grp, &qB, public_key_compressed, sizeof(public_key_compressed)); + RET_MBEDTLS_SUCCESS_OR_EXIT_MSG("mbedtls_ecp_point_read_binary failed: 0x%08x", ret); + + ret = mbedtls_ecdh_compute_shared(&grp, &zA, &qB, &dA, &get_random_mbedtls_callback, NULL); + RET_MBEDTLS_SUCCESS_OR_EXIT_MSG("mbedtls_ecdh_compute_shared failed: 0x%08x", ret); + + mbedtls_ecp_point_write_binary(&grp, &qB, MBEDTLS_ECP_PF_UNCOMPRESSED, &strlen, &strbuf[0], sizeof(strbuf)); + printf_buffer("public_key", strbuf, strlen); + + mbedtls_mpi_write_binary(&zA, shared_secret, *shared_secret_size); + PLOG_DEBUG_BUFFER("shared_secret", shared_secret, *shared_secret_size); +exit: + return status; +} + +static status_t host_derive_key(const uint8_t * input_key, size_t input_key_size, const uint8_t * derivation_data, + size_t derivation_data_size, uint32_t key_properties, uint8_t * output, size_t * output_size) +{ + status_t status = STATUS_SUCCESS; + + int ret = 0; + uint32_t counter = 1; + mbedtls_cipher_context_t ctx; + memset(&ctx, 0, sizeof(ctx)); + bool ctx_valid = false; + + assert(input_key != NULL); + assert(input_key_size == 32); + assert(derivation_data != NULL); + assert(derivation_data_size == 12); + assert(output != NULL); + assert(*output_size == 32); + + uint32_t lsbit = key_properties & 0x01; + uint32_t length_blocks = 1 + lsbit; + uint32_t length_bytes = length_blocks * AES_BLOCK_SIZE; + assert(*output_size >= length_bytes); + *output_size = length_bytes; + + // KDF in counter mode implementation as described in Section 5.1 + // of NIST SP 800-108, Recommendation for Key Derivation Using Pseudorandom Functions + // Derivation data[191:0](sic!) = software_derivation_data[95:0] || 64'h0 || requested_ + // properties[31:0 || length[31:0] || counter[31:0] + + uint8_t dd[32] = { 0 }; + memcpy(&dd[0], derivation_data, derivation_data_size); + memset(&dd[12], 0, 8); + write_uint32_msb_first(&dd[20], key_properties); + write_uint32_msb_first(&dd[24], length_bytes * 8); // expected in bits! + write_uint32_msb_first(&dd[28], counter); + + mbedtls_cipher_type_t mbedtls_cipher_type = MBEDTLS_CIPHER_AES_256_ECB; + const mbedtls_cipher_info_t * cipher_info = mbedtls_cipher_info_from_type(mbedtls_cipher_type); + + PLOG_DEBUG_BUFFER("input_key", input_key, input_key_size); + PLOG_DEBUG_BUFFER("dd", dd, sizeof(dd)); + + uint8_t * pos = output; + do + { + mbedtls_cipher_init(&ctx); + ctx_valid = true; + + ret = mbedtls_cipher_setup(&ctx, cipher_info); + RET_MBEDTLS_SUCCESS_OR_EXIT_MSG("mbedtls_cipher_setup failed: 0x%08x", ret); + + ret = mbedtls_cipher_cmac_starts(&ctx, input_key, input_key_size * 8); + RET_MBEDTLS_SUCCESS_OR_EXIT_MSG("mbedtls_cipher_cmac_starts failed: 0x%08x", ret); + + ret = mbedtls_cipher_cmac_update(&ctx, dd, sizeof(dd)); + RET_MBEDTLS_SUCCESS_OR_EXIT_MSG("mbedtls_cipher_cmac_update failed: 0x%08x", ret); + + ret = mbedtls_cipher_cmac_finish(&ctx, pos); + RET_MBEDTLS_SUCCESS_OR_EXIT_MSG("mbedtls_cipher_cmac_finish failed: 0x%08x", ret); + + mbedtls_cipher_free(&ctx); + ctx_valid = false; + + write_uint32_msb_first(&dd[28], ++counter); + pos += AES_BLOCK_SIZE; + } while (counter * AES_BLOCK_SIZE <= length_bytes); + + PLOG_DEBUG_BUFFER("output", output, length_bytes); + +exit: + if (ctx_valid) + { + mbedtls_cipher_free(&ctx); + ctx_valid = false; + } + + return status; +} + +static status_t host_wrap_key(const uint8_t * data, size_t data_size, const uint8_t * key, size_t key_size, uint8_t * output, + size_t * output_size) +{ + status_t status = STATUS_SUCCESS; + int ret = 0; + mbedtls_nist_kw_context ctx; + mbedtls_nist_kw_init(&ctx); + ret = mbedtls_nist_kw_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key, key_size * 8, true); + RET_MBEDTLS_SUCCESS_OR_EXIT_MSG("mbedtls_nist_kw_setkey failed: 0x%08x", ret); + ret = mbedtls_nist_kw_wrap(&ctx, MBEDTLS_KW_MODE_KW, data, data_size, output, output_size, *output_size); + RET_MBEDTLS_SUCCESS_OR_EXIT_MSG("mbedtls_nist_kw_wrap failed: 0x%08x", ret); + PLOG_DEBUG_BUFFER("wrapped buffer", output, *output_size); +exit: + mbedtls_nist_kw_free(&ctx); + return status; +} + +static status_t create_els_import_keyblob(const uint8_t * plain_key, size_t plain_key_size, mcuxClEls_KeyProp_t plain_key_prop, + const uint8_t * key_wrap_in, size_t key_wrap_in_size, uint8_t * blob, size_t * blob_size) +{ + assert(plain_key_size == 16 || plain_key_size == 32); + assert(key_wrap_in_size == 16); + + uint8_t buffer[ELS_BLOB_METADATA_SIZE + MAX_ELS_KEY_SIZE] = { 0 }; + size_t buffer_size = ELS_BLOB_METADATA_SIZE + plain_key_size; + + // Enforce the wrpok bit - the key needs to be re-wrappable! + plain_key_prop.bits.wrpok = MCUXCLELS_KEYPROPERTY_WRAP_TRUE; + + // This is what ELS documentation says. It does not work though?? + // memset(&buffer[0], 0xA6, 8); + // write_uint32_msb_first(&buffer[8], plain_key_prop.word.value); + // memset(&buffer[12], 0, 4); + // memcpy(&buffer[16], plain_key, plain_key_size); + + write_uint32_msb_first(&buffer[0], plain_key_prop.word.value); + memset(&buffer[4], 0, 4); + memcpy(&buffer[8], plain_key, plain_key_size); + PLOG_DEBUG_BUFFER("plain buffer before wrapping for import", buffer, buffer_size); + + status_t status = host_wrap_key(buffer, buffer_size, key_wrap_in, key_wrap_in_size, blob, blob_size); + return status; +} + +static status_t els_perform_key_agreement(mcuxClEls_KeyIndex_t keypair_index, mcuxClEls_KeyProp_t shared_secret_prop, + mcuxClEls_KeyIndex_t * dst_key_index, const uint8_t * public_key, size_t public_key_size) +{ + uint32_t shared_secret_required_keyslots = get_required_keyslots(shared_secret_prop); + *dst_key_index = els_get_free_keyslot(shared_secret_required_keyslots); + + if (!(*dst_key_index < MCUXCLELS_KEY_SLOTS)) + { + PLOG_ERROR("no free keyslot found"); + return STATUS_ERROR_GENERIC; + } + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, + mcuxClEls_EccKeyExchange_Async(keypair_index, public_key, *dst_key_index, shared_secret_prop)); + + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_EccKeyExchange_Async) != token) || (MCUXCLELS_STATUS_OK_WAIT != result)) + { + PLOG_ERROR("mcuxClEls_EccKeyExchange_Async failed: 0x%08x", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + MCUX_CSSL_FP_FUNCTION_CALL_BEGIN(result, token, mcuxClEls_WaitForOperation(MCUXCLELS_ERROR_FLAGS_CLEAR)); + if ((MCUX_CSSL_FP_FUNCTION_CALLED(mcuxClEls_WaitForOperation) != token) || (MCUXCLELS_STATUS_OK != result)) + { + PLOG_ERROR("mcuxClEls_WaitForOperation failed: 0x%08x", result); + return STATUS_ERROR_GENERIC; + } + MCUX_CSSL_FP_FUNCTION_CALL_END(); + + return STATUS_SUCCESS; +} + +static status_t import_plain_key_into_els(const uint8_t * plain_key, size_t plain_key_size, mcuxClEls_KeyProp_t key_properties, + mcuxClEls_KeyIndex_t * index_output) +{ + status_t status = STATUS_SUCCESS; + mcuxClEls_KeyIndex_t index_plain = MCUXCLELS_KEY_SLOTS; + mcuxClEls_KeyIndex_t index_shared_secret = MCUXCLELS_KEY_SLOTS; + mcuxClEls_KeyIndex_t index_unwrap = MCUXCLELS_KEY_SLOTS; + mcuxClEls_KeyIndex_t * potentially_used_key_indices[] = { &index_plain, &index_shared_secret, &index_unwrap }; + + uint8_t els_key_in_blob[ELS_BLOB_METADATA_SIZE + MAX_ELS_KEY_SIZE + ELS_WRAP_OVERHEAD]; + size_t els_key_in_blob_size = sizeof(els_key_in_blob); + + uint8_t shared_secret[32] = { 0 }; + size_t shared_secret_len = sizeof(shared_secret); + + uint8_t key_wrap_in[32]; + size_t key_wrap_in_size = sizeof(key_wrap_in); + + PLOG_INFO("Generating random ECC keypair..."); + uint8_t public_key[64] = { 0u }; + size_t public_key_size = sizeof(public_key); + status = els_generate_keypair(&index_plain, &public_key[0], &public_key_size); + STATUS_SUCCESS_OR_EXIT_MSG("generate_keypair failed: 0x%08x", status); + + PLOG_INFO("Calculating shared secret on host..."); + status = host_perform_key_agreement(public_key, public_key_size, &shared_secret[0], &shared_secret_len); + STATUS_SUCCESS_OR_EXIT_MSG("perform_key_agreement_host failed: 0x%08x", status); + + PLOG_INFO("Deriving wrapping key for import on host..."); + status = host_derive_key(shared_secret, shared_secret_len, ckdf_derivation_data_wrap_in, sizeof(ckdf_derivation_data_wrap_in), + wrap_in_key_prop.word.value, &key_wrap_in[0], &key_wrap_in_size); + STATUS_SUCCESS_OR_EXIT_MSG("ckdf_host failed: 0x%08x", status); + + PLOG_INFO("Creating ELS keyblob for import..."); + + status = create_els_import_keyblob(plain_key, plain_key_size, key_properties, key_wrap_in, key_wrap_in_size, + &els_key_in_blob[0], &els_key_in_blob_size); + STATUS_SUCCESS_OR_EXIT_MSG("create_els_import_keyblob failed: 0x%08x", status); + + PLOG_INFO("Calculating shared secret on ELS..."); + status = els_perform_key_agreement(index_plain, shared_secret_prop, &index_shared_secret, import_die_int_ecdh_pk, + sizeof(import_die_int_ecdh_pk)); + STATUS_SUCCESS_OR_EXIT_MSG("perform_key_agreement failed: 0x%08x", status); + + status = els_delete_key(index_plain); + STATUS_SUCCESS_OR_EXIT_MSG("delete_key failed: 0x%08x", status); + index_plain = MCUXCLELS_KEY_SLOTS; + + PLOG_INFO("Deriving wrapping key for import on ELS..."); + status = els_derive_key(index_shared_secret, wrap_in_key_prop, ckdf_derivation_data_wrap_in, &index_unwrap); + STATUS_SUCCESS_OR_EXIT_MSG("derive_key failed: 0x%08x", status); + + status = els_delete_key(index_shared_secret); + STATUS_SUCCESS_OR_EXIT_MSG("delete_key failed: 0x%08x", status); + index_shared_secret = MCUXCLELS_KEY_SLOTS; + + PLOG_INFO("Importing wrapped key..."); + status = els_import_key(els_key_in_blob, els_key_in_blob_size, key_properties, index_unwrap, index_output); + STATUS_SUCCESS_OR_EXIT_MSG("import_wrapped_key failed: 0x%08x", status); + + status = els_delete_key(index_unwrap); + STATUS_SUCCESS_OR_EXIT_MSG("delete_key failed: 0x%08x", status); + index_unwrap = MCUXCLELS_KEY_SLOTS; + +exit: + for (size_t i = 0; i < ARRAY_SIZE(potentially_used_key_indices); i++) + { + mcuxClEls_KeyIndex_t key_index = *(potentially_used_key_indices[i]); + if (key_index < MCUXCLELS_KEY_SLOTS) + { + (void) els_delete_key(key_index); + } + } + return status; +} + +CHIP_ERROR FactoryDataProviderImpl::ELS_ExportBlob(uint8_t * data, size_t * dataLen, uint32_t & addr) +{ + uint8_t keyBuf[Crypto::kP256_PrivateKey_Length]; + MutableByteSpan dacPrivateKeySpan(keyBuf); + uint16_t keySize = 0; + + status_t status = STATUS_SUCCESS; + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_set_key_algorithm(&attributes, PSA_ALG_ECDSA(PSA_ALG_SHA_256)); + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_HASH | PSA_KEY_USAGE_VERIFY_HASH); + psa_set_key_type(&attributes, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attributes, 256); + psa_set_key_lifetime( + &attributes, + PSA_KEY_LIFETIME_FROM_PERSISTENCE_AND_LOCATION(PSA_KEY_LIFETIME_PERSISTENT, PSA_KEY_LOCATION_S50_BLOB_STORAGE)); + psa_set_key_id(&attributes, 0x3E000021); + + /* Search key ID FactoryDataId::kDacPrivateKeyId */ + ReturnErrorOnFailure( + SearchForId(FactoryDataId::kDacPrivateKeyId, dacPrivateKeySpan.data(), dacPrivateKeySpan.size(), keySize, &addr)); + dacPrivateKeySpan.reduce_size(keySize); + PLOG_DEBUG_BUFFER("Private DAC key plain", dacPrivateKeySpan.data(), dacPrivateKeySpan.size()); + + mcuxClEls_KeyProp_t plain_key_properties = { + .word = { .value = MCUXCLELS_KEYPROPERTY_VALUE_SECURE | MCUXCLELS_KEYPROPERTY_VALUE_PRIVILEGED | + MCUXCLELS_KEYPROPERTY_VALUE_KEY_SIZE_256 | MCUXCLELS_KEYPROPERTY_VALUE_KGSRC } + }; + + mcuxClEls_KeyIndex_t key_index = MCUXCLELS_KEY_SLOTS; + /* Import plain DAC key into S50 */ + status = import_plain_key_into_els(dacPrivateKeySpan.data(), dacPrivateKeySpan.size(), plain_key_properties, &key_index); + STATUS_SUCCESS_OR_EXIT_MSG("derive_key failed: 0x%08x", status); + + /* ELS generate key blob. The blob created here is one that can be directly imported into ELS again. */ + status = export_key_from_els(key_index, data, dataLen); + STATUS_SUCCESS_OR_EXIT_MSG("export_key_from_els failed: 0x%08x", status); + + status = els_delete_key(key_index); + STATUS_SUCCESS_OR_EXIT_MSG("delete_key failed: 0x%08x", status); +exit: + return CHIP_NO_ERROR; +} + +CHIP_ERROR FactoryDataProviderImpl::ReplaceWithBlob(uint8_t * data, uint8_t * blob, size_t blobLen, uint32_t KeyAddr) +{ + size_t newSize = mHeader.size + ELS_BLOB_METADATA_SIZE + ELS_WRAP_OVERHEAD; + FactoryDataProviderImpl::Header * header = reinterpret_cast(data); + uint8_t * payload = data + sizeof(FactoryDataProviderImpl::Header); + uint8_t offset = (uint8_t *) (KeyAddr - kValueOffset) - (uint8_t *) &factoryDataRamBuffer[0]; + size_t subsequentDataOffset = offset + kValueOffset + Crypto::kP256_PrivateKey_Length; + + memmove(payload + subsequentDataOffset + ELS_BLOB_METADATA_SIZE + ELS_WRAP_OVERHEAD, payload + subsequentDataOffset, + mHeader.size - subsequentDataOffset); + header->size = newSize; + /* Update associated TLV length */ + memcpy(payload + offset + kLengthOffset, (uint16_t *) &blobLen, sizeof(uint16_t)); + /* Replace private plain DAC key by the blob */ + memcpy(payload + offset + kValueOffset, blob, blobLen); + + /* Update Header with new hash value */ + uint8_t hash[Crypto::kSHA256_Hash_Length] = { 0 }; + ReturnErrorOnFailure(Crypto::Hash_SHA256(payload, header->size, hash)); + memcpy(header->hash, hash, sizeof(header->hash)); + + return CHIP_NO_ERROR; +} +#endif // CHIP_DEVICE_CONFIG_SECURE_DAC_PRIVATE_KEY + +CHIP_ERROR FactoryDataProviderImpl::unittest(void) +{ +#if FACTORY_DATA_PROVIDER_RUN_TESTS + CHIP_ERROR res; + + uint8_t ecc_message[295] = { + 0x15, 0x30, 0x01, 0xec, 0x30, 0x81, 0xe9, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, 0xa0, 0x81, + 0xdb, 0x30, 0x81, 0xd8, 0x02, 0x01, 0x03, 0x31, 0x0d, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, + 0x02, 0x01, 0x30, 0x45, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x38, 0x04, 0x36, 0x15, + 0x24, 0x00, 0x01, 0x25, 0x01, 0x37, 0x10, 0x36, 0x02, 0x05, 0x26, 0xa2, 0x18, 0x25, 0x03, 0x01, 0x03, 0x2c, 0x04, 0x13, + 0x5a, 0x49, 0x47, 0x32, 0x30, 0x31, 0x34, 0x32, 0x5a, 0x42, 0x33, 0x33, 0x30, 0x30, 0x30, 0x33, 0x2d, 0x32, 0x34, 0x24, + 0x05, 0x00, 0x24, 0x06, 0x00, 0x25, 0x07, 0x76, 0x98, 0x24, 0x08, 0x01, 0x18, 0x31, 0x7d, 0x30, 0x7b, 0x02, 0x01, 0x03, + 0x80, 0x14, 0x62, 0xfa, 0x82, 0x33, 0x59, 0xac, 0xfa, 0xa9, 0x96, 0x3e, 0x1c, 0xfa, 0x14, 0x0a, 0xdd, 0xf5, 0x04, 0xf3, + 0x71, 0x60, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x30, 0x0a, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x04, 0x47, 0x30, 0x45, 0x02, 0x21, 0x00, 0xc3, 0xbf, 0xd1, 0xcb, 0xed, 0x21, + 0x1e, 0x54, 0x76, 0x81, 0xa6, 0xfa, 0x08, 0x8f, 0x26, 0xce, 0x14, 0x8c, 0x72, 0xae, 0x1b, 0x6f, 0x61, 0x18, 0x0f, 0x6f, + 0x01, 0xc2, 0x75, 0xad, 0x6e, 0x5e, 0x02, 0x20, 0x31, 0x11, 0x00, 0x88, 0xcc, 0xc9, 0x98, 0x55, 0x0e, 0xf1, 0xd2, 0x42, + 0x07, 0x7a, 0xaa, 0x41, 0x0c, 0xd2, 0xd3, 0xd4, 0x76, 0xab, 0xd5, 0xaf, 0x32, 0x2c, 0x45, 0x75, 0xfa, 0xcc, 0x51, 0x5b, + 0x30, 0x02, 0x20, 0x73, 0x09, 0xbb, 0x01, 0xa5, 0xae, 0x2f, 0xfc, 0x0b, 0x7f, 0xee, 0xcb, 0xa0, 0xc4, 0x94, 0xf1, 0xd3, + 0x61, 0xce, 0x4a, 0x83, 0x21, 0x5e, 0x84, 0x07, 0xcf, 0x42, 0xc5, 0xee, 0xea, 0x1a, 0x2e, 0x24, 0x03, 0x00, 0x18, 0x90, + 0x75, 0x48, 0x87, 0x85, 0x5f, 0x73, 0xb0, 0xcb, 0x3e, 0x38, 0xa7, 0xbd, 0xad, 0x22, 0xf4 + }; + + const uint8_t kDevelopmentDAC_Cert_FFF1_8002[492] = { + 0x30, 0x82, 0x01, 0xe8, 0x30, 0x82, 0x01, 0x8e, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x08, 0x52, 0x72, 0x4d, 0x21, 0xe2, + 0xc1, 0x74, 0xaf, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x3d, 0x31, 0x25, 0x30, + 0x23, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x1c, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x44, 0x65, 0x76, 0x20, 0x50, + 0x41, 0x49, 0x20, 0x30, 0x78, 0x46, 0x46, 0x46, 0x31, 0x20, 0x6e, 0x6f, 0x20, 0x50, 0x49, 0x44, 0x31, 0x14, 0x30, 0x12, + 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x02, 0x01, 0x0c, 0x04, 0x46, 0x46, 0x46, 0x31, 0x30, 0x20, + 0x17, 0x0d, 0x32, 0x32, 0x30, 0x32, 0x30, 0x35, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x39, 0x39, 0x39, + 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x53, 0x31, 0x25, 0x30, 0x23, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0c, 0x1c, 0x4d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, 0x44, 0x65, 0x76, 0x20, 0x44, 0x41, 0x43, 0x20, + 0x30, 0x78, 0x46, 0x46, 0x46, 0x31, 0x2f, 0x30, 0x78, 0x38, 0x30, 0x30, 0x32, 0x31, 0x14, 0x30, 0x12, 0x06, 0x0a, 0x2b, + 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x02, 0x01, 0x0c, 0x04, 0x46, 0x46, 0x46, 0x31, 0x31, 0x14, 0x30, 0x12, 0x06, + 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xa2, 0x7c, 0x02, 0x02, 0x0c, 0x04, 0x38, 0x30, 0x30, 0x32, 0x30, 0x59, 0x30, + 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, + 0x03, 0x42, 0x00, 0x04, 0xda, 0x93, 0xf1, 0x67, 0x36, 0x25, 0x67, 0x50, 0xd9, 0x03, 0xb0, 0x34, 0xba, 0x45, 0x88, 0xab, + 0xaf, 0x58, 0x95, 0x4f, 0x77, 0xaa, 0x9f, 0xd9, 0x98, 0x9d, 0xfd, 0x40, 0x0d, 0x7a, 0xb3, 0xfd, 0xc9, 0x75, 0x3b, 0x3b, + 0x92, 0x1b, 0x29, 0x4c, 0x95, 0x0f, 0xd9, 0xd2, 0x80, 0xd1, 0x4c, 0x43, 0x86, 0x2f, 0x16, 0xdc, 0x85, 0x4b, 0x00, 0xed, + 0x39, 0xe7, 0x50, 0xba, 0xbf, 0x1d, 0xc4, 0xca, 0xa3, 0x60, 0x30, 0x5e, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, + 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, + 0x07, 0x80, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xef, 0x06, 0x56, 0x11, 0x9c, 0x1c, 0x91, + 0xa7, 0x9a, 0x94, 0xe6, 0xdc, 0xf3, 0x79, 0x79, 0xdb, 0xd0, 0x7f, 0xf8, 0xa3, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, + 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x63, 0x54, 0x0e, 0x47, 0xf6, 0x4b, 0x1c, 0x38, 0xd1, 0x38, 0x84, 0xa4, 0x62, 0xd1, + 0x6c, 0x19, 0x5d, 0x8f, 0xfb, 0x3c, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, + 0x00, 0x30, 0x45, 0x02, 0x20, 0x46, 0x86, 0x81, 0x07, 0x33, 0xbf, 0x0d, 0xc8, 0xff, 0x4c, 0xb5, 0x14, 0x5a, 0x6b, 0xfa, + 0x1a, 0xec, 0xff, 0xa8, 0xb6, 0xda, 0xb6, 0xc3, 0x51, 0xaa, 0xee, 0xcd, 0xaf, 0xb8, 0xbe, 0x95, 0x7d, 0x02, 0x21, 0x00, + 0xe8, 0xc2, 0x8d, 0x6b, 0xfc, 0xc8, 0x7a, 0x7d, 0x54, 0x2e, 0xad, 0x6e, 0xda, 0xca, 0x14, 0x8d, 0x5f, 0xa5, 0x06, 0x1e, + 0x51, 0x7c, 0xbe, 0x4f, 0x24, 0xa7, 0x20, 0xe1, 0xc0, 0x59, 0xde, 0x1a, + }; + const uint8_t kDevelopmentDAC_PublicKey_FFF1_8002[65] = { + 0x04, 0xda, 0x93, 0xf1, 0x67, 0x36, 0x25, 0x67, 0x50, 0xd9, 0x03, 0xb0, 0x34, 0xba, 0x45, 0x88, 0xab, + 0xaf, 0x58, 0x95, 0x4f, 0x77, 0xaa, 0x9f, 0xd9, 0x98, 0x9d, 0xfd, 0x40, 0x0d, 0x7a, 0xb3, 0xfd, 0xc9, + 0x75, 0x3b, 0x3b, 0x92, 0x1b, 0x29, 0x4c, 0x95, 0x0f, 0xd9, 0xd2, 0x80, 0xd1, 0x4c, 0x43, 0x86, 0x2f, + 0x16, 0xdc, 0x85, 0x4b, 0x00, 0xed, 0x39, 0xe7, 0x50, 0xba, 0xbf, 0x1d, 0xc4, 0xca, + }; + + /* Sign using the example attestation private key */ + P256ECDSASignature da_signature; + MutableByteSpan out_sig_span(da_signature.Bytes(), da_signature.Capacity()); + CHIP_ERROR err = SignWithDacKey(ByteSpan{ ecc_message, sizeof(ecc_message) }, out_sig_span); + assert(err == CHIP_NO_ERROR); + + assert(out_sig_span.size() == kP256_ECDSA_Signature_Length_Raw); + da_signature.SetLength(out_sig_span.size()); + + /* Get DAC from the provider */ + uint8_t dac_cert_buf[kMaxDERCertLength]; + MutableByteSpan dac_cert_span(dac_cert_buf); + + memcpy(dac_cert_span.data(), kDevelopmentDAC_Cert_FFF1_8002, 492); + + /* Extract public key from DAC, prior to signature verification */ + P256PublicKey dac_public_key; + err = ExtractPubkeyFromX509Cert(dac_cert_span, dac_public_key); + assert(err == CHIP_NO_ERROR); + assert(dac_public_key.Length() == 65); + assert(0 == memcmp(dac_public_key.ConstBytes(), kDevelopmentDAC_PublicKey_FFF1_8002, 65)); + + /* Verify round trip signature */ + err = dac_public_key.ECDSA_validate_msg_signature(&ecc_message[0], sizeof(ecc_message), da_signature); + assert(err == CHIP_NO_ERROR); + PRINTF("ECDSA signature validated with SUCCESS \n"); +#endif + return CHIP_NO_ERROR; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/rt/rw61x/FactoryDataProviderImpl.h b/src/platform/nxp/rt/rw61x/FactoryDataProviderImpl.h new file mode 100644 index 00000000000000..43a759fa8ee15c --- /dev/null +++ b/src/platform/nxp/rt/rw61x/FactoryDataProviderImpl.h @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright 2023 NXP + * + * 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. + */ + +#pragma once + +#include + +#define FACTORY_DATA_MAX_SIZE 4096 + +namespace chip { +namespace DeviceLayer { + +/** + * @brief This class provides Commissionable data and Device Attestation Credentials. + * + * This implementation allows to use the ELS hardware module to load the Matter factory + * dataset in RAM at the boot. + * + * + */ + +class FactoryDataProviderImpl : public FactoryDataProvider +{ +public: + static FactoryDataProviderImpl sInstance; + + CHIP_ERROR Init(void); + CHIP_ERROR SearchForId(uint8_t searchedType, uint8_t * pBuf, size_t bufLength, uint16_t & length, + uint32_t * contentAddr = NULL); + CHIP_ERROR SignWithDacKey(const ByteSpan & digestToSign, MutableByteSpan & outSignBuffer); + +private: + struct Header + { + uint32_t hashId; + uint32_t size; + uint8_t hash[4]; + }; + uint8_t factoryDataRamBuffer[FACTORY_DATA_MAX_SIZE]; + Header mHeader; + + /* TLV offset */ + static constexpr uint32_t kLengthOffset = 1; + static constexpr uint32_t kValueOffset = 3; + CHIP_ERROR Hash256(const uint8_t * input, size_t inputSize, uint8_t * output); + CHIP_ERROR unittest(void); + + CHIP_ERROR ReplaceWithBlob(uint8_t * data, uint8_t * blob, size_t blobLen, uint32_t offset); + CHIP_ERROR ELS_ExportBlob(uint8_t * data, size_t * dataLen, uint32_t & offset); + CHIP_ERROR ELS_ConvertDacKey(); +}; + +inline FactoryDataProvider & FactoryDataPrvd() +{ + return FactoryDataProviderImpl::sInstance; +} + +inline FactoryDataProviderImpl & FactoryDataPrvdImpl() +{ + return FactoryDataProviderImpl::sInstance; +} + +} // namespace DeviceLayer +} // namespace chip diff --git a/src/platform/nxp/rt/rw61x/args.gni b/src/platform/nxp/rt/rw61x/args.gni index 9e7cf8a37d0292..0c2d183aca8646 100644 --- a/src/platform/nxp/rt/rw61x/args.gni +++ b/src/platform/nxp/rt/rw61x/args.gni @@ -42,6 +42,12 @@ declare_args() { # if rw610_mbedtls_port_els_pkc is set to false software mbedtls is used instead rw610_mbedtls_port_els_pkc = true + + # Enable DAC private key secure usage, chip_with_factory_data must be set to 1 + chip_enable_secure_dac_private_key_storage = 0 + + # Generate DAC private key blob for factory data, chip_enable_secure_dac_private_key_storage must be set to 1 + chip_convert_dac_private_key = 0 } # TODO : Enable the OTA Requestor by default. @@ -54,11 +60,6 @@ declare_args() { no_mcuboot = true } -# As a temporary workaround, we are disabling the session resumption because currently -# the mbedtls port does not support this. -# If not disabled, reconnecting to the OTA Provider node will fail. -chip_enable_session_resumption = false - mbedtls_target = "${nxp_sdk_build_root}:nxp_mbedtls" openthread_external_mbedtls = mbedtls_target diff --git a/src/system/BUILD.gn b/src/system/BUILD.gn index 9f24ad171be72a..afeab8d343f9df 100644 --- a/src/system/BUILD.gn +++ b/src/system/BUILD.gn @@ -58,8 +58,6 @@ if (chip_device_platform == "cc13x2_26x2") { } else if (chip_device_platform == "qpg") { import("//build_overrides/qpg_sdk.gni") import("${qpg_sdk_build_root}/qpg_sdk.gni") -} else if (chip_device_platform == "k32w0") { - import("//build_overrides/k32w0_sdk.gni") } else if (chip_device_platform == "nxp") { import("//build_overrides/nxp_sdk.gni") } else if (chip_device_platform == "psoc6") { @@ -184,9 +182,6 @@ source_set("system_config_header") { if (chip_device_platform == "qpg") { public_deps += [ "${qpg_sdk_build_root}:qpg_sdk" ] } - if (chip_device_platform == "k32w0") { - public_deps += [ "${k32w0_sdk_build_root}:k32w0_sdk" ] - } if (chip_device_platform == "nxp") { public_deps += [ "${nxp_sdk_build_root}:nxp_sdk" ] } diff --git a/src/transport/tests/BUILD.gn b/src/transport/tests/BUILD.gn index 18987c6c8c38e9..8e3f9365d023c8 100644 --- a/src/transport/tests/BUILD.gn +++ b/src/transport/tests/BUILD.gn @@ -45,7 +45,8 @@ chip_test_suite_using_nltest("tests") { ] if (chip_device_platform != "mbed" && chip_device_platform != "efr32" && - chip_device_platform != "esp32" && chip_device_platform != "nrfconnect") { + chip_device_platform != "esp32" && chip_device_platform != "nrfconnect" && + chip_device_platform != "nxp") { test_sources += [ "TestSecureSessionTable.cpp" ] } diff --git a/third_party/nxp/k32w0_sdk/BUILD.gn b/third_party/nxp/k32w0_sdk/BUILD.gn index efc1d786420239..6052497bd43145 100644 --- a/third_party/nxp/k32w0_sdk/BUILD.gn +++ b/third_party/nxp/k32w0_sdk/BUILD.gn @@ -13,11 +13,14 @@ # limitations under the License. import("//build_overrides/chip.gni") -import("//build_overrides/k32w0_sdk.gni") +import("//build_overrides/chip.gni") import("//build_overrides/mbedtls.gni") +import("//build_overrides/nxp_sdk.gni") -import("${k32w0_sdk_build_root}/k32w0_sdk.gni") import("${mbedtls_root}/mbedtls.gni") +import("${nxp_sdk_build_root}/nxp_sdk.gni") + +import("${nxp_sdk_build_root}/${nxp_sdk_name}/${nxp_sdk_name}.gni") declare_args() { # Build target to use for k32w0 SDK. Use this to set global SDK defines. @@ -26,7 +29,7 @@ declare_args() { assert(k32w0_sdk_target != "", "k32w0_sdk_target must be specified") -group("k32w0_sdk") { +group("nxp_sdk") { public_deps = [ k32w0_sdk_target ] } @@ -95,7 +98,7 @@ mbedtls_target("mbedtls") { public_configs = [ ":mbedtls_k32w0_config" ] public_deps = [ - ":k32w0_sdk", + ":nxp_sdk", "${chip_root}/third_party/openthread/platforms/nxp/k32w/k32w0:openthread_mbedtls_config_k32w0", ] } diff --git a/third_party/nxp/k32w0_sdk/k32w0_sdk.gni b/third_party/nxp/k32w0_sdk/k32w0_sdk.gni index 754ac91df80875..509a4584316a9f 100644 --- a/third_party/nxp/k32w0_sdk/k32w0_sdk.gni +++ b/third_party/nxp/k32w0_sdk/k32w0_sdk.gni @@ -14,14 +14,15 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") -import("//build_overrides/k32w0_sdk.gni") import("//build_overrides/mbedtls.gni") +import("//build_overrides/nxp_sdk.gni") import("//build_overrides/openthread.gni") import("${build_root}/config/compiler/compiler.gni") import("${chip_root}/src/crypto/crypto.gni") import("${chip_root}/src/lib/core/core.gni") -import("${chip_root}/src/platform/nxp/k32w/k32w0/args.gni") +import("${chip_root}/src/platform/device.gni") +import("${chip_root}/src/platform/nxp/${nxp_platform}/args.gni") declare_args() { # Location of the k32w0 SDK. @@ -38,7 +39,6 @@ declare_args() { board = "k32w061dk6" chip_with_ntag = 1 chip_with_high_power = 0 - chip_with_factory_data = 0 use_fro_32k = 0 use_custom_factory_provider = 0 chip_crypto_flavor = "NXP-Ultrafast-P256" @@ -52,15 +52,6 @@ declare_args() { chip_with_ota_encryption = 0 chip_with_ota_key = "1234567890ABCDEFA1B2C3D4E5F6F1B4" chip_with_sdk_package = 1 - - # ICD Matter Configuration flags - nxp_ot_idle_interval_ms = 2000 # 2s Idle Intervals - nxp_ot_active_interval_ms = 500 # 500ms Active Intervals - - nxp_idle_mode_duration_s = 600 # 10min Idle Mode Interval - nxp_active_mode_duration_ms = 10000 # 10s Active Mode Interval - nxp_active_mode_threshold_ms = 1000 # 1s Active Mode Threshold - nxp_icd_supported_clients_per_fabric = 2 # 2 registration slots per fabric } assert(k32w0_sdk_root != "", "k32w0_sdk_root must be specified") @@ -108,6 +99,16 @@ template("k32w0_sdk") { chip_with_factory_data == 1, "Please set chip_with_factory_data=1 if using default factory data processor.") + assert( + chip_enable_ota_factory_data_processor == 0 || + chip_enable_ota_firmware_processor == 1, + "Please set chip_enable_ota_firmware_processor=1 if using default factory data processor.") + + assert( + chip_enable_ota_factory_data_processor == 0 || + chip_with_factory_data == 1, + "Please set chip_with_factory_data=1 if using default factory data processor.") + if (build_for_k32w041am == 1 || build_for_k32w041a == 1 || build_for_k32w041 == 1) { build_for_k32w061 = 0 @@ -162,6 +163,7 @@ template("k32w0_sdk") { print("OTA default factory data processor: ", chip_enable_ota_factory_data_processor) print("PDM Encryption: ", chip_with_pdm_encryption) + print("Antenna Diversity enabled: ", use_antenna_diversity) if (chip_with_low_power == 1 && chip_logging == true) { print( @@ -375,6 +377,11 @@ template("k32w0_sdk") { "NXP_ICD_SUPPORTED_CLIENTS_PER_FABRIC=${nxp_icd_supported_clients_per_fabric}", ] + if (use_antenna_diversity == 1) { + print("Check ADO/ADE pin configuration when using Antenna Diversity.") + defines += [ "ANTENNA_DIVERSITY_ENABLE" ] + } + # If OTA default processors are enabled, then OTA custom entry structure # will be saved in external flash: gOTACustomOtaEntryMemory=OTACustomStorage_ExtFlash (1) if (chip_enable_ota_firmware_processor == 1) { @@ -413,6 +420,12 @@ template("k32w0_sdk") { defines += [ "PDM_ENCRYPTION=0" ] } + if (chip_with_pdm_encryption == 1) { + defines += [ "PDM_ENCRYPTION=1" ] + } else { + defines += [ "PDM_ENCRYPTION=0" ] + } + if (chip_with_ota_encryption == 1) { defines += [ "OTA_ENCRYPTION_ENABLE=1", diff --git a/third_party/nxp/k32w0_sdk/nxp_arm.gni b/third_party/nxp/k32w0_sdk/nxp_arm.gni new file mode 100644 index 00000000000000..5afd55a8b3129f --- /dev/null +++ b/third_party/nxp/k32w0_sdk/nxp_arm.gni @@ -0,0 +1,22 @@ +# Copyright (c) 2023 Project CHIP Authors +# +# 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("//build_overrides/nxp_sdk.gni") +import("${nxp_sdk_build_root}/nxp_sdk.gni") + +assert(nxp_platform == "k32w/k32w0", "${nxp_platform} must be k32w/k32w0.") + +arm_float_abi = "soft" +arm_cpu = "cortex-m4" +arm_arch = "armv7e-m" diff --git a/third_party/nxp/k32w0_sdk/k32w0_executable.gni b/third_party/nxp/k32w0_sdk/nxp_executable.gni similarity index 100% rename from third_party/nxp/k32w0_sdk/k32w0_executable.gni rename to third_party/nxp/k32w0_sdk/nxp_executable.gni diff --git a/third_party/nxp/k32w1_sdk/k32w1_sdk.gni b/third_party/nxp/k32w1_sdk/k32w1_sdk.gni index d6d9f6146086d7..5a9e37991e149c 100644 --- a/third_party/nxp/k32w1_sdk/k32w1_sdk.gni +++ b/third_party/nxp/k32w1_sdk/k32w1_sdk.gni @@ -36,6 +36,7 @@ declare_args() { use_smu2_dynamic = false use_hw_sha256 = false use_hw_aes = false + chip_config_dimmable_led = false } openthread_nxp_root = "${chip_root}/third_party/openthread/ot-nxp" @@ -157,6 +158,10 @@ template("k32w1_sdk") { "${chip_root}/src/platform/nxp/k32w/k32w1", ] + if (chip_config_dimmable_led) { + _sdk_include_dirs += [ "${k32w1_sdk_root}/components/pwm" ] + } + if (sdk_release == 1) { _sdk_include_dirs += [ "${k32w1_sdk_root}/middleware/wireless/bluetooth/application/common/matter", @@ -419,6 +424,10 @@ template("k32w1_sdk") { "${k32w1_sdk_root}/middleware/wireless/bluetooth/application/common/matter/ble_init.c", ] + if (chip_config_dimmable_led) { + sources += [ "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_tpm.c" ] + } + if (chip_enable_pw_rpc) { sources += [ "${k32w1_sdk_root}/devices/K32W1480/drivers/fsl_edma.c", @@ -547,6 +556,10 @@ template("k32w1_sdk") { "${k32w1_sdk_root}/rtos/freertos/freertos_kernel/timers.c", ] + if (chip_config_dimmable_led) { + sources += [ "${k32w1_sdk_root}/components/pwm/fsl_adapter_pwm_tpm.c" ] + } + if (chip_with_low_power == 1) { sources += [] } diff --git a/third_party/nxp/rt_sdk/BUILD.gn b/third_party/nxp/rt_sdk/BUILD.gn index 90640e8b1eb2ed..0fd636441543b1 100644 --- a/third_party/nxp/rt_sdk/BUILD.gn +++ b/third_party/nxp/rt_sdk/BUILD.gn @@ -25,18 +25,16 @@ import( "${nxp_sdk_build_root}/${nxp_sdk_name}/${rt_platform}/${rt_platform}.gni") import("${nxp_sdk_build_root}/${nxp_sdk_name}/bt_ble/bt_ble.gni") import("${nxp_sdk_build_root}/${nxp_sdk_name}/lwip/lwip.gni") -import("${nxp_sdk_build_root}/${nxp_sdk_name}/mbedtls/mbedtls.gni") import("//build_overrides/openthread.gni") import("${chip_root}/src/platform/device.gni") import("${chip_root}/src/platform/nxp/${nxp_platform}/args.gni") -mbedtls_root = "${rt_sdk_root}/middleware/mbedtls" - group("nxp_sdk") { public_deps = [ ":nxp_transceiver", + "${nxp_sdk_build_root}/${nxp_sdk_name}/${rt_platform}:nxp_sdk", nxp_sdk_driver_target, nxp_sdk_target, ] @@ -53,76 +51,6 @@ group("nxp_sdk") { } } -config("mbedtls_ksdk_config") { - include_dirs = [ "${mbedtls_root}/port/ksdk" ] - defines = [ "MBEDTLS_PORT_INCLUDE=" ] - if (chip_enable_openthread) { - defines += [ "MBEDTLS_CONFIG_FILE=" ] - } else { - defines += [ "MBEDTLS_CONFIG_FILE=" ] - include_dirs += [ "${nxp_sdk_build_root}/${nxp_sdk_name}/mbedtls/config" ] - } -} - -config("mbedtls_els_pkc_config") { - include_dirs = [ - "${mbedtls_root}/port/els", - "${mbedtls_root}/port/pkc", - ] - defines = [ "MBEDTLS_PORT_INCLUDE=" ] - - if (chip_enable_openthread) { - defines += [ "MBEDTLS_CONFIG_FILE=" ] - } else { - defines += [ "MBEDTLS_CONFIG_FILE=" ] - } -} - -mbedtls_target("nxp_els_pkc_mbedtls") { - sources = [] - public_deps = [ ":nxp_sdk" ] - public_configs = [ ":mbedtls_els_pkc_config" ] - sources += [ - # els port - "${mbedtls_root}/port/els/aes_alt.c", - "${mbedtls_root}/port/els/cbc_mac_alt.c", - "${mbedtls_root}/port/els/cmac_alt.c", - "${mbedtls_root}/port/els/ctr_drbg_alt.c", - "${mbedtls_root}/port/els/els_mbedtls.c", - "${mbedtls_root}/port/els/entropy_poll_alt.c", - "${mbedtls_root}/port/els/gcm_alt.c", - "${mbedtls_root}/port/els/sha256_alt.c", - "${mbedtls_root}/port/els/sha512_alt.c", - - # pkc port - "${mbedtls_root}/port/pkc/ecc_alt.c", - "${mbedtls_root}/port/pkc/ecdh_alt.c", - "${mbedtls_root}/port/pkc/ecdsa_alt.c", - "${mbedtls_root}/port/pkc/els_pkc_mbedtls.c", - "${mbedtls_root}/port/pkc/rsa_alt.c", - ] -} - -mbedtls_target("nxp_ksdk_mbedtls") { - sources = [] - public_deps = [ ":nxp_sdk" ] - public_configs = [ ":mbedtls_ksdk_config" ] - sources += [ - "${mbedtls_root}/port/ksdk/aes_alt.c", - "${mbedtls_root}/port/ksdk/des_alt.c", - "${mbedtls_root}/port/ksdk/ecp_alt.c", - "${mbedtls_root}/port/ksdk/ecp_alt_ksdk.c", - "${mbedtls_root}/port/ksdk/ecp_curves_alt.c", - "${mbedtls_root}/port/ksdk/ksdk_mbedtls.c", - ] - - # Allow a platform to use a software implementation of ksdk_mbedtls.c if provided - if (defined(ksdk_mbedtls_sw_impl)) { - sources -= [ "${mbedtls_root}/port/ksdk/ksdk_mbedtls.c" ] - sources += [ "${ksdk_mbedtls_sw_impl}" ] - } -} - group("nxp_mbedtls") { public_deps = [ "${nxp_sdk_build_root}/${nxp_sdk_name}/${rt_platform}:nxp_mbedtls" ] @@ -141,7 +69,10 @@ config("lwip_rt_config") { } else if (chip_enable_openthread) { include_dirs += [ "lwip/openthread" ] } else if (chip_enable_wifi) { - include_dirs += [ "lwip/wifi" ] + include_dirs += [ + "lwip/wifi", + "${rt_sdk_root}/middleware/wifi_nxp/port/lwip", + ] } } @@ -157,6 +88,14 @@ lwip_target("nxp_lwip") { "${rt_sdk_root}/middleware/lwip/port/enet_ethernetif_kinetis.c", ] } + + if (chip_enable_wifi) { + sources += [ + "${rt_sdk_root}/middleware/wifi_nxp/port/lwip/net.c", + "${rt_sdk_root}/middleware/wifi_nxp/port/lwip/wifi_netif.c", + ] + } + public_configs = [ ":lwip_rt_config" ] public_deps = [ ":nxp_sdk" ] diff --git a/third_party/nxp/rt_sdk/bt_ble/bt_ble.gni b/third_party/nxp/rt_sdk/bt_ble/bt_ble.gni index e039a6072d38f5..83d2495ed9808d 100644 --- a/third_party/nxp/rt_sdk/bt_ble/bt_ble.gni +++ b/third_party/nxp/rt_sdk/bt_ble/bt_ble.gni @@ -48,8 +48,8 @@ template("bt_ble_target") { #edgefast_bluetooth "${rt_sdk_root}/middleware/edgefast_bluetooth/include", "${rt_sdk_root}/middleware/edgefast_bluetooth/source/porting", + "${rt_sdk_root}/middleware/edgefast_bluetooth/source/impl/ethermind/host", "${rt_sdk_root}/middleware/edgefast_bluetooth/source/impl/ethermind/platform", - "${rt_sdk_root}/middleware/edgefast_bluetooth/source/impl/ethermind/platform/configs", #ethermind "${rt_sdk_root}/middleware/wireless/ethermind/bluetooth/export/include", @@ -108,6 +108,15 @@ template("bt_ble_target") { [ "${rt_sdk_root}/middleware/wireless/ethermind/port/pal/mcux" ] } + if (is_sdk_2_15) { + bt_ble_include_dirs += [ + #sdk hook for fatfs config file required for ethermind include files + "${nxp_sdk_build_root}/${nxp_sdk_name}/sdk_hook/fatfs/config", + ] + } else { + bt_ble_include_dirs += [ "${rt_sdk_root}/middleware/edgefast_bluetooth/source/impl/ethermind/platform/configs" ] + } + defines += [ #BT config, "CONFIG_BT_PERIPHERAL=1", @@ -215,9 +224,8 @@ template("bt_ble_target") { deps = [] } - # Adding a dependency to platform_buildconfig to make sure that the file platform_buildconfig.h is generated first - # In fact depending on the build order, issue could happen if the file platform_buildconfig.h is not generated before building rand32.cpp - deps += [ "${chip_root}/src/platform:platform_buildconfig" ] + defines = + [ "vApplicationMallocFailedHook=vApplicationMallocFailedHookBTBLE" ] if (!defined(public_configs)) { public_configs = [] diff --git a/third_party/nxp/rt_sdk/lwip/common/lwipopts_common.h b/third_party/nxp/rt_sdk/lwip/common/lwipopts_common.h index 0cd0675469cc49..14b8fad5823d85 100644 --- a/third_party/nxp/rt_sdk/lwip/common/lwipopts_common.h +++ b/third_party/nxp/rt_sdk/lwip/common/lwipopts_common.h @@ -409,9 +409,11 @@ Some MCU allow computing and verifying the IP, UDP, TCP and ICMP checksums by ha #define TCP_QUEUE_OOSEQ 0 #define ARP_QUEUEING (0) -// TODO: seems like this is unnecessary on Thread-only platforms +// For Thread-only platforms this is unnecessary but Border router config needs to redefine to larger value #define LWIP_RAW 1 +#ifndef MEMP_NUM_RAW_PCB #define MEMP_NUM_RAW_PCB (1) +#endif // TODO: verify count @@ -469,11 +471,17 @@ Some MCU allow computing and verifying the IP, UDP, TCP and ICMP checksums by ha #define PBUF_LINK_HLEN (14) #if LWIP_IPV6 +#ifndef LWIP_IPV6_NUM_ADDRESSES #define LWIP_IPV6_NUM_ADDRESSES 5 +#endif #define LWIP_IPV6_MLD 1 #define LWIP_ND6_QUEUEING 0 + +#ifndef LWIP_IPV6_SCOPES #define LWIP_IPV6_SCOPES 0 +#endif + #endif /* LWIP_IPV6 */ #define LWIP_MULTICAST_PING 0 diff --git a/third_party/nxp/rt_sdk/lwip/ethernet/lwipopts.h b/third_party/nxp/rt_sdk/lwip/ethernet/lwipopts.h new file mode 100644 index 00000000000000..7e59f4c48df3ee --- /dev/null +++ b/third_party/nxp/rt_sdk/lwip/ethernet/lwipopts.h @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 2020 Nest Labs, Inc. + * 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. + */ + +/** + * @file + * Compile-time configuration for LwIP + */ + +#ifndef __LWIPOPTS_H__ +#define __LWIPOPTS_H__ + +#define LWIP_DISABLE_PBUF_POOL_SIZE_SANITY_CHECKS 1 +#define PBUF_POOL_SIZE 15 +#define TCP_MSS (1500 - 40) /* TCP_MSS = (Ethernet MTU - IP header size - TCP header size) */ +#define TCP_WND (8 * TCP_MSS) /* ENET_RXBD_NUM minus spare buffer */ +#define ENET_RXBUFF_NUM 14 +#define ENET_RXBD_NUM 9 + +#define LWIP_ETHERNET 1 +#define LWIP_IPV4 0 + +#include "lwipopts_common.h" + +#endif /* __LWIPOPTS_H__ */ diff --git a/third_party/nxp/rt_sdk/lwip/wifi_openthread/lwipopts.h b/third_party/nxp/rt_sdk/lwip/wifi_openthread/lwipopts.h index 92439d19d3e2ed..f45675af6f7166 100644 --- a/third_party/nxp/rt_sdk/lwip/wifi_openthread/lwipopts.h +++ b/third_party/nxp/rt_sdk/lwip/wifi_openthread/lwipopts.h @@ -28,6 +28,31 @@ #define MEMP_NUM_NETCONN (4) #define NETIF_MAX_HWADDR_LEN 8U +// BR specific defines +#define LWIP_IPV6_SCOPES 1 +#define MAX_SOCKETS_UDP 10 +#define MEMP_NUM_UDP_PCB (MAX_SOCKETS_UDP + 2) +#define LWIP_IPV6_NUM_ADDRESSES 8 +#define LWIP_IPV6_FORWARD 1 +#define MEMP_NUM_RAW_PCB (2) +// Note: According to Thread Conformance v1.2.0, a Thread Border Router MUST be able to hold a Multicast Listeners Table +// in memory with at least seventy five (75) entries. +#define MEMP_NUM_MLD6_GROUP 10 + 75 + +// DHCPv6 Prefix Delegation +#define LWIP_IPV6_DHCP6 1 +#define LWIP_IPV6_DHCP6_STATEFUL 1 +#define LWIP_IPV6_DHCP6_PD 1 + +// Header file with lwIP hooks +#define LWIP_HOOK_FILENAME "lwip_hooks.h" + +// Hook for multicast forwarding and other filtering +#define LWIP_HOOK_IP6_CANFORWARD lwipCanForwardHook + +// Hook for filtering of input traffic +#define LWIP_HOOK_IP6_INPUT lwipInputHook + #include "lwipopts_common.h" #endif /* __LWIPOPTS_H__ */ diff --git a/third_party/nxp/rt_sdk/mbedtls/mbedtls.gni b/third_party/nxp/rt_sdk/mbedtls/mbedtls.gni index d5595ab2683f2b..04f628686d29a9 100644 --- a/third_party/nxp/rt_sdk/mbedtls/mbedtls.gni +++ b/third_party/nxp/rt_sdk/mbedtls/mbedtls.gni @@ -77,6 +77,7 @@ template("mbedtls_target") { "${_mbedtls_root}/library/hmac_drbg.c", "${_mbedtls_root}/library/md.c", "${_mbedtls_root}/library/md5.c", + "${_mbedtls_root}/library/nist_kw.c", "${_mbedtls_root}/library/oid.c", "${_mbedtls_root}/library/padlock.c", "${_mbedtls_root}/library/pem.c", @@ -89,6 +90,7 @@ template("mbedtls_target") { "${_mbedtls_root}/library/platform.c", "${_mbedtls_root}/library/platform_util.c", "${_mbedtls_root}/library/poly1305.c", + "${_mbedtls_root}/library/psa_crypto_client.c", "${_mbedtls_root}/library/ripemd160.c", "${_mbedtls_root}/library/rsa.c", "${_mbedtls_root}/library/rsa_internal.c", @@ -99,6 +101,7 @@ template("mbedtls_target") { "${_mbedtls_root}/library/ssl_ciphersuites.c", "${_mbedtls_root}/library/ssl_cli.c", "${_mbedtls_root}/library/ssl_cookie.c", + "${_mbedtls_root}/library/ssl_msg.c", "${_mbedtls_root}/library/ssl_srv.c", "${_mbedtls_root}/library/ssl_ticket.c", "${_mbedtls_root}/library/ssl_tls.c", diff --git a/third_party/nxp/rt_sdk/rt_executable.gni b/third_party/nxp/rt_sdk/rt_executable.gni index 2acfe190a2d960..e1ad6698062939 100644 --- a/third_party/nxp/rt_sdk/rt_executable.gni +++ b/third_party/nxp/rt_sdk/rt_executable.gni @@ -20,7 +20,7 @@ import("${build_root}/toolchain/flashable_executable.gni") template("rt_executable") { output_base_name = get_path_info(invoker.output_name, "name") objcopy_image_name = output_base_name + ".hex" - objcopy_image_format = "srec" + objcopy_image_format = "ihex" objcopy = "arm-none-eabi-objcopy" # Copy flashing dependencies to the output directory so that the output diff --git a/third_party/nxp/rt_sdk/rt_sdk.gni b/third_party/nxp/rt_sdk/rt_sdk.gni index 20cffacc1a150e..9063361f7b9d4d 100644 --- a/third_party/nxp/rt_sdk/rt_sdk.gni +++ b/third_party/nxp/rt_sdk/rt_sdk.gni @@ -24,7 +24,7 @@ import("${nxp_sdk_build_root}/nxp_sdk.gni") # declares some sdk args such as declare_args() { # Location of the rt SDK. - rt_sdk_root = "${chip_root}/third_party/nxp/rt_sdk/repo" + rt_sdk_root = "${chip_root}/third_party/nxp/rt_sdk/repo/sdk-2.15" # Enables Matter over Ethernet support chip_enable_ethernet = false @@ -41,6 +41,10 @@ declare_args() { # Could be used only if k32w0_transceiver is enabled hci_spinel_single_uart = false + # Enables spinel communication over spi + # Could be used only if iwx12_transceiver is enabled + spinel_over_spi = false + # Allows to enable OTW logs to see # firmware download logs for K32W0 transceiver otw_logs_enabled = false @@ -48,7 +52,10 @@ declare_args() { # Defines the RT NVM component supported rt_nvm_component = "littlefs" + # KeyStorage enable only for RW61x + #if(nxp_platform == "rt/rw61x"){ #rt_nvm_component = "key_storage" + #} # Enable wifi PTA support wifi_enable_pta = false @@ -67,6 +74,12 @@ declare_args() { # Enables Watchdog support use_watchdog = 0 + + # SDK 2.15 support + is_sdk_2_15 = false + + # Describe the transceiver detail flags + nxp_transceiver_interface_detail = "WIFI_IW612_BOARD_MURATA_2EL_USD" } declare_args() { @@ -84,6 +97,19 @@ if (!defined(rt_fwk_platform)) { assert(rt_sdk_root != "", "rt_sdk_root must be specified") +# Assuming 2.15 SDK support for RT1170 +if (rt_platform == "rt1060" || rt_platform == "rt1170") { + is_sdk_2_15 = true +} + +if (rt_platform == "rt1060") { + nxp_transceiver_interface_detail = "WIFI_IW612_BOARD_MURATA_2EL_M2" +} + +if (iwx12_transceiver && chip_enable_openthread) { + spinel_over_spi = true +} + rt_sdk_freertos_kernel = "${rt_sdk_root}/rtos/freertos/freertos-kernel" template("rt_transceiver") { @@ -98,8 +124,8 @@ template("rt_transceiver") { if (iw416_transceiver || w8801_transceiver || iwx12_transceiver) { _transceiver_include_dirs += [ # Transceiver files - "${rt_sdk_root}/middleware/wifi_nxp/wifi_bt_firmware", "${rt_sdk_root}/middleware/wifi_nxp/wifidriver", + "${rt_sdk_root}/middleware/wifi_nxp/wifidriver/incl", "${rt_sdk_root}/middleware/sdmmc/common", "${rt_sdk_root}/middleware/sdmmc/sdio", "${rt_sdk_root}/middleware/sdmmc/host/usdhc", @@ -110,7 +136,21 @@ template("rt_transceiver") { "${rt_sdk_root}/middleware/wifi_nxp/incl/port/os", "${rt_sdk_root}/middleware/wifi_nxp/wifidriver", "${rt_sdk_root}/middleware/wifi_nxp/wifidriver/incl", + "${rt_sdk_root}/middleware/wifi_nxp/fwdnld_intf_abs", + "${rt_sdk_root}/middleware/wifi_nxp/sdio_nxp_abs/incl", + "${rt_sdk_root}/middleware/wifi_nxp/firmware_dnld", ] + if (is_sdk_2_15) { + _transceiver_include_dirs += [ + "${rt_sdk_root}/middleware/wifi_nxp/wifi_bt_firmware/nw61x", + "${rt_sdk_root}/middleware/wifi_nxp/fwdnld_intf_abs", + "${rt_sdk_root}/middleware/wifi_nxp/sdio_nxp_abs/incl", + "${rt_sdk_root}/middleware/wifi_nxp/firmware_dnld", + ] + } else { + _transceiver_include_dirs += + [ "${rt_sdk_root}/middleware/wifi_nxp/wifi_bt_firmware" ] + } } libs = [] @@ -145,10 +185,21 @@ template("rt_transceiver") { "${rt_sdk_root}/middleware/sdmmc/host/usdhc/non_blocking/fsl_sdmmc_host.c", "${rt_sdk_root}/middleware/sdmmc/osa/fsl_sdmmc_osa.c", "${rt_sdk_root}/middleware/sdmmc/sdio/fsl_sdio.c", - "${rt_sdk_root}/middleware/wifi_nxp/wifidriver/firmware_dnld.c", - "${rt_sdk_root}/middleware/wifi_nxp/wifidriver/mlan_sdio.c", "${rt_sdk_root}/middleware/wifi_nxp/wifidriver/sdio.c", ] + if (is_sdk_2_15) { + sources += [ + "${rt_sdk_root}/middleware/wifi_nxp/firmware_dnld/firmware_dnld.c", + "${rt_sdk_root}/middleware/wifi_nxp/fwdnld_intf_abs/fwdnld_intf_abs.c", + "${rt_sdk_root}/middleware/wifi_nxp/sdio_nxp_abs/fwdnld_sdio.c", + "${rt_sdk_root}/middleware/wifi_nxp/sdio_nxp_abs/mlan_sdio.c", + ] + } else { + sources += [ + "${rt_sdk_root}/middleware/wifi_nxp/wifidriver/firmware_dnld.c", + "${rt_sdk_root}/middleware/wifi_nxp/wifidriver/mlan_sdio.c", + ] + } } if (!defined(public_configs)) { @@ -191,7 +242,6 @@ template("rt_wifi") { defines += [ "CHIP_DEVICE_CONFIG_ENABLE_WPA", "CONFIG_IPV6=1", # missing in wifi_config.h - "CONFIG_MAX_IPV6_ADDRESSES=5", # missing in wifi_config.h ] if (wifi_enable_pta) { @@ -223,8 +273,6 @@ template("rt_wifi") { "${rt_sdk_root}/middleware/wifi_nxp/dhcpd/dhcp-server-main.c", "${rt_sdk_root}/middleware/wifi_nxp/dhcpd/dhcp-server.c", "${rt_sdk_root}/middleware/wifi_nxp/dhcpd/dns-server.c", - "${rt_sdk_root}/middleware/wifi_nxp/port/lwip/net.c", - "${rt_sdk_root}/middleware/wifi_nxp/port/lwip/wifi_netif.c", "${rt_sdk_root}/middleware/wifi_nxp/port/os/os.c", "${rt_sdk_root}/middleware/wifi_nxp/wifidriver/mlan_11ac.c", "${rt_sdk_root}/middleware/wifi_nxp/wifidriver/mlan_11d.c", @@ -305,6 +353,7 @@ template("rt_sdk") { "${rt_core_sdk_root}/components/wifi_bt_module/AzureWave/tx_pwr_limits", "${rt_core_sdk_root}/components/wifi_bt_module/Murata/tx_pwr_limits", "${rt_core_sdk_root}/components/wifi_bt_module/template", + "${rt_core_sdk_root}/components/wifi_bt_module/incl", "${rt_core_sdk_root}/components/messaging", "${rt_core_sdk_root}/components/mem_manager", "${rt_core_sdk_root}/components/flash/mflash", @@ -320,6 +369,8 @@ template("rt_sdk") { "${rt_sdk_root}/middleware/wireless/framework/FSAbstraction", "${rt_sdk_root}/middleware/wireless/framework/KeyStorage", + #"${rt_sdk_root}/middleware/wireless/framework/DBG", + #littlefs "${rt_sdk_root}/middleware/littlefs", ] @@ -382,11 +433,6 @@ template("rt_sdk") { "PRINTF_ADVANCED_ENABLE=1", ] - if (rt_platform != "rw61x") { - # This adds a temporary workakound to fix mbdetls concurrent access to crypto hardware - defines += [ "RT_MATTER_SUPPORT" ] - } - if (use_watchdog != 0) { defines += [ "WATCHDOG_ALLOWED" ] } @@ -423,6 +469,11 @@ template("rt_sdk") { # No OSA main task "FSL_OSA_MAIN_FUNC_ENABLE=0", ] + if (chip_enable_openthread) { + defines += [ "CONFIG_MAX_IPV6_ADDRESSES=8" ] + } else { + defines += [ "CONFIG_MAX_IPV6_ADDRESSES=5" ] + } } if (chip_enable_ethernet) { @@ -451,7 +502,7 @@ template("rt_sdk") { if (iwx12_transceiver) { defines += [ - "WIFI_IW61x_BOARD_RD_USD", + nxp_transceiver_interface_detail, "SDIO_ENABLED", ] } @@ -523,6 +574,10 @@ template("rt_sdk") { cflags += [ "-isystem" + rebase_path(include_dir, root_build_dir) ] } + if (is_sdk_2_15) { + defines += [ "NXP_SDK_2_15_SUPPORT" ] + } + #Adding pre-include files cflags += [ "-include" + rebase_path( "${nxp_sdk_build_root}/${nxp_sdk_name}/transceiver/app_transceiver_config.h", @@ -589,6 +644,11 @@ template("rt_sdk") { "${rt_sdk_root}/middleware/wireless/framework/FileCache/fwk_file_cache.c", "${rt_sdk_root}/middleware/wireless/framework/KeyStorage/fwk_key_storage.c", ] + sources += [ + #littlefs + "${rt_sdk_root}/middleware/littlefs/lfs.c", + "${rt_sdk_root}/middleware/littlefs/lfs_util.c", + ] } if (rt_nvm_component == "littlefs") { diff --git a/third_party/nxp/rt_sdk/rw61x/BUILD.gn b/third_party/nxp/rt_sdk/rw61x/BUILD.gn index 007d83ee40c036..2b439c6e55ef14 100644 --- a/third_party/nxp/rt_sdk/rw61x/BUILD.gn +++ b/third_party/nxp/rt_sdk/rw61x/BUILD.gn @@ -21,33 +21,80 @@ import("${nxp_sdk_build_root}/nxp_sdk.gni") # Allows to get various RT gn options import("${nxp_sdk_build_root}/${nxp_sdk_name}/${nxp_sdk_name}.gni") +import( + "${nxp_sdk_build_root}/${nxp_sdk_name}/${rt_platform}/${rt_platform}.gni") + #allows to get common NXP SDK gn options import("${nxp_sdk_build_root}/nxp_sdk.gni") -group("nxp_mbedtls") { - public_deps = [] - # mbedtls port - if (rw610_mbedtls_port_els_pkc) { - public_deps += - [ "${nxp_sdk_build_root}/${nxp_sdk_name}:nxp_els_pkc_mbedtls" ] +import("${nxp_sdk_build_root}/${nxp_sdk_name}/mbedtls/mbedtls.gni") +rt_mbedtls_root = "${rt_sdk_root}/middleware/mbedtls" + +config("mbedtls_els_pkc_config") { + include_dirs = [ + "${rt_mbedtls_root}/port/els", + "${rt_mbedtls_root}/port/pkc", + ] + defines = [ "MBEDTLS_PORT_INCLUDE=" ] + + if (chip_enable_openthread) { + defines += [ "MBEDTLS_CONFIG_FILE=" ] } else { - public_deps += [ "${nxp_sdk_build_root}/${nxp_sdk_name}:nxp_ksdk_mbedtls" ] + defines += [ "MBEDTLS_CONFIG_FILE=" ] + } +} + +mbedtls_target("nxp_els_pkc_mbedtls") { + sources = [] + public_configs = [ ":mbedtls_els_pkc_config" ] + public_deps = [ nxp_sdk_driver_target ] + sources += [ + # els port + "${rt_mbedtls_root}/port/els/aes_alt.c", + "${rt_mbedtls_root}/port/els/cbc_mac_alt.c", + "${rt_mbedtls_root}/port/els/cmac_alt.c", + "${rt_mbedtls_root}/port/els/ctr_drbg_alt.c", + "${rt_mbedtls_root}/port/els/els_mbedtls.c", + "${rt_mbedtls_root}/port/els/entropy_poll_alt.c", + "${rt_mbedtls_root}/port/els/gcm_alt.c", + "${rt_mbedtls_root}/port/els/sha256_alt.c", + "${rt_mbedtls_root}/port/els/sha512_alt.c", + + # pkc port + "${rt_mbedtls_root}/port/pkc/ecc_alt.c", + "${rt_mbedtls_root}/port/pkc/ecdh_alt.c", + "${rt_mbedtls_root}/port/pkc/ecdsa_alt.c", + "${rt_mbedtls_root}/port/pkc/els_pkc_mbedtls.c", + "${rt_mbedtls_root}/port/pkc/rsa_alt.c", + ] + if (chip_enable_openthread) { + public_deps += [ "${openthread_root}/src/core:libopenthread_core_headers" ] } } group("nxp_sdk_mbedtls_config") { - public_configs = [] - - # mbedtls port - if (rw610_mbedtls_port_els_pkc) { - public_configs += - [ "${nxp_sdk_build_root}/${nxp_sdk_name}:nxp_els_pkc_mbedtls_config" ] - public_configs += - [ "${nxp_sdk_build_root}/${nxp_sdk_name}:mbedtls_els_pkc_config" ] - } else { - public_configs += - [ "${nxp_sdk_build_root}/${nxp_sdk_name}:nxp_ksdk_mbedtls_config" ] - public_configs += - [ "${nxp_sdk_build_root}/${nxp_sdk_name}:mbedtls_ksdk_config" ] + public_configs = [ ":mbedtls_els_pkc_config" ] +} + +group("nxp_mbedtls") { + public_deps = [ ":nxp_els_pkc_mbedtls" ] +} + +source_set("nxp_sdk_mbedtls_dep") { + # Add here SDK source files which have a dependency on mbedtls + # this approach helps avoid circular dependencies between mbedtls and rw61x_sdk_drivers targets + if (chip_enable_secure_dac_private_key_storage == 0) { + sources = [ "${rt_sdk_root}/middleware/wireless/framework/FactoryDataProvider/fwk_factory_data_provider.c" ] } + + deps = [ + "${nxp_sdk_build_root}/${nxp_sdk_name}/${rt_platform}:nxp_mbedtls", + "${nxp_sdk_build_root}/${nxp_sdk_name}/${rt_platform}:nxp_sdk_mbedtls_config", + nxp_sdk_driver_target, + ] +} + +group("nxp_sdk") { + # Add SDK's source set which depends on mbedtls + public_deps = [ ":nxp_sdk_mbedtls_dep" ] } diff --git a/third_party/nxp/rt_sdk/rw61x/rw61x.gni b/third_party/nxp/rt_sdk/rw61x/rw61x.gni index 24251d3a6def36..c15b4dbabf5e22 100644 --- a/third_party/nxp/rt_sdk/rw61x/rw61x.gni +++ b/third_party/nxp/rt_sdk/rw61x/rw61x.gni @@ -244,56 +244,60 @@ template("rw61x_sdk_drivers") { ] _rw61x_sdk_drivers_include_dirs += [ "${ELS_PKC_CL_PATH}", - "${ELS_PKC_CL_PATH}/src/platforms/redfinchSdk_sample", - "${ELS_PKC_CL_PATH}/src/platforms/redfinchSdk_sample/inc", - "${ELS_PKC_CL_PATH}/src/inc", - "${ELS_PKC_CL_PATH}/src/inc/impl", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClHash/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClSession/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClSession/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslFlowProtection/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClCore/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslSecureCounter/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslCPreProcessor/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClMemory/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslMemory/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslParamIntegrity/inc", "${ELS_PKC_CL_PATH}/src/compiler", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClRandom/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClRandom/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClRandomModes/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClRandomModes/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClAead/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClAead/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/inc/internal", "${ELS_PKC_CL_PATH}/src/comps/mcuxClAes/inc", "${ELS_PKC_CL_PATH}/src/comps/mcuxClAes/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClKey/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipher/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipher/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipherModes/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipherModes/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClCore/inc", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/inc", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClTrng/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClTrng/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClPrng/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClPrng/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClMath/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClMath/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClPkc/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClRsa/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHash/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHashModes/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHashModes/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHmac/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClKey/inc", "${ELS_PKC_CL_PATH}/src/comps/mcuxClMac/inc", "${ELS_PKC_CL_PATH}/src/comps/mcuxClMac/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClMath/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClMath/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClMemory/inc", "${ELS_PKC_CL_PATH}/src/comps/mcuxClPadding/inc", "${ELS_PKC_CL_PATH}/src/comps/mcuxClPadding/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClAead/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClAead/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipherModes/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipherModes/inc/internal", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipher/inc", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipher/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClPkc/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClPkc/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClPrng/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClPrng/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClRandom/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClRandom/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClRandomModes/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClRandomModes/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClRsa/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClSession/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClSession/inc/internal", "${ELS_PKC_CL_PATH}/src/comps/mcuxClTrng/inc", "${ELS_PKC_CL_PATH}/src/comps/mcuxClTrng/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslCPreProcessor/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslDataIntegrity/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslFlowProtection/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslMemory/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslMemory/inc/internal", + "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslParamIntegrity/inc", + "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslSecureCounter/inc", + "${ELS_PKC_CL_PATH}/src/inc", + "${ELS_PKC_CL_PATH}/src/inc/impl", + "${ELS_PKC_CL_PATH}/src/platforms/rw61x", + "${ELS_PKC_CL_PATH}/src/platforms/rw61x/inc", ] } @@ -302,7 +306,7 @@ template("rw61x_sdk_drivers") { "${rt_sdk_root}/middleware/wifi_nxp/wifidriver", # Include lpm.h and host_sleep.h files - "${rt_sdk_root}/boards/rdrw612bga/wifi_examples/wifi_cli", + "${rt_sdk_root}/boards/rdrw612bga/wifi_examples/common/lpm", # wifi config file path "${WIFI_BT_TEMPLATE_PATH}", @@ -444,9 +448,6 @@ template("rw61x_sdk_drivers") { # framework coex file to initialize controllers "${rt_sdk_root}/middleware/wireless/framework/platform/${rt_fwk_platform}/fwk_platform_coex.c", - - # other framework files - "${rt_sdk_root}/middleware/wireless/framework/FactoryDataProvider/fwk_factory_data_provider.c", ] if (sdk_fsl_assert_support) { @@ -457,30 +458,41 @@ template("rw61x_sdk_drivers") { # els_pkc component for mbedtls els_pkc port sources += [ "${ELS_PKC_CL_PATH}/src/comps/mcuxClAead/src/mcuxClAead.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_ELS_AesCcm.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_ELS_AesGcm.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_ELS_CcmEngineAes.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_ELS_Constants.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_ELS_GcmEngineAes.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_ELS_MultiPart.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_ELS_OneShot.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_Els_AesCcm.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_Els_AesGcm.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_Els_CcmEngineAes.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_Els_GcmEngineAes.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_Els_Modes.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_Els_Multipart.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClAeadModes/src/mcuxClAeadModes_Els_Oneshot.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClAes/src/mcuxClAes_KeyTypes.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipher/src/mcuxClCipher.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipherModes/src/mcuxClCipherModes_ELS_Aes.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipherModes/src/mcuxClCipherModes_ELS_EngineAes.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipherModes/src/mcuxClCipherModes_Els_Aes.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipherModes/src/mcuxClCipherModes_Els_EngineAes.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipherModes/src/mcuxClCipherModes_Helper.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClCipherModes/src/mcuxClCipherModes_Modes.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Constants.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_GenerateKeyPair.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_GenerateKeyPair_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_GenerateSignature.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_GenerateSignatureMode.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_GenerateSignature_FUP.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_InitPrivKeyInputMode.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_Internal_CalcHashModN.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_Internal_CalcHashModN_FUP.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_Internal_DecodePoint_Ed25519.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_Internal_DecodePoint_Ed448.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_Internal_DecodePoint_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_Internal_SetupEnvironment.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_Internal_SignatureMechanisms.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_EdDSA_VerifySignature.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Internal_BlindedScalarMult.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Internal_Convert_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Internal_GenerateMultiplicativeBlinding.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Internal_InterleaveScalar.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Internal_InterleaveTwoScalars.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Internal_Interleave_FUP.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Internal_PointComparison_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Internal_RecodeAndReorderScalar.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Internal_SetupEnvironment.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Internal_SetupEnvironment_FUP.c", @@ -501,6 +513,7 @@ template("rw61x_sdk_drivers") { "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_TwEd_Internal_PlainVarScalarMult.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_TwEd_Internal_PointArithmeticEd25519.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_TwEd_Internal_PointArithmeticEd25519_FUP.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_TwEd_Internal_PointSubtraction_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_TwEd_Internal_PointValidation_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_TwEd_Internal_PrecPointImportAndValidate.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_TwEd_Internal_VarScalarMult.c", @@ -510,6 +523,7 @@ template("rw61x_sdk_drivers") { "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_WeierECC_Internal_GenerateDomainParams_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_WeierECC_Internal_SetupEnvironment.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_Internal_ConvertPoint_FUP.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_Internal_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_Internal_KeyGen.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_Internal_KeyGen_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_Internal_PointArithmetic.c", @@ -521,6 +535,7 @@ template("rw61x_sdk_drivers") { "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_Internal_SecurePointMult_CoZMontLadder_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_Internal_SetupEnvironment.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_KeyGen.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_KeyGen_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_PointMult.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_PointMult_FUP.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEcc/src/mcuxClEcc_Weier_Sign.c", @@ -531,9 +546,8 @@ template("rw61x_sdk_drivers") { "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/src/mcuxClEls_Cipher.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/src/mcuxClEls_Cmac.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/src/mcuxClEls_Common.c", - - #"${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/src/mcuxClEls_Crc.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/src/mcuxClEls_Ecc.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/src/mcuxClEls_GlitchDetector.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/src/mcuxClEls_Hash.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/src/mcuxClEls_Hmac.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClEls/src/mcuxClEls_Kdf.c", @@ -542,18 +556,21 @@ template("rw61x_sdk_drivers") { "${ELS_PKC_CL_PATH}/src/comps/mcuxClHash/src/mcuxClHash_api_multipart_common.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClHash/src/mcuxClHash_api_multipart_compute.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClHash/src/mcuxClHash_api_oneshot_compute.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClHash/src/mcuxClHash_core_els_sha2.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClHash/src/mcuxClHash_internal_els_sha2.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHashModes/src/mcuxClHashModes_Core_els_sha2.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHashModes/src/mcuxClHashModes_Internal_els_sha2.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHmac/src/mcuxClHmac_Els.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHmac/src/mcuxClHmac_Functions.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHmac/src/mcuxClHmac_Helper.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHmac/src/mcuxClHmac_KeyTypes.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHmac/src/mcuxClHmac_Modes.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClHmac/src/mcuxClHmac_Sw.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClKey/src/mcuxClKey.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClKey/src/mcuxClKey_KeyTypes.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClKey/src/mcuxClKey_Protection.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClMac/src/mcuxClMac.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/src/mcuxClMacModes.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/src/mcuxClMacModes_ELS_CBCMAC.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/src/mcuxClMacModes_ELS_CMAC.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/src/mcuxClMacModes_ELS_Functions.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/src/mcuxClMacModes_ELS_HMAC.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/src/mcuxClMacModes_Helper.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/src/mcuxClMacModes_Els_Cbcmac.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/src/mcuxClMacModes_Els_Cmac.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/src/mcuxClMacModes_Els_Functions.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClMacModes/src/mcuxClMacModes_Modes.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClMath/src/mcuxClMath_ExactDivide.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClMath/src/mcuxClMath_ExactDivideOdd.c", @@ -617,14 +634,15 @@ template("rw61x_sdk_drivers") { "${ELS_PKC_CL_PATH}/src/comps/mcuxClRsa/src/mcuxClRsa_Verify.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClRsa/src/mcuxClRsa_VerifyE.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxClSession/src/mcuxClSession.c", - "${ELS_PKC_CL_PATH}/src/comps/mcuxClTrng/src/mcuxClTrng_RNG4.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxClTrng/src/mcuxClTrng_SA_TRNG.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslMemory/src/mcuxCsslMemory_Clear.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslMemory/src/mcuxCsslMemory_Compare.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslMemory/src/mcuxCsslMemory_Copy.c", + "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslMemory/src/mcuxCsslMemory_Internal_SecureCompare_Stub.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslMemory/src/mcuxCsslMemory_Set.c", "${ELS_PKC_CL_PATH}/src/comps/mcuxCsslParamIntegrity/src/mcuxCsslParamIntegrity.c", - "${ELS_PKC_CL_PATH}/src/platforms/redfinchSdk_sample/mcux_els.c", - "${ELS_PKC_CL_PATH}/src/platforms/redfinchSdk_sample/mcux_pkc.c", + "${ELS_PKC_CL_PATH}/src/platforms/rw61x/mcux_els.c", + "${ELS_PKC_CL_PATH}/src/platforms/rw61x/mcux_pkc.c", ] } @@ -675,9 +693,6 @@ template("rw61x_sdk_drivers") { public_configs += [ ":${target_name}_config" ] public_deps += [ nxp_sdk_target ] - # The FactoryDataProvider/fwk_factory_data_provider.c component has a dependency to mbedtls - deps += [ "${nxp_sdk_build_root}/${nxp_sdk_name}/${rt_platform}:nxp_sdk_mbedtls_config" ] - if (chip_enable_openthread) { #Dep to ot header files required for mbedtls as mbedtls config file has a dependency to ot deps += [ "${openthread_root}/src/core:libopenthread_core_headers" ] diff --git a/third_party/nxp/rt_sdk/sdk_hook/fatfs/config/ffconf.h b/third_party/nxp/rt_sdk/sdk_hook/fatfs/config/ffconf.h new file mode 100644 index 00000000000000..7138317b86f169 --- /dev/null +++ b/third_party/nxp/rt_sdk/sdk_hook/fatfs/config/ffconf.h @@ -0,0 +1,302 @@ +/*----------------------------------------------------------------------------/ +/ FatFs - Generic FAT Filesystem Module R0.14b / +/-----------------------------------------------------------------------------/ +/ +/ Copyright (C) 2021, ChaN, all right reserved. +/ Copyright 2023 NXP +/ +/ +/ FatFs module is an open source software. Redistribution and use of FatFs in +/ source and binary forms, with or without modification, are permitted provided +/ that the following condition is met: +/ +/ 1. Redistributions of source code must retain the above copyright notice, +/ this condition and the following disclaimer. +/ +/ This software is provided by the copyright holder and contributors "AS IS" +/ and any warranties related to this software are DISCLAIMED. +/ The copyright owner or contributors be NOT LIABLE for any damages caused +/ by use of this software. +/ +/----------------------------------------------------------------------------*/ + +#ifndef _FFCONF_H_ +#define _FFCONF_H_ + +/*---------------------------------------------------------------------------/ +/ FatFs Functional Configurations +/---------------------------------------------------------------------------*/ + +#define FFCONF_DEF 80286 /* Revision ID */ + +/*---------------------------------------------------------------------------/ +/ MSDK adaptation configuration +/---------------------------------------------------------------------------*/ +#define USB_DISK_ENABLE + +/*---------------------------------------------------------------------------/ +/ Function Configurations +/---------------------------------------------------------------------------*/ + +#define FF_FS_READONLY 0 +/* This option switches read-only configuration. (0:Read/Write or 1:Read-only) +/ Read-only configuration removes writing API functions, f_write(), f_sync(), +/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree() +/ and optional writing functions as well. */ + +#define FF_FS_MINIMIZE 0 +/* This option defines minimization level to remove some basic API functions. +/ +/ 0: Basic functions are fully enabled. +/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename() +/ are removed. +/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1. +/ 3: f_lseek() function is removed in addition to 2. */ + +#define FF_USE_STRFUNC 1 +/* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf(). +/ +/ 0: Disable string functions. +/ 1: Enable without LF-CRLF conversion. +/ 2: Enable with LF-CRLF conversion. */ + +#define FF_USE_FIND 0 +/* This option switches filtered directory read functions, f_findfirst() and +/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */ + +#define FF_USE_MKFS 0 +/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */ + +#define FF_USE_FASTSEEK 0 +/* This option switches fast seek function. (0:Disable or 1:Enable) */ + +#define FF_USE_EXPAND 0 +/* This option switches f_expand function. (0:Disable or 1:Enable) */ + +#define FF_USE_CHMOD 1 +/* This option switches attribute manipulation functions, f_chmod() and f_utime(). +/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */ + +#define FF_USE_LABEL 0 +/* This option switches volume label functions, f_getlabel() and f_setlabel(). +/ (0:Disable or 1:Enable) */ + +#define FF_USE_FORWARD 0 +/* This option switches f_forward() function. (0:Disable or 1:Enable) */ + +/*---------------------------------------------------------------------------/ +/ Locale and Namespace Configurations +/---------------------------------------------------------------------------*/ + +#define FF_CODE_PAGE 932 +/* This option specifies the OEM code page to be used on the target system. +/ Incorrect code page setting can cause a file open failure. +/ +/ 437 - U.S. +/ 720 - Arabic +/ 737 - Greek +/ 771 - KBL +/ 775 - Baltic +/ 850 - Latin 1 +/ 852 - Latin 2 +/ 855 - Cyrillic +/ 857 - Turkish +/ 860 - Portuguese +/ 861 - Icelandic +/ 862 - Hebrew +/ 863 - Canadian French +/ 864 - Arabic +/ 865 - Nordic +/ 866 - Russian +/ 869 - Greek 2 +/ 932 - Japanese (DBCS) +/ 936 - Simplified Chinese (DBCS) +/ 949 - Korean (DBCS) +/ 950 - Traditional Chinese (DBCS) +/ 0 - Include all code pages above and configured by f_setcp() +*/ + +#define FF_USE_LFN 1 +#define FF_MAX_LFN 255 +/* The FF_USE_LFN switches the support for LFN (long file name). +/ +/ 0: Disable LFN. FF_MAX_LFN has no effect. +/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe. +/ 2: Enable LFN with dynamic working buffer on the STACK. +/ 3: Enable LFN with dynamic working buffer on the HEAP. +/ +/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function +/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and +/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled. +/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can +/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN +/ specification. +/ When use stack for the working buffer, take care on stack overflow. When use heap +/ memory for the working buffer, memory management functions, ff_memalloc() and +/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */ + +#define FF_LFN_UNICODE 0 +/* This option switches the character encoding on the API when LFN is enabled. +/ +/ 0: ANSI/OEM in current CP (TCHAR = char) +/ 1: Unicode in UTF-16 (TCHAR = WCHAR) +/ 2: Unicode in UTF-8 (TCHAR = char) +/ 3: Unicode in UTF-32 (TCHAR = DWORD) +/ +/ Also behavior of string I/O functions will be affected by this option. +/ When LFN is not enabled, this option has no effect. */ + +#define FF_LFN_BUF 255 +#define FF_SFN_BUF 12 +/* This set of options defines size of file name members in the FILINFO structure +/ which is used to read out directory items. These values should be suffcient for +/ the file names to read. The maximum possible length of the read file name depends +/ on character encoding. When LFN is not enabled, these options have no effect. */ + +#define FF_STRF_ENCODE 3 +/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(), +/ f_putc(), f_puts and f_printf() convert the character encoding in it. +/ This option selects assumption of character encoding ON THE FILE to be +/ read/written via those functions. +/ +/ 0: ANSI/OEM in current CP +/ 1: Unicode in UTF-16LE +/ 2: Unicode in UTF-16BE +/ 3: Unicode in UTF-8 +*/ + +#define FF_FS_RPATH 0 +/* This option configures support for relative path. +/ +/ 0: Disable relative path and remove related functions. +/ 1: Enable relative path. f_chdir() and f_chdrive() are available. +/ 2: f_getcwd() function is available in addition to 1. +*/ + +/*---------------------------------------------------------------------------/ +/ Drive/Volume Configurations +/---------------------------------------------------------------------------*/ + +#define FF_VOLUMES 3 +/* Number of volumes (logical drives) to be used. (1-10) */ + +#define FF_STR_VOLUME_ID 0 +#define FF_VOLUME_STRS "RAM", "NAND", "CF", "SD", "SD2", "USB", "USB2", "USB3" +/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings. +/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive +/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each +/ logical drives. Number of items must not be less than FF_VOLUMES. Valid +/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are +/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is +/ not defined, a user defined volume string table needs to be defined as: +/ +/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",... +*/ + +#define FF_MULTI_PARTITION 0 +/* This option switches support for multiple volumes on the physical drive. +/ By default (0), each logical drive number is bound to the same physical drive +/ number and only an FAT volume found on the physical drive will be mounted. +/ When this function is enabled (1), each logical drive number can be bound to +/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk() +/ funciton will be available. */ + +#define FF_MIN_SS 512 +#define FF_MAX_SS 4096 +/* This set of options configures the range of sector size to be supported. (512, +/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and +/ harddisk. But a larger value may be required for on-board flash memory and some +/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured +/ for variable sector size mode and disk_ioctl() function needs to implement +/ GET_SECTOR_SIZE command. */ + +#define FF_LBA64 0 +/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable) +/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */ + +#define FF_MIN_GPT 0x100000000 +/* Minimum number of sectors to switch GPT format to create partition in f_mkfs and +/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */ + +#define FF_USE_TRIM 0 +/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable) +/ To enable Trim function, also CTRL_TRIM command should be implemented to the +/ disk_ioctl() function. */ + +/*---------------------------------------------------------------------------/ +/ System Configurations +/---------------------------------------------------------------------------*/ + +#define FF_FS_TINY 0 +/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny) +/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes. +/ Instead of private sector buffer eliminated from the file object, common sector +/ buffer in the filesystem object (FATFS) is used for the file data transfer. */ + +#define FF_FS_EXFAT 0 +/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable) +/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1) +/ Note that enabling exFAT discards ANSI C (C89) compatibility. */ + +#define FF_FS_NORTC 1 +#define FF_NORTC_MON 1 +#define FF_NORTC_MDAY 1 +#define FF_NORTC_YEAR 2018 +/* The option FF_FS_NORTC switches timestamp functiton. If the system does not have +/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable +/ the timestamp function. Every object modified by FatFs will have a fixed timestamp +/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time. +/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be +/ added to the project to read current time form real-time clock. FF_NORTC_MON, +/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect. +/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */ + +#define FF_FS_NOFSINFO 0 +/* If you need to know correct free space on the FAT32 volume, set bit 0 of this +/ option, and f_getfree() function at first time after volume mount will force +/ a full FAT scan. Bit 1 controls the use of last allocated cluster number. +/ +/ bit0=0: Use free cluster count in the FSINFO if available. +/ bit0=1: Do not trust free cluster count in the FSINFO. +/ bit1=0: Use last allocated cluster number in the FSINFO if available. +/ bit1=1: Do not trust last allocated cluster number in the FSINFO. +*/ + +#define FF_FS_LOCK 0 +/* The option FF_FS_LOCK switches file lock function to control duplicated file open +/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY +/ is 1. +/ +/ 0: Disable file lock function. To avoid volume corruption, application program +/ should avoid illegal open, remove and rename to the open objects. +/ >0: Enable file lock function. The value defines how many files/sub-directories +/ can be opened simultaneously under file lock control. Note that the file +/ lock control is independent of re-entrancy. */ + +#define FF_FS_REENTRANT 0 +#define FF_FS_TIMEOUT 5000 +#if FF_FS_REENTRANT +#include "FreeRTOS.h" +#include "semphr.h" +#define FF_SYNC_t SemaphoreHandle_t +#endif +/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs +/ module itself. Note that regardless of this option, file access to different +/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs() +/ and f_fdisk() function, are always not re-entrant. Only file/directory access +/ to the same volume is under control of this function. +/ +/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect. +/ 1: Enable re-entrancy. Also user provided synchronization handlers, +/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj() +/ function, must be added to the project. Samples are available in +/ option/syscall.c. +/ +/ The FF_FS_TIMEOUT defines timeout period in unit of time tick. +/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*, +/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be +/ included somewhere in the scope of ff.h. */ + +/*--- End of configuration options ---*/ + +#endif /* _FFCONF_H_ */ diff --git a/third_party/nxp/rt_sdk/sdk_hook/zephyr/bluetooth/hci.h b/third_party/nxp/rt_sdk/sdk_hook/zephyr/bluetooth/hci.h new file mode 100644 index 00000000000000..effc18d6095571 --- /dev/null +++ b/third_party/nxp/rt_sdk/sdk_hook/zephyr/bluetooth/hci.h @@ -0,0 +1,17 @@ +/* + * Copyright 2023 NXP + * All rights reserved. + * + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#if defined(__cplusplus) +extern "C" { +#endif /* _cplusplus */ + +#include + +#if defined(__cplusplus) +} +#endif diff --git a/third_party/nxp/rt_sdk/transceiver/app_transceiver_config.h b/third_party/nxp/rt_sdk/transceiver/app_transceiver_config.h index 2422b61dbf0b1c..c4dd38750c2bd0 100644 --- a/third_party/nxp/rt_sdk/transceiver/app_transceiver_config.h +++ b/third_party/nxp/rt_sdk/transceiver/app_transceiver_config.h @@ -9,8 +9,9 @@ #define CONTROLLER_ID kUSB_ControllerEhci0 #if defined(WIFI_IW416_BOARD_AW_AM457_USD) || defined(WIFI_IW416_BOARD_AW_AM510_USD) || \ - defined(WIFI_88W8987_BOARD_AW_CM358_USD) || defined(WIFI_IW61x_BOARD_RD_USD) || defined(WIFI_BOARD_RW610) || \ - defined(WIFI_88W8801_BOARD_MURATA_2DS_USD) + defined(WIFI_88W8987_BOARD_AW_CM358_USD) || defined(WIFI_IW612_BOARD_RD_USD) || defined(WIFI_BOARD_RW610) || \ + defined(WIFI_88W8801_BOARD_MURATA_2DS_USD) || defined(WIFI_IW612_BOARD_MURATA_2EL_USD) || \ + defined(WIFI_IW612_BOARD_MURATA_2EL_M2) #define WIFI_TRANSCEIVER_SUPPORT 1 #else #define WIFI_TRANSCEIVER_SUPPORT 0 @@ -27,9 +28,16 @@ #endif /* CHIP_DEVICE_CONFIG_ENABLE_WPA || CHIP_DEVICE_CONFIG_ENABLE_THREAD */ #if WIFI_TRANSCEIVER_SUPPORT +#ifdef NXP_SDK_2_15_SUPPORT +/* comes from: /components/wifi_bt_module/incl/wifi_bt_module_config.h */ +#include "wifi_bt_module_config.h" +#else #define NOT_DEFINE_DEFAULT_WIFI_MODULE -/* app_config.h comes from: /components/wifi_bt_module/template/wifi_config.h */ +/* app_config.h comes from: /components/wifi_bt_module/template */ #include "app_config.h" +#endif +/* comes from: /components/wifi_bt_module/template/wifi_config.h */ +#include "wifi_config.h" #endif /* WIFI_TRANSCEIVER_SUPPORT */ #if CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE diff --git a/third_party/openthread/ot-nxp b/third_party/openthread/ot-nxp index 0a8fcca1828cac..2128e8dbe8af40 160000 --- a/third_party/openthread/ot-nxp +++ b/third_party/openthread/ot-nxp @@ -1 +1 @@ -Subproject commit 0a8fcca1828cac3e3d70577010c365b4dc8e3d66 +Subproject commit 2128e8dbe8af40d0cce55fc514a39a0b1376a95b diff --git a/third_party/openthread/platforms/nxp/k32w/k32w0/BUILD.gn b/third_party/openthread/platforms/nxp/k32w/k32w0/BUILD.gn index a67158170d0a18..7ebd860e571371 100644 --- a/third_party/openthread/platforms/nxp/k32w/k32w0/BUILD.gn +++ b/third_party/openthread/platforms/nxp/k32w/k32w0/BUILD.gn @@ -14,11 +14,13 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") -import("//build_overrides/k32w0_sdk.gni") +import("//build_overrides/nxp_sdk.gni") import("//build_overrides/openthread.gni") import("${build_root}/config/compiler/compiler.gni") -import("${chip_root}/third_party/nxp/k32w0_sdk/k32w0_sdk.gni") +import("${nxp_sdk_build_root}/nxp_sdk.gni") + +import("${nxp_sdk_build_root}/${nxp_sdk_name}/${nxp_sdk_name}.gni") openthread_nxp_root = "${chip_root}/third_party/openthread/ot-nxp" @@ -90,8 +92,8 @@ source_set("libopenthread-k32w0") { public_deps = [ ":openthread_core_config_k32w0", - "${k32w0_sdk_build_root}:k32w0_sdk", - "${k32w0_sdk_build_root}:mbedtls", + "${nxp_sdk_build_root}:nxp_sdk", + "${nxp_sdk_build_root}/${nxp_sdk_name}:mbedtls", "${openthread_root}/src/core:libopenthread_core_headers", "../../..:libopenthread-platform", "../../..:libopenthread-platform-utils", diff --git a/third_party/openthread/platforms/nxp/k32w/k32w1/BUILD.gn b/third_party/openthread/platforms/nxp/k32w/k32w1/BUILD.gn index 446768b23b778c..b004389a09fa64 100644 --- a/third_party/openthread/platforms/nxp/k32w/k32w1/BUILD.gn +++ b/third_party/openthread/platforms/nxp/k32w/k32w1/BUILD.gn @@ -30,7 +30,6 @@ config("openthread_k32w1_config") { "OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE=1", "OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE=1", "MBEDTLS_ENTROPY_HARDWARE_ALT=1", - "OPENTHREAD_PLATFORM_CORE_CONFIG_FILE=\"app/project_include/OpenThreadConfig.h\"", "MBEDTLS_THREADING_C=1", "MBEDTLS_THREADING_ALT=1", ] diff --git a/third_party/openthread/platforms/nxp/rt/rw61x/BUILD.gn b/third_party/openthread/platforms/nxp/rt/rw61x/BUILD.gn index 98cc50084dfd50..318276cc0aa666 100644 --- a/third_party/openthread/platforms/nxp/rt/rw61x/BUILD.gn +++ b/third_party/openthread/platforms/nxp/rt/rw61x/BUILD.gn @@ -28,6 +28,7 @@ openthread_nxp_root = "${chip_root}/third_party/openthread/ot-nxp" config("openthread_rw61x_config") { include_dirs = [ "${openthread_nxp_root}/src/common", + "${openthread_nxp_root}/src/common/br", "${openthread_nxp_root}/src/rw/rw612", "${openthread_nxp_root}/third_party/mbedtls/configs", "${openthread_root}/third_party/mbedtls", @@ -36,6 +37,22 @@ config("openthread_rw61x_config") { if (spinel_interface_rpmsg) { include_dirs += [ "${openthread_nxp_root}/src/common/spinel" ] } + if (chip_enable_wifi && chip_enable_openthread) { + defines = [ + "OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE=1", + "OPENTHREAD_CONFIG_COMMISSIONER_ENABLE=1", + "OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE=1", + "OPENTHREAD_CONFIG_MDNS_SERVER_ENABLE=1", + "OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE=1", + "OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE=1", + "OPENTHREAD_CONFIG_MAX_STATECHANGE_HANDLERS=3", + "OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE=1", + "OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE=1", + "OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE=1", + "OT_APP_BR_LWIP_HOOKS_EN=1", + "OPENTHREAD_CONFIG_GENERIC_TASKLET_ENABLE=1", + ] + } } #Config used by the openthread stack to get the path to OpenthreadConfig.h @@ -45,6 +62,7 @@ source_set("openthread_core_config_rw61x") { } source_set("libopenthread-rw61x") { + deps = [] sources = [ "${openthread_nxp_root}/src/common/alarm_freertos.c", "${openthread_nxp_root}/src/common/logging.c", @@ -52,6 +70,17 @@ source_set("libopenthread-rw61x") { "${openthread_nxp_root}/src/rw/rw612/platform/entropy.c", ] + if (chip_enable_wifi && chip_enable_openthread) { + sources += [ + "${openthread_nxp_root}/src/common/br/border_agent.c", + "${openthread_nxp_root}/src/common/br/br_rtos_manager.c", + "${openthread_nxp_root}/src/common/br/infra_if.c", + "${openthread_nxp_root}/src/common/br/lwip_hooks.c", + "${openthread_nxp_root}/src/common/br/lwip_mcast.c", + "${openthread_nxp_root}/src/common/br/udp_plat.c", + ] + deps += [ "${nxp_sdk_build_root}:nxp_lwip" ] + } if (spinel_interface_rpmsg) { sources += [ "${openthread_nxp_root}/src/common/spinel/misc.c", @@ -72,8 +101,8 @@ source_set("libopenthread-rw61x") { sources += [ "${openthread_nxp_root}/src/common/flash_nvm.c" ] } else if (rt_nvm_component == "littlefs") { sources += [ "${openthread_nxp_root}/src/common/flash_littlefs.c" ] - } else { - sources += [ "${openthread_nxp_root}/src/common/flash_ks.c" ] + } else if (rt_nvm_component == "key_storage") { + sources += [ "${openthread_nxp_root}/src/common/flash_fsa.c" ] } defines = [ @@ -83,7 +112,7 @@ source_set("libopenthread-rw61x") { public_configs = [ ":openthread_rw61x_config" ] - deps = [ + deps += [ "${nxp_sdk_build_root}:nxp_mbedtls", "../../..:libopenthread-platform-utils", nxp_sdk_target, From d3384550af48ec6659b594f7f9fd399994d553e2 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 20 Mar 2024 11:40:40 -0400 Subject: [PATCH 08/11] Create `ember-compatibility-functions.h` to represent what `ember-compatibility-functions.cpp` contains (#32623) * Create a ember-compatibility-functions header to define what the cpp contains * Fix includes of ember compatibility functions * Fix lint * Restyle * Remove reporting.h since that actually declares what ember-compatibility-functions.cpp implements * Revert "Remove reporting.h since that actually declares what ember-compatibility-functions.cpp implements" This reverts commit 297f526359fbc7a8fd57b58335faf033bedeaf2e. * Move the reporting bits into reporting.cpp * Restyle * Remove some unused includes * Add reporting.cpp to cmake builds * Fix rpc builds with new includes * Fix oven and java matter controller compilation * Some additional minor comments for the dynamic dispatcher ... unfortunately deps seem a bit strict * Restyle * Add reporting.cpp to the list of know issues (it is the same as ember-compatibility-functions) * Add reporting.cpp to xcode * Fix darwin compile * Remove extra comment based on code review * Comment rephrase --------- Co-authored-by: Andrei Litvin --- .github/workflows/lint.yml | 2 + .../common/pigweed/rpc_services/Attributes.h | 4 +- src/app/BUILD.gn | 7 ++ src/app/InteractionModelEngine.cpp | 1 + src/app/InteractionModelEngine.h | 64 ------------- src/app/WriteHandler.cpp | 1 + src/app/chip_data_model.cmake | 1 + src/app/chip_data_model.gni | 1 + .../microwave-oven-control-server.cpp | 1 + .../pump-configuration-and-control-server.cpp | 1 + src/app/dynamic_server/AccessControl.cpp | 5 + src/app/dynamic_server/DynamicDispatcher.cpp | 1 + src/app/reporting/Engine.cpp | 1 + src/app/reporting/reporting.cpp | 79 ++++++++++++++++ src/app/server/Server.cpp | 1 + .../util/ember-compatibility-functions.cpp | 57 +---------- src/app/util/ember-compatibility-functions.h | 94 +++++++++++++++++++ .../ServerEndpoint/MTRServerAccessControl.mm | 1 + .../Matter.xcodeproj/project.pbxproj | 14 +++ 19 files changed, 215 insertions(+), 121 deletions(-) create mode 100644 src/app/reporting/reporting.cpp create mode 100644 src/app/util/ember-compatibility-functions.h diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 15d0d3ed1be5f7..59c0d4e59a7c9b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -97,6 +97,7 @@ jobs: --known-failure app/CommandSenderLegacyCallback.h \ --known-failure app/data-model/ListLargeSystemExtensions.h \ --known-failure app/ReadHandler.h \ + --known-failure app/reporting/reporting.cpp \ --known-failure app/reporting/tests/MockReportScheduler.cpp \ --known-failure app/reporting/tests/MockReportScheduler.h \ --known-failure app/TestEventTriggerDelegate.h \ @@ -111,6 +112,7 @@ jobs: --known-failure app/util/DataModelHandler.cpp \ --known-failure app/util/DataModelHandler.h \ --known-failure app/util/ember-compatibility-functions.cpp \ + --known-failure app/util/ember-compatibility-functions.h \ --known-failure app/util/endpoint-config-api.h \ --known-failure app/util/generic-callbacks.h \ --known-failure app/util/generic-callback-stubs.cpp \ diff --git a/examples/common/pigweed/rpc_services/Attributes.h b/examples/common/pigweed/rpc_services/Attributes.h index 223ec3336f25bb..78e310f70ab5b5 100644 --- a/examples/common/pigweed/rpc_services/Attributes.h +++ b/examples/common/pigweed/rpc_services/Attributes.h @@ -18,12 +18,14 @@ #pragma once -#include "app/util/attribute-storage.h" #include "attributes_service/attributes_service.rpc.pb.h" #include "pigweed/rpc_services/internal/StatusUtils.h" + #include #include #include +#include +#include #include #include #include diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 2ac041439aaa5f..2641356da45308 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -189,6 +189,13 @@ static_library("interaction-model") { "reporting/ReportSchedulerImpl.h", "reporting/SynchronizedReportSchedulerImpl.cpp", "reporting/SynchronizedReportSchedulerImpl.h", + + # TODO: reporting header is part of interaction-model, however actual implementation depends + # on generated code as subscriptions depend on attribute data versioning to determine + # when "data changed" reports should be sent. + # + # This breaks having `.h` and `.cpp` based off the same compilation unit and + # should ideally be cleaned up. "reporting/reporting.h", ] diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 68ab4525d083e4..74c7aeda76a1a0 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index 22ec1fb758153a..0e021a0b812074 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -678,41 +678,6 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, void DispatchSingleClusterCommand(const ConcreteCommandPath & aCommandPath, chip::TLV::TLVReader & aReader, CommandHandler * apCommandObj); -/** - * Check whether the given cluster exists on the given endpoint and supports - * the given command. If it does, Success will be returned. If it does not, - * one of UnsupportedEndpoint, UnsupportedCluster, or UnsupportedCommand - * will be returned, depending on how the command fails to exist. - */ -Protocols::InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath); - -/** - * Fetch attribute value and version info and write to the AttributeReport provided. - * The ReadSingleClusterData will do everything required for encoding an attribute, i.e. it will try to put one or more - * AttributeReportIB to the AttributeReportIBs::Builder. - * When the endpoint / cluster / attribute data specified by aPath does not exist, corresponding interaction - * model error code will be put into aAttributeReports, and CHIP_NO_ERROR will be returned. If the data exists on the server, the - * data (with tag kData) and the data version (with tag kDataVersion) will be put into aAttributeReports. TLVWriter error will be - * returned if any error occurred while encoding these values. This function is implemented by CHIP as a part of cluster data - * storage & management. - * - * @param[in] aSubjectDescriptor The subject descriptor for the read. - * @param[in] aPath The concrete path of the data being read. - * @param[in] aAttributeReports The TLV Builder for Cluter attribute builder. - * - * @retval CHIP_NO_ERROR on success - */ -CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered, - const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports, - AttributeValueEncoder::AttributeEncodeState * apEncoderState); - -/** - * Check whether concrete attribute path is an "existent attribute path" in spec terms. - * @param[in] aPath The concrete path of the data being read. - * @retval boolean true if the concrete attribute path indicates an attribute that exists on the node. - */ -bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath); - /** * Get the registered attribute access override. nullptr when attribute access override is not found. * @@ -720,34 +685,5 @@ bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath); */ AttributeAccessInterface * GetAttributeAccessOverride(EndpointId aEndpointId, ClusterId aClusterId); -/** - * TODO: Document. - */ -CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, - const ConcreteDataAttributePath & aAttributePath, TLV::TLVReader & aReader, - WriteHandler * apWriteHandler); - -/** - * Check if the given cluster has the given DataVersion. - */ -bool IsClusterDataVersionEqual(const ConcreteClusterPath & aConcreteClusterPath, DataVersion aRequiredVersion); - -/** - * Returns true if device type is on endpoint, false otherwise. - */ -bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint); - -/** - * Returns the metadata of the attribute for the given path. - * - * @retval The metadata of the attribute, will return null if the given attribute does not exists. - */ -const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aPath); - -/** - * Returns the event support status for the given event, as an interaction model status. - */ -Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath); - } // namespace app } // namespace chip diff --git a/src/app/WriteHandler.cpp b/src/app/WriteHandler.cpp index c0bc52b1f2ae71..f7fc1f3273d7c8 100644 --- a/src/app/WriteHandler.cpp +++ b/src/app/WriteHandler.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include diff --git a/src/app/chip_data_model.cmake b/src/app/chip_data_model.cmake index 6e84ce2a4c6734..5573eeb275acd7 100644 --- a/src/app/chip_data_model.cmake +++ b/src/app/chip_data_model.cmake @@ -135,6 +135,7 @@ function(chip_configure_data_model APP_TARGET) target_sources(${APP_TARGET} ${SCOPE} ${CHIP_APP_BASE_DIR}/../../zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp ${CHIP_APP_BASE_DIR}/../../zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp + ${CHIP_APP_BASE_DIR}/reporting/reporting.cpp ${CHIP_APP_BASE_DIR}/util/attribute-storage.cpp ${CHIP_APP_BASE_DIR}/util/attribute-table.cpp ${CHIP_APP_BASE_DIR}/util/binding-table.cpp diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index eb8036b79990e0..c8a30b1b5bd36a 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -204,6 +204,7 @@ template("chip_data_model") { if (!chip_build_controller_dynamic_server) { sources += [ + "${_app_root}/reporting/reporting.cpp", "${_app_root}/util/DataModelHandler.cpp", "${_app_root}/util/attribute-storage.cpp", "${_app_root}/util/attribute-table.cpp", diff --git a/src/app/clusters/microwave-oven-control-server/microwave-oven-control-server.cpp b/src/app/clusters/microwave-oven-control-server/microwave-oven-control-server.cpp index ddaed23187481d..cae604c625f0cb 100644 --- a/src/app/clusters/microwave-oven-control-server/microwave-oven-control-server.cpp +++ b/src/app/clusters/microwave-oven-control-server/microwave-oven-control-server.cpp @@ -22,6 +22,7 @@ #include #include #include +#include using namespace chip; using namespace chip::app; diff --git a/src/app/clusters/pump-configuration-and-control-server/pump-configuration-and-control-server.cpp b/src/app/clusters/pump-configuration-and-control-server/pump-configuration-and-control-server.cpp index c3eebe257c6b17..eea313c3eb31db 100644 --- a/src/app/clusters/pump-configuration-and-control-server/pump-configuration-and-control-server.cpp +++ b/src/app/clusters/pump-configuration-and-control-server/pump-configuration-and-control-server.cpp @@ -26,6 +26,7 @@ #include #include #include +#include using namespace chip; using namespace chip::app; diff --git a/src/app/dynamic_server/AccessControl.cpp b/src/app/dynamic_server/AccessControl.cpp index 38928b52fb0dea..d091b9ae1faea6 100644 --- a/src/app/dynamic_server/AccessControl.cpp +++ b/src/app/dynamic_server/AccessControl.cpp @@ -25,6 +25,11 @@ #include #include +// TODO: this include is unclear as dynamic server should NOT link those. +// we should probably have some separate includes here for dynamic +// server +#include + using namespace chip; using namespace chip::Access; using namespace chip::app::Clusters; diff --git a/src/app/dynamic_server/DynamicDispatcher.cpp b/src/app/dynamic_server/DynamicDispatcher.cpp index ca396c1b023df2..c54e7d87b96a0c 100644 --- a/src/app/dynamic_server/DynamicDispatcher.cpp +++ b/src/app/dynamic_server/DynamicDispatcher.cpp @@ -15,6 +15,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include #include #include diff --git a/src/app/reporting/Engine.cpp b/src/app/reporting/Engine.cpp index 2dd730c358fe0a..e21080839ed248 100644 --- a/src/app/reporting/Engine.cpp +++ b/src/app/reporting/Engine.cpp @@ -32,6 +32,7 @@ #include #include #include +#include using namespace chip::Access; diff --git a/src/app/reporting/reporting.cpp b/src/app/reporting/reporting.cpp new file mode 100644 index 00000000000000..8ed06a8e13c857 --- /dev/null +++ b/src/app/reporting/reporting.cpp @@ -0,0 +1,79 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * + * 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 "reporting.h" + +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app; + +namespace { + +void IncreaseClusterDataVersion(const ConcreteClusterPath & aConcreteClusterPath) +{ + DataVersion * version = emberAfDataVersionStorage(aConcreteClusterPath); + if (version == nullptr) + { + ChipLogError(DataManagement, "Endpoint %x, Cluster " ChipLogFormatMEI " not found in IncreaseClusterDataVersion!", + aConcreteClusterPath.mEndpointId, ChipLogValueMEI(aConcreteClusterPath.mClusterId)); + } + else + { + (*(version))++; + ChipLogDetail(DataManagement, "Endpoint %x, Cluster " ChipLogFormatMEI " update version to %" PRIx32, + aConcreteClusterPath.mEndpointId, ChipLogValueMEI(aConcreteClusterPath.mClusterId), *(version)); + } +} + +} // namespace + +void MatterReportingAttributeChangeCallback(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId) +{ + // Attribute writes have asserted this already, but this assert should catch + // applications notifying about changes from their end. + assertChipStackLockedByCurrentThread(); + + AttributePathParams info; + info.mClusterId = clusterId; + info.mAttributeId = attributeId; + info.mEndpointId = endpoint; + + IncreaseClusterDataVersion(ConcreteClusterPath(endpoint, clusterId)); + InteractionModelEngine::GetInstance()->GetReportingEngine().SetDirty(info); +} + +void MatterReportingAttributeChangeCallback(const ConcreteAttributePath & aPath) +{ + return MatterReportingAttributeChangeCallback(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); +} + +void MatterReportingAttributeChangeCallback(EndpointId endpoint) +{ + // Attribute writes have asserted this already, but this assert should catch + // applications notifying about changes from their end. + assertChipStackLockedByCurrentThread(); + + AttributePathParams info; + info.mEndpointId = endpoint; + + // We are adding or enabling a whole endpoint, in this case, we do not touch the cluster data version. + + InteractionModelEngine::GetInstance()->GetReportingEngine().SetDirty(info); +} diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index e046080c1089a5..3d7ceb50890000 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #if CONFIG_NETWORK_LAYER_BLE #include diff --git a/src/app/util/ember-compatibility-functions.cpp b/src/app/util/ember-compatibility-functions.cpp index f8c8ccae559166..81bf518558a5e3 100644 --- a/src/app/util/ember-compatibility-functions.cpp +++ b/src/app/util/ember-compatibility-functions.cpp @@ -14,12 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/** - * @file - * Contains the functions for compatibility with ember ZCL inner state - * when calling ember callbacks. - */ +#include #include #include @@ -272,22 +267,6 @@ CHIP_ERROR ReadClusterDataVersion(const ConcreteClusterPath & aConcreteClusterPa return CHIP_NO_ERROR; } -void IncreaseClusterDataVersion(const ConcreteClusterPath & aConcreteClusterPath) -{ - DataVersion * version = emberAfDataVersionStorage(aConcreteClusterPath); - if (version == nullptr) - { - ChipLogError(DataManagement, "Endpoint %x, Cluster " ChipLogFormatMEI " not found in IncreaseClusterDataVersion!", - aConcreteClusterPath.mEndpointId, ChipLogValueMEI(aConcreteClusterPath.mClusterId)); - } - else - { - (*(version))++; - ChipLogDetail(DataManagement, "Endpoint %x, Cluster " ChipLogFormatMEI " update version to %" PRIx32, - aConcreteClusterPath.mEndpointId, ChipLogValueMEI(aConcreteClusterPath.mClusterId), *(version)); - } -} - CHIP_ERROR SendSuccessStatus(AttributeReportIB::Builder & aAttributeReport, AttributeDataIB::Builder & aAttributeDataIBBuilder) { ReturnErrorOnFailure(aAttributeDataIBBuilder.EndOfAttributeDataIB()); @@ -1123,37 +1102,3 @@ Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventP } // namespace app } // namespace chip - -void MatterReportingAttributeChangeCallback(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId) -{ - // Attribute writes have asserted this already, but this assert should catch - // applications notifying about changes from their end. - assertChipStackLockedByCurrentThread(); - - AttributePathParams info; - info.mClusterId = clusterId; - info.mAttributeId = attributeId; - info.mEndpointId = endpoint; - - IncreaseClusterDataVersion(ConcreteClusterPath(endpoint, clusterId)); - InteractionModelEngine::GetInstance()->GetReportingEngine().SetDirty(info); -} - -void MatterReportingAttributeChangeCallback(const ConcreteAttributePath & aPath) -{ - return MatterReportingAttributeChangeCallback(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId); -} - -void MatterReportingAttributeChangeCallback(EndpointId endpoint) -{ - // Attribute writes have asserted this already, but this assert should catch - // applications notifying about changes from their end. - assertChipStackLockedByCurrentThread(); - - AttributePathParams info; - info.mEndpointId = endpoint; - - // We are adding or enabling a whole endpoint, in this case, we do not touch the cluster data version. - - InteractionModelEngine::GetInstance()->GetReportingEngine().SetDirty(info); -} diff --git a/src/app/util/ember-compatibility-functions.h b/src/app/util/ember-compatibility-functions.h new file mode 100644 index 00000000000000..a0888ac7f90498 --- /dev/null +++ b/src/app/util/ember-compatibility-functions.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * + * 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. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { + +/** + * Check whether the given cluster exists on the given endpoint and supports + * the given command. If it does, Success will be returned. If it does not, + * one of UnsupportedEndpoint, UnsupportedCluster, or UnsupportedCommand + * will be returned, depending on how the command fails to exist. + */ +Protocols::InteractionModel::Status ServerClusterCommandExists(const ConcreteCommandPath & aCommandPath); + +/** + * Check whether concrete attribute path is an "existent attribute path" in spec terms. + * @param[in] aPath The concrete path of the data being read. + * @retval boolean true if the concrete attribute path indicates an attribute that exists on the node. + */ +bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath); + +/** + * Fetch attribute value and version info and write to the AttributeReport provided. + * The ReadSingleClusterData will do everything required for encoding an attribute, i.e. it will try to put one or more + * AttributeReportIB to the AttributeReportIBs::Builder. + * When the endpoint / cluster / attribute data specified by aPath does not exist, corresponding interaction + * model error code will be put into aAttributeReports, and CHIP_NO_ERROR will be returned. If the data exists on the server, the + * data (with tag kData) and the data version (with tag kDataVersion) will be put into aAttributeReports. TLVWriter error will be + * returned if any error occurred while encoding these values. This function is implemented by CHIP as a part of cluster data + * storage & management. + * + * @param[in] aSubjectDescriptor The subject descriptor for the read. + * @param[in] aPath The concrete path of the data being read. + * @param[in] aAttributeReports The TLV Builder for Cluter attribute builder. + * + * @retval CHIP_NO_ERROR on success + */ +CHIP_ERROR ReadSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, bool aIsFabricFiltered, + const ConcreteReadAttributePath & aPath, AttributeReportIBs::Builder & aAttributeReports, + AttributeValueEncoder::AttributeEncodeState * apEncoderState); + +/** + * Returns the metadata of the attribute for the given path. + * + * @retval The metadata of the attribute, will return null if the given attribute does not exists. + */ +const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aPath); + +/** + * TODO: Document. + */ +CHIP_ERROR WriteSingleClusterData(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteDataAttributePath & aPath, + TLV::TLVReader & aReader, WriteHandler * apWriteHandler); + +/** + * Check if the given cluster has the given DataVersion. + */ +bool IsClusterDataVersionEqual(const ConcreteClusterPath & aConcreteClusterPath, DataVersion aRequiredVersion); + +/** + * Returns true if device type is on endpoint, false otherwise. + */ +bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint); + +/** + * Returns the event support status for the given event, as an interaction model status. + */ +Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath); + +} // namespace app +} // namespace chip diff --git a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAccessControl.mm b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAccessControl.mm index abf386be53b735..28dacef27c2a78 100644 --- a/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAccessControl.mm +++ b/src/darwin/Framework/CHIP/ServerEndpoint/MTRServerAccessControl.mm @@ -32,6 +32,7 @@ #include #include +#include #include using namespace chip; diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index c6111d85b0f537..74ab025151ef5a 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -262,6 +262,8 @@ 7596A8512878709F004DAE0E /* MTRAsyncCallbackQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7596A8502878709F004DAE0E /* MTRAsyncCallbackQueueTests.m */; }; 7596A85528788557004DAE0E /* MTRClusters.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7596A85228788557004DAE0E /* MTRClusters.mm */; }; 7596A85728788557004DAE0E /* MTRClusters.h in Headers */ = {isa = PBXBuildFile; fileRef = 7596A85428788557004DAE0E /* MTRClusters.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 75A202E52BA8DBAC00A771DD /* reporting.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 75A202E42BA8DBAC00A771DD /* reporting.cpp */; }; + 75A202E62BA8DBAC00A771DD /* reporting.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 75A202E42BA8DBAC00A771DD /* reporting.cpp */; }; 75B0D01E2B71B47F002074DD /* MTRDeviceTestDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 75B0D01D2B71B47F002074DD /* MTRDeviceTestDelegate.m */; }; 75B765C12A1D71BC0014719B /* MTRAttributeSpecifiedCheck.h in Headers */ = {isa = PBXBuildFile; fileRef = 75B765C02A1D71BC0014719B /* MTRAttributeSpecifiedCheck.h */; }; 75B765C32A1D82D30014719B /* MTRAttributeSpecifiedCheck.mm in Sources */ = {isa = PBXBuildFile; fileRef = 75B765C22A1D82D30014719B /* MTRAttributeSpecifiedCheck.mm */; }; @@ -669,6 +671,7 @@ 7596A8502878709F004DAE0E /* MTRAsyncCallbackQueueTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRAsyncCallbackQueueTests.m; sourceTree = ""; }; 7596A85228788557004DAE0E /* MTRClusters.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRClusters.mm; sourceTree = ""; }; 7596A85428788557004DAE0E /* MTRClusters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRClusters.h; sourceTree = ""; }; + 75A202E42BA8DBAC00A771DD /* reporting.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = reporting.cpp; sourceTree = ""; }; 75B0D01C2B71B46F002074DD /* MTRDeviceTestDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRDeviceTestDelegate.h; sourceTree = ""; }; 75B0D01D2B71B47F002074DD /* MTRDeviceTestDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRDeviceTestDelegate.m; sourceTree = ""; }; 75B765BF2A1D70F80014719B /* MTRAttributeSpecifiedCheck-src.zapt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "MTRAttributeSpecifiedCheck-src.zapt"; sourceTree = ""; }; @@ -1013,6 +1016,7 @@ 1E857311265519DE0050A4D9 /* app */ = { isa = PBXGroup; children = ( + 75A202E72BA8DBB700A771DD /* reporting */, 5143041F2914CED9004DC7FE /* generic-callback-stubs.cpp */, 514C79F22B62ED5500DD6D7B /* attribute-storage.cpp */, 514C79EF2B62ADDA00DD6D7B /* descriptor.cpp */, @@ -1166,6 +1170,14 @@ path = ServerEndpoint; sourceTree = ""; }; + 75A202E72BA8DBB700A771DD /* reporting */ = { + isa = PBXGroup; + children = ( + 75A202E42BA8DBAC00A771DD /* reporting.cpp */, + ); + path = reporting; + sourceTree = ""; + }; B20252832459E34F00F97062 = { isa = PBXGroup; children = ( @@ -1788,6 +1800,7 @@ B45373F32A9FEC1A00807602 /* server-ws.c in Sources */, 03F430AA2994113500166449 /* sysunix.c in Sources */, B45373C42A9FEA9100807602 /* dummy-callback.c in Sources */, + 75A202E62BA8DBAC00A771DD /* reporting.cpp in Sources */, 514C79EE2B62ADCD00DD6D7B /* ember-compatibility-functions.cpp in Sources */, 039145E82993179300257B3E /* GetCommissionerNodeIdCommand.mm in Sources */, 0395469F2991DFC5006D42A8 /* json_reader.cpp in Sources */, @@ -1896,6 +1909,7 @@ 51B22C262740CB32008D5055 /* MTRStructsObjc.mm in Sources */, 2C222AD1255C620600E446B9 /* MTRBaseDevice.mm in Sources */, 1EC3238D271999E2002A8BF0 /* cluster-objects.cpp in Sources */, + 75A202E52BA8DBAC00A771DD /* reporting.cpp in Sources */, 3CF134A9289D8D800017A19E /* MTRCSRInfo.mm in Sources */, 991DC0892475F47D00C13860 /* MTRDeviceController.mm in Sources */, B2E0D7B7245B0B5C003C5B48 /* MTRQRCodeSetupPayloadParser.mm in Sources */, From cdd0f5f530a476a4cc5def867f7bcd00feeb4657 Mon Sep 17 00:00:00 2001 From: Andrei Litvin Date: Wed, 20 Mar 2024 11:52:15 -0400 Subject: [PATCH 09/11] Revert "CI: Don't run bootstrap if we restored from cache (#32610)" (#32650) This reverts commit 82287051caf2bd8a80dce800f21f92a27c4f31dd. --- .github/actions/bootstrap-cache/action.yaml | 2 +- .github/actions/bootstrap/action.yaml | 35 ++----------------- .../action.yaml | 9 ++++- .../actions/upload-bootstrap-logs/action.yaml | 18 ++++++++++ .github/workflows/cirque.yaml | 1 - .github/workflows/fuzzing-build.yaml | 8 +++++ .github/workflows/release_artifacts.yaml | 8 +++++ scripts/setup/bootstrap.sh | 20 +++++------ ...abs.txt => requirements.silabs_docker.txt} | 0 9 files changed, 55 insertions(+), 46 deletions(-) create mode 100644 .github/actions/upload-bootstrap-logs/action.yaml rename scripts/setup/{requirements.silabs.txt => requirements.silabs_docker.txt} (100%) diff --git a/.github/actions/bootstrap-cache/action.yaml b/.github/actions/bootstrap-cache/action.yaml index 9a883ecf22da79..b22ed1e0ac60bf 100644 --- a/.github/actions/bootstrap-cache/action.yaml +++ b/.github/actions/bootstrap-cache/action.yaml @@ -1,5 +1,5 @@ name: Bootstrap cache -description: Bootstrap cache (deprecated) +description: Bootstrap cache runs: using: "composite" steps: diff --git a/.github/actions/bootstrap/action.yaml b/.github/actions/bootstrap/action.yaml index 32fe6c9cfedba3..8f94830b8c0e51 100644 --- a/.github/actions/bootstrap/action.yaml +++ b/.github/actions/bootstrap/action.yaml @@ -5,40 +5,9 @@ inputs: description: "Platform name" required: false default: none - bootstrap-log-name: - description: "Bootstrap log name" - required: false - default: bootstrap-logs-${{ github.job }} - runs: using: "composite" steps: - - uses: Wandalen/wretry.action@v1.4.10 - name: Bootstrap from cache - id: bootstrap-cache - continue-on-error: true - with: - action: buildjet/cache@v4 - attempt_limit: 3 - attempt_delay: 2000 - with: | - key: ${{ runner.os }}-${{ inputs.platform }}-env-${{ hashFiles('scripts/setup/*', 'third_party/pigweed/**') }} - path: | - .environment - build_overrides/pigweed_environment.gni - - - name: Run bootstrap - if: fromJSON(steps.bootstrap-cache.outputs.outputs).cache-hit != 'true' # retry returns all outputs in `outputs` - env: - PW_NO_CIPD_CACHE_DIR: Y + - name: Bootstrap shell: bash - run: source scripts/bootstrap.sh -p all,${{ inputs.platform }} - - - name: Uploading bootstrap logs - uses: actions/upload-artifact@v3 - if: always() && !env.ACT && fromJSON(steps.bootstrap-cache.outputs.outputs).cache-hit != 'true' - with: - name: ${{ inputs.bootstrap-log-name }} - path: | - .environment/gn_out/.ninja_log - .environment/pigweed-venv/*.log + run: bash scripts/bootstrap.sh -p all,${{ inputs.platform }} diff --git a/.github/actions/checkout-submodules-and-bootstrap/action.yaml b/.github/actions/checkout-submodules-and-bootstrap/action.yaml index df3fdff1e02ce4..7424ca529f74df 100644 --- a/.github/actions/checkout-submodules-and-bootstrap/action.yaml +++ b/.github/actions/checkout-submodules-and-bootstrap/action.yaml @@ -26,14 +26,21 @@ runs: with: platform: ${{ inputs.platform }} extra-parameters: ${{ inputs.extra-submodule-parameters }} + - name: Bootstrap Cache + uses: ./.github/actions/bootstrap-cache - name: Bootstrap uses: ./.github/actions/bootstrap + env: + PW_NO_CIPD_CACHE_DIR: Y with: platform: ${{ inputs.platform }} - bootstrap-log-name: ${{ inputs.bootstrap-log-name }} - name: Dump disk info after checkout submodule & Bootstrap shell: bash run: scripts/dump_diskspace_info.sh + - name: Upload Bootstrap Logs + uses: ./.github/actions/upload-bootstrap-logs + with: + bootstrap-log-name: ${{ inputs.bootstrap-log-name }} - name: Work around TSAN ASLR issues if: runner.os == 'Linux' && !env.ACT shell: bash diff --git a/.github/actions/upload-bootstrap-logs/action.yaml b/.github/actions/upload-bootstrap-logs/action.yaml new file mode 100644 index 00000000000000..522058c16ff93c --- /dev/null +++ b/.github/actions/upload-bootstrap-logs/action.yaml @@ -0,0 +1,18 @@ +name: Upload bootstrap logs +description: Upload bootstrap logs +inputs: + bootstrap-log-name: + description: "Bootstrap log name" + required: false + default: bootstrap-logs-${{ github.job }} +runs: + using: "composite" + steps: + - name: Uploading bootstrap logs + uses: actions/upload-artifact@v4 + if: ${{ always() && !env.ACT }} + with: + name: ${{ inputs.bootstrap-log-name }} + path: | + .environment/gn_out/.ninja_log + .environment/pigweed-venv/*.log diff --git a/.github/workflows/cirque.yaml b/.github/workflows/cirque.yaml index 919085ac77ee0f..1f0b7c5d327fcf 100644 --- a/.github/workflows/cirque.yaml +++ b/.github/workflows/cirque.yaml @@ -57,7 +57,6 @@ jobs: with: platform: linux - # TODO: Is what's being cached here actually compatible with a regular bootstrap? - name: Bootstrap Cache uses: ./.github/actions/bootstrap-cache - name: Bootstrap Cirque diff --git a/.github/workflows/fuzzing-build.yaml b/.github/workflows/fuzzing-build.yaml index f354ce369c27fd..ee19873cc32f2a 100644 --- a/.github/workflows/fuzzing-build.yaml +++ b/.github/workflows/fuzzing-build.yaml @@ -46,8 +46,12 @@ jobs: run: | mkdir objdir-clone || true + - name: Bootstrap Cache + uses: ./.github/actions/bootstrap-cache - name: Bootstrap uses: ./.github/actions/bootstrap + - name: Upload Bootstrap Logs + uses: ./.github/actions/upload-bootstrap-logs - name: Build all-clusters-app run: | @@ -80,8 +84,12 @@ jobs: run: | mkdir objdir-clone || true + - name: Bootstrap Cache + uses: ./.github/actions/bootstrap-cache - name: Bootstrap uses: ./.github/actions/bootstrap + - name: Upload Bootstrap Logs + uses: ./.github/actions/upload-bootstrap-logs - name: Build all-clusters-app run: | diff --git a/.github/workflows/release_artifacts.yaml b/.github/workflows/release_artifacts.yaml index 57a1ce7b325dc3..5aa38ecc893d28 100644 --- a/.github/workflows/release_artifacts.yaml +++ b/.github/workflows/release_artifacts.yaml @@ -39,8 +39,12 @@ jobs: uses: actions/checkout@v4 with: ref: "${{ github.event.inputs.releaseTag }}" + - name: Bootstrap Cache + uses: ./.github/actions/bootstrap-cache - name: Bootstrap uses: ./.github/actions/bootstrap + - name: Upload Bootstrap Logs + uses: ./.github/actions/upload-bootstrap-logs - name: Build run: scripts/examples/esp_example.sh all-clusters-app @@ -70,8 +74,12 @@ jobs: uses: actions/checkout@v4 with: ref: "${{ github.event.inputs.releaseTag }}" + - name: Bootstrap Cache + uses: ./.github/actions/bootstrap-cache - name: Bootstrap uses: ./.github/actions/bootstrap + - name: Upload Bootstrap Logs + uses: ./.github/actions/upload-bootstrap-logs - name: Build example EFR32 Lock App run: scripts/examples/gn_silabs_example.sh examples/lock-app/efr32/ diff --git a/scripts/setup/bootstrap.sh b/scripts/setup/bootstrap.sh index d723e86f70924a..1b813216222efe 100644 --- a/scripts/setup/bootstrap.sh +++ b/scripts/setup/bootstrap.sh @@ -21,14 +21,14 @@ _install_additional_pip_requirements() { # figure out additional pip install items while [ $# -gt 0 ]; do case $1 in - -p | --platform) - _SETUP_PLATFORM=$2 - shift # argument - shift # value - ;; - *) - shift - ;; + -p | --platform) + _SETUP_PLATFORM=$2 + shift # argument + shift # value + ;; + *) + shift + ;; esac done @@ -41,7 +41,7 @@ _install_additional_pip_requirements() { for platform in ${_SETUP_PLATFORM}; do # Allow none as an alias of nothing extra installed (like -p none) - if [ "$platform" != "none" -a -e "$_CHIP_ROOT/scripts/setup/requirements.$platform.txt" ]; then + if [ "$platform" != "none" ]; then echo "Installing pip requirements for $platform..." pip install -q \ -r "$_CHIP_ROOT/scripts/setup/requirements.$platform.txt" \ @@ -66,7 +66,7 @@ _bootstrap_or_activate() { local _BOOTSTRAP_NAME="${_BOOTSTRAP_PATH##*/}" local _BOOTSTRAP_DIR="${_BOOTSTRAP_PATH%/*}" # Strip off the 'scripts[/setup]' directory, leaving the root of the repo. - _CHIP_ROOT="$(cd "${_BOOTSTRAP_DIR%/setup}/.." >/dev/null && pwd)" + _CHIP_ROOT="$(cd "${_BOOTSTRAP_DIR%/setup}/.." > /dev/null && pwd)" local _CONFIG_FILE="scripts/setup/environment.json" diff --git a/scripts/setup/requirements.silabs.txt b/scripts/setup/requirements.silabs_docker.txt similarity index 100% rename from scripts/setup/requirements.silabs.txt rename to scripts/setup/requirements.silabs_docker.txt From d94a5270545ae93377886aa2c71a30517125ee4e Mon Sep 17 00:00:00 2001 From: Erwin Pan Date: Wed, 20 Mar 2024 23:54:14 +0800 Subject: [PATCH 10/11] [Chef] Enable Fan Control all features (#32649) --- .../devices/rootnode_fan_7N2TobIlOX.matter | 26 ++++--- .../chef/devices/rootnode_fan_7N2TobIlOX.zap | 68 +++++++++++++++---- 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/examples/chef/devices/rootnode_fan_7N2TobIlOX.matter b/examples/chef/devices/rootnode_fan_7N2TobIlOX.matter index 097015b4634695..a0d28140e16cc7 100644 --- a/examples/chef/devices/rootnode_fan_7N2TobIlOX.matter +++ b/examples/chef/devices/rootnode_fan_7N2TobIlOX.matter @@ -1638,22 +1638,26 @@ endpoint 1 { } server cluster FanControl { - ram attribute fanMode default = 0; + persist attribute fanMode default = 0; ram attribute fanModeSequence default = 2; - ram attribute percentSetting default = 0; - ram attribute percentCurrent default = 0; - ram attribute speedMax default = 1; - ram attribute speedSetting default = 0; - ram attribute speedCurrent default = 0; - ram attribute rockSupport default = 0x00; - ram attribute rockSetting default = 0x00; - ram attribute windSupport default = 0x00; - ram attribute windSetting default = 0x00; + persist attribute percentSetting default = 0x00; + ram attribute percentCurrent default = 0x00; + ram attribute speedMax default = 100; + persist attribute speedSetting default = 0x00; + persist attribute speedCurrent default = 0; + ram attribute rockSupport default = 0x03; + persist attribute rockSetting default = 0x00; + ram attribute windSupport default = 0x03; + persist attribute windSetting default = 0x00; + persist attribute airflowDirection default = 0; callback attribute generatedCommandList; callback attribute acceptedCommandList; + callback attribute eventList; callback attribute attributeList; - ram attribute featureMap default = 0; + ram attribute featureMap default = 0x3F; ram attribute clusterRevision default = 1; + + handle command Step; } } diff --git a/examples/chef/devices/rootnode_fan_7N2TobIlOX.zap b/examples/chef/devices/rootnode_fan_7N2TobIlOX.zap index 875cd6b3ff535e..3ce2411a08df9a 100644 --- a/examples/chef/devices/rootnode_fan_7N2TobIlOX.zap +++ b/examples/chef/devices/rootnode_fan_7N2TobIlOX.zap @@ -2997,6 +2997,16 @@ "define": "FAN_CONTROL_CLUSTER", "side": "server", "enabled": 1, + "commands": [ + { + "name": "Step", + "code": 0, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + } + ], "attributes": [ { "name": "FanMode", @@ -3005,7 +3015,7 @@ "side": "server", "type": "FanModeEnum", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, "defaultValue": "0", @@ -3037,10 +3047,10 @@ "side": "server", "type": "percent", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "0x00", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3056,7 +3066,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "0x00", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3072,7 +3082,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "1", + "defaultValue": "100", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3085,10 +3095,10 @@ "side": "server", "type": "int8u", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "0x00", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3101,7 +3111,7 @@ "side": "server", "type": "int8u", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, "defaultValue": "0", @@ -3120,7 +3130,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x00", + "defaultValue": "0x03", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3133,7 +3143,7 @@ "side": "server", "type": "RockBitmap", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, "defaultValue": "0x00", @@ -3152,7 +3162,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x00", + "defaultValue": "0x03", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3165,7 +3175,7 @@ "side": "server", "type": "WindBitmap", "included": 1, - "storageOption": "RAM", + "storageOption": "NVM", "singleton": 0, "bounded": 0, "defaultValue": "0x00", @@ -3174,6 +3184,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "AirflowDirection", + "code": 11, + "mfgCode": null, + "side": "server", + "type": "AirflowDirectionEnum", + "included": 1, + "storageOption": "NVM", + "singleton": 0, + "bounded": 0, + "defaultValue": "0", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "GeneratedCommandList", "code": 65528, @@ -3206,6 +3232,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "EventList", + "code": 65530, + "mfgCode": null, + "side": "server", + "type": "array", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "AttributeList", "code": 65531, @@ -3232,7 +3274,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0", + "defaultValue": "0x3F", "reportable": 1, "minInterval": 1, "maxInterval": 65534, From 015340ad2edebcec4b499a93db416604df093a4c Mon Sep 17 00:00:00 2001 From: Vivien Nicolas Date: Wed, 20 Mar 2024 17:36:32 +0100 Subject: [PATCH 11/11] [Matter.framework] Add back downloadLogOfType onto MTRBaseDevice and MTRDevice (#32373) * [Matter.framework] Move downloadLogOfType from MTRDevice to MTRBaseDevice * [Matter.framework] Get downloadLogOfType to work over XPC * Add a simple test to MTRXPCProtocolTests.m to check that the XPC protocol works * Add a simple test to MTRXPCListenerSampleTests * [Matter.framework] Do not delete the file containing the log data once the downloadLogOfType completion is done * [Matter.framework] Get downloadLogOfType onto MTRDevice as well --- .github/workflows/darwin.yaml | 3 +- src/darwin/Framework/CHIP/MTRBaseDevice.h | 24 +++++++++ src/darwin/Framework/CHIP/MTRBaseDevice.mm | 13 +++++ src/darwin/Framework/CHIP/MTRDevice.h | 1 - src/darwin/Framework/CHIP/MTRDevice.mm | 10 ++-- .../Framework/CHIP/MTRDeviceController+XPC.h | 10 ++++ .../Framework/CHIP/MTRDeviceController+XPC.mm | 4 ++ src/darwin/Framework/CHIP/MTRDeviceOverXPC.mm | 32 ++++++++++++ .../CHIP/MTRDiagnosticLogsDownloader.mm | 1 - .../CHIPTests/MTRXPCListenerSampleTests.m | 46 ++++++++++++++++ .../Framework/CHIPTests/MTRXPCProtocolTests.m | 52 +++++++++++++++++++ 11 files changed, 188 insertions(+), 8 deletions(-) diff --git a/.github/workflows/darwin.yaml b/.github/workflows/darwin.yaml index 5445c7b3276144..1555032827dd28 100644 --- a/.github/workflows/darwin.yaml +++ b/.github/workflows/darwin.yaml @@ -106,7 +106,8 @@ jobs: working-directory: src/darwin/Framework run: | mkdir -p /tmp/darwin/framework-tests - ../../../out/debug/chip-all-clusters-app --interface-id -1 > >(tee /tmp/darwin/framework-tests/all-cluster-app.log) 2> >(tee /tmp/darwin/framework-tests/all-cluster-app-err.log >&2) & + echo "This is a simple log" > /tmp/darwin/framework-tests/end_user_support_log.txt + ../../../out/debug/chip-all-clusters-app --interface-id -1 --end_user_support_log /tmp/darwin/framework-tests/end_user_support_log.txt > >(tee /tmp/darwin/framework-tests/all-cluster-app.log) 2> >(tee /tmp/darwin/framework-tests/all-cluster-app-err.log >&2) & ../../../out/debug/chip-all-clusters-app --interface-id -1 --dac_provider ../../../credentials/development/commissioner_dut/struct_cd_origin_pid_vid_correct/test_case_vector.json --product-id 32768 --discriminator 3839 --secured-device-port 5539 --KVS /tmp/chip-all-clusters-app-kvs2 > >(tee /tmp/darwin/framework-tests/all-cluster-app-origin-vid.log) 2> >(tee /tmp/darwin/framework-tests/all-cluster-app-origin-vid-err.log >&2) & export TEST_RUNNER_ASAN_OPTIONS=__CURRENT_VALUE__:detect_stack_use_after_return=1 diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.h b/src/darwin/Framework/CHIP/MTRBaseDevice.h index af0a15bd1a6857..5eb00475dfdd8d 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.h +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.h @@ -19,6 +19,7 @@ #import #import +#import @class MTRSetupPayload; @class MTRDeviceController; @@ -542,6 +543,29 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) reportHandler:(MTRDeviceResponseHandler)reportHandler subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)); + +/** + * Download log of the desired type from the device. + * + * Note: The consumer of this API should move the file that the url points to or open it for reading before the + * completion handler returns. Otherwise, the file will be deleted, and the data will be lost. + * + * @param type The type of log being requested. This should correspond to a value in the enum MTRDiagnosticLogType. + * @param timeout The timeout for getting the log. If the timeout expires, completion will be called with whatever + * has been retrieved by that point (which might be none or a partial log). + * If the timeout is set to 0, the request will not expire and completion will not be called until + * the log is fully retrieved or an error occurs. + * @param queue The queue on which completion will be called. + * @param completion The completion handler that is called after attempting to retrieve the requested log. + * - In case of success, the completion handler is called with a non-nil URL and a nil error. + * - If there is an error, a non-nil error is used and the url can be non-nil too if some logs have already been downloaded. + */ +- (void)downloadLogOfType:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion + MTR_NEWLY_AVAILABLE; + @end /** diff --git a/src/darwin/Framework/CHIP/MTRBaseDevice.mm b/src/darwin/Framework/CHIP/MTRBaseDevice.mm index 56a49f9859e288..914917a77adcde 100644 --- a/src/darwin/Framework/CHIP/MTRBaseDevice.mm +++ b/src/darwin/Framework/CHIP/MTRBaseDevice.mm @@ -2160,6 +2160,19 @@ + (NSDictionary *)eventReportForHeader:(const chip::app::EventHeader &)header an return buffer; } + +- (void)downloadLogOfType:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion +{ + [_deviceController downloadLogFromNodeWithID:@(_nodeID) + type:type + timeout:timeout + queue:queue + completion:completion]; +} + @end @implementation MTRBaseDevice (Deprecated) diff --git a/src/darwin/Framework/CHIP/MTRDevice.h b/src/darwin/Framework/CHIP/MTRDevice.h index 989ed44f7667be..ee161947de000b 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.h +++ b/src/darwin/Framework/CHIP/MTRDevice.h @@ -18,7 +18,6 @@ #import #import #import -#import NS_ASSUME_NONNULL_BEGIN diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index a63902f68d3711..493d3a0cb18a95 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -1760,11 +1760,11 @@ - (void)downloadLogOfType:(MTRDiagnosticLogType)type queue:(dispatch_queue_t)queue completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion { - [_deviceController downloadLogFromNodeWithID:_nodeID - type:type - timeout:timeout - queue:queue - completion:completion]; + auto * baseDevice = [self newBaseDevice]; + [baseDevice downloadLogOfType:type + timeout:timeout + queue:queue + completion:completion]; } #pragma mark - Cache management diff --git a/src/darwin/Framework/CHIP/MTRDeviceController+XPC.h b/src/darwin/Framework/CHIP/MTRDeviceController+XPC.h index 4316ba65f25198..46543090951080 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController+XPC.h +++ b/src/darwin/Framework/CHIP/MTRDeviceController+XPC.h @@ -20,6 +20,7 @@ #import #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -183,6 +184,15 @@ typedef void (^MTRValuesHandler)(id _Nullable values, NSError * _Nullable error) attributeId:(NSNumber * _Nullable)attributeId completion:(MTRValuesHandler)completion; +/** + * Requests downloading some logs + */ +- (void)downloadLogWithController:(id _Nullable)controller + nodeId:(NSNumber *)nodeId + type:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + completion:(void (^)(NSString * _Nullable url, NSError * _Nullable error))completion MTR_NEWLY_AVAILABLE; + @end /** diff --git a/src/darwin/Framework/CHIP/MTRDeviceController+XPC.mm b/src/darwin/Framework/CHIP/MTRDeviceController+XPC.mm index fa532f19aa833b..87ce5560381cca 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController+XPC.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController+XPC.mm @@ -220,6 +220,10 @@ + (NSXPCInterface *)xpcInterfaceForServerProtocol forSelector:@selector(readAttributeCacheWithController:nodeId:endpointId:clusterId:attributeId:completion:) argumentIndex:0 ofReply:YES]; + [xpcInterface setClasses:GetXPCAllowedClasses() + forSelector:@selector(downloadLogWithController:nodeId:type:timeout:completion:) + argumentIndex:0 + ofReply:YES]; return xpcInterface; } diff --git a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.mm b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.mm index 36dc5b27410da1..00b69d5899842a 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceOverXPC.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceOverXPC.mm @@ -427,6 +427,38 @@ - (void)openCommissioningWindowWithDiscriminator:(NSNumber *)discriminator }); } +- (void)downloadLogOfType:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + queue:(dispatch_queue_t)queue + completion:(void (^)(NSURL * _Nullable url, NSError * _Nullable error))completion +{ + MTR_LOG_DEBUG("Downloading log ..."); + + __auto_type workBlock = ^(MTRDeviceControllerXPCProxyHandle * _Nullable handle, NSError * _Nullable error) { + if (error != nil) { + completion(nil, error); + return; + } + + [handle.proxy downloadLogWithController:self.controllerID + nodeId:self.nodeID + type:type + timeout:timeout + completion:^(NSString * _Nullable url, NSError * _Nullable error) { + dispatch_async(queue, ^{ + MTR_LOG_DEBUG("Download log"); + completion([NSURL URLWithString:url], error); + // The following captures the proxy handle in the closure so that the + // handle won't be released prior to block call. + __auto_type handleRetainer = handle; + (void) handleRetainer; + }); + }]; + }; + + [self fetchProxyHandleWithQueue:queue completion:workBlock]; +} + - (void)fetchProxyHandleWithQueue:(dispatch_queue_t)queue completion:(MTRFetchProxyHandleCompletion)completion { if (self.controllerID != nil) { diff --git a/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm b/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm index 46226d69357058..8d2832052979e7 100644 --- a/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm +++ b/src/darwin/Framework/CHIP/MTRDiagnosticLogsDownloader.mm @@ -165,7 +165,6 @@ - (instancetype)initWithType:(MTRDiagnosticLogType)type // data in the logs that the caller may find useful. For this reason, fileURL is passed in even // when there is an error but fileHandle is not nil. completion(strongSelf->_fileHandle ? fileURL : nil, bdxError); - [strongSelf deleteFile]; done(strongSelf); } diff --git a/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m b/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m index eabd682a5a0d59..e8a25fc054a7f1 100644 --- a/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m +++ b/src/darwin/Framework/CHIPTests/MTRXPCListenerSampleTests.m @@ -167,6 +167,28 @@ - (void)getAnyDeviceControllerWithCompletion:(void (^)(id _Nullable controller, completion(MTRDeviceControllerId, nil); } +- (void)downloadLogWithController:(id)controller + nodeId:(NSNumber *)nodeId + type:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + completion:(void (^)(NSString * _Nullable url, NSError * _Nullable error))completion +{ + (void) controller; + __auto_type sharedController = sController; + if (sharedController) { + __auto_type device = [MTRBaseDevice deviceWithNodeID:nodeId controller:sharedController]; + [device downloadLogOfType:type + timeout:timeout + queue:dispatch_get_main_queue() + completion:^(NSURL * _Nullable url, NSError * _Nullable error) { + completion([url absoluteString], error); + }]; + } else { + NSLog(@"Failed to get shared controller"); + completion(nil, [NSError errorWithDomain:MTRErrorDomain code:MTRErrorCodeGeneralError userInfo:nil]); + } +} + - (void)readAttributeWithController:(id)controller nodeId:(uint64_t)nodeId endpointId:(NSNumber * _Nullable)endpointId @@ -1865,6 +1887,30 @@ - (void)test015_MTRDeviceInteraction }, @(NO)); } +- (void)test016_DownloadLog +{ + XCTestExpectation * expectation = + [self expectationWithDescription:@"Download EndUserSupport log"]; + + MTRBaseDevice * device = GetConnectedDevice(); + dispatch_queue_t queue = dispatch_get_main_queue(); + + [device downloadLogOfType:MTRDiagnosticLogTypeEndUserSupport + timeout:10 + queue:queue + completion:^(NSURL * _Nullable url, NSError * _Nullable error) { + NSLog(@"downloadLogOfType: url: %@, error: %@", url, error); + XCTAssertNil(error); + + NSError * readError; + NSString * fileContent = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&readError]; + XCTAssertNil(readError); + XCTAssertEqualObjects(fileContent, @"This is a simple log\n"); + [expectation fulfill]; + }]; + [self waitForExpectationsWithTimeout:kTimeoutInSeconds handler:nil]; +} + - (void)test900_SubscribeClusterStateCache { XCTestExpectation * expectation = [self expectationWithDescription:@"subscribe attributes by cache"]; diff --git a/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m b/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m index d3d010ff7f0764..2f1bfca409fa15 100644 --- a/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m +++ b/src/darwin/Framework/CHIPTests/MTRXPCProtocolTests.m @@ -116,6 +116,8 @@ @interface MTRXPCProtocolTests @property (readwrite, strong) void (^handleReadClusterStateCache) (id controller, NSNumber * nodeId, NSNumber * _Nullable endpointId, NSNumber * _Nullable clusterId, NSNumber * _Nullable attributeId, void (^completion)(id _Nullable values, NSError * _Nullable error)); +@property (readwrite, strong) void (^handleDownloadLog)(id controller, NSNumber * nodeId, MTRDiagnosticLogType type, NSTimeInterval timeout, + void (^completion)(NSString * _Nullable url, NSError * _Nullable error)); @end @@ -274,6 +276,18 @@ - (void)readAttributeCacheWithController:(id _Nullable)controller }); } +- (void)downloadLogWithController:(id)controller + nodeId:(NSNumber *)nodeId + type:(MTRDiagnosticLogType)type + timeout:(NSTimeInterval)timeout + completion:(void (^)(NSString * _Nullable url, NSError * _Nullable error))completion +{ + dispatch_async(dispatch_get_main_queue(), ^{ + XCTAssertNotNil(self.handleDownloadLog); + self.handleDownloadLog(controller, nodeId, type, timeout, completion); + }); +} + - (void)setUp { [self setContinueAfterFailure:NO]; @@ -300,6 +314,44 @@ - (void)tearDown _xpcDisconnectExpectation = nil; } +- (void)testDownloadLogSuccess +{ + uint64_t myNodeId = 9876543210; + NSString * myBdxURL = @"bdx://foo"; + NSTimeInterval myTimeout = 10; + + XCTestExpectation * callExpectation = [self expectationWithDescription:@"XPC call received"]; + XCTestExpectation * responseExpectation = [self expectationWithDescription:@"XPC response received"]; + + __auto_type uuid = self.controllerUUID; + _handleDownloadLog = ^(id controller, NSNumber * nodeId, MTRDiagnosticLogType type, NSTimeInterval timeout, + void (^completion)(NSString * _Nullable url, NSError * _Nullable error)) { + XCTAssertTrue([controller isEqualToString:uuid]); + XCTAssertEqual([nodeId unsignedLongLongValue], myNodeId); + [callExpectation fulfill]; + completion(myBdxURL, nil); + }; + + __auto_type * device = [MTRBaseDevice deviceWithNodeID:@(myNodeId) controller:_remoteDeviceController]; + NSLog(@"Device acquired. Downloading..."); + [device downloadLogOfType:MTRDiagnosticLogTypeEndUserSupport + timeout:myTimeout + queue:dispatch_get_main_queue() + completion:^(NSURL * _Nullable url, NSError * _Nullable error) { + NSLog(@"Read url: %@", url); + XCTAssertNotNil(url); + XCTAssertNil(error); + [responseExpectation fulfill]; + self.xpcDisconnectExpectation = [self expectationWithDescription:@"XPC Disconnected"]; + }]; + + [self waitForExpectations:[NSArray arrayWithObjects:callExpectation, responseExpectation, nil] timeout:kTimeoutInSeconds]; + + // When download is done, connection should have been released + [self waitForExpectations:[NSArray arrayWithObject:_xpcDisconnectExpectation] timeout:kTimeoutInSeconds]; + XCTAssertNil(_xpcConnection); +} + - (void)testReadAttributeSuccess { uint64_t myNodeId = 9876543210;