From 7050e90b500eb578b60aced5f365966eff090863 Mon Sep 17 00:00:00 2001 From: Michael Rupp <95718139+mykrupp@users.noreply.github.com> Date: Wed, 10 Jul 2024 21:44:27 -0400 Subject: [PATCH 1/4] move AttemptToGetTimeFromTrustedNode out of TIME_SYNC_ENABLE_TSC_FEATURE since it is called out of TIME_SYNC_ENABLE_TSC_FEATURE blocks (#34273) --- .../time-synchronization-server/time-synchronization-server.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/clusters/time-synchronization-server/time-synchronization-server.h b/src/app/clusters/time-synchronization-server/time-synchronization-server.h index 2581c9712e7862..ecc9a68cc3fc12 100644 --- a/src/app/clusters/time-synchronization-server/time-synchronization-server.h +++ b/src/app/clusters/time-synchronization-server/time-synchronization-server.h @@ -124,9 +124,10 @@ class TimeSynchronizationServer : public FabricTable::Delegate void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override; void OnDone(ReadClient * apReadClient) override; - CHIP_ERROR AttemptToGetTimeFromTrustedNode(); #endif + CHIP_ERROR AttemptToGetTimeFromTrustedNode(); + // Platform event handler functions void OnPlatformEventFn(const DeviceLayer::ChipDeviceEvent & event); From 9609dffa25eb68f1e9552fb25f82b1b0b0a44831 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Wed, 10 Jul 2024 22:18:56 -0400 Subject: [PATCH 2/4] Update Darwin availability annotations. (#34292) --- src/darwin/Framework/CHIP/MTRDevice.h | 8 ++++---- src/darwin/Framework/CHIP/MTRDevice.mm | 2 +- src/darwin/Framework/CHIP/templates/availability.yaml | 8 +++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/darwin/Framework/CHIP/MTRDevice.h b/src/darwin/Framework/CHIP/MTRDevice.h index a8098a7174572e..1c31cbc26a661b 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.h +++ b/src/darwin/Framework/CHIP/MTRDevice.h @@ -114,7 +114,7 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) * * The delegate will be called on the provided queue, for attribute reports, event reports, and device state changes. */ -- (void)setDelegate:(id)delegate queue:(dispatch_queue_t)queue MTR_NEWLY_DEPRECATED("Please use addDelegate:queue:interestedPaths:"); +- (void)setDelegate:(id)delegate queue:(dispatch_queue_t)queue MTR_DEPRECATED("Please use addDelegate:queue:interestedPaths:", ios(16.1, 18.0), macos(13.0, 15.0), watchos(9.1, 11.0), tvos(16.1, 18.0)); /** * Adds a delegate to receive asynchronous callbacks about the device. @@ -123,7 +123,7 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) * * MTRDevice holds a weak reference to the delegate object. */ -- (void)addDelegate:(id)delegate queue:(dispatch_queue_t)queue MTR_NEWLY_AVAILABLE; +- (void)addDelegate:(id)delegate queue:(dispatch_queue_t)queue MTR_AVAILABLE(ios(18.0), macos(15.0), watchos(11.0), tvos(18.0)); /** * Adds a delegate to receive asynchronous callbacks about the device, and limit attribute and/or event reports to a specific set of paths. @@ -138,12 +138,12 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1)) * * MTRDevice holds a weak reference to the delegate object. */ -- (void)addDelegate:(id)delegate queue:(dispatch_queue_t)queue interestedPathsForAttributes:(NSArray * _Nullable)interestedPathsForAttributes interestedPathsForEvents:(NSArray * _Nullable)interestedPathsForEvents MTR_NEWLY_AVAILABLE; +- (void)addDelegate:(id)delegate queue:(dispatch_queue_t)queue interestedPathsForAttributes:(NSArray * _Nullable)interestedPathsForAttributes interestedPathsForEvents:(NSArray * _Nullable)interestedPathsForEvents MTR_AVAILABLE(ios(18.0), macos(15.0), watchos(11.0), tvos(18.0)); /** * Removes the delegate from receiving callbacks about the device. */ -- (void)removeDelegate:(id)delegate MTR_NEWLY_AVAILABLE; +- (void)removeDelegate:(id)delegate MTR_AVAILABLE(ios(18.0), macos(15.0), watchos(11.0), tvos(18.0)); /** * Read attribute in a designated attribute path. If there is no value available diff --git a/src/darwin/Framework/CHIP/MTRDevice.mm b/src/darwin/Framework/CHIP/MTRDevice.mm index 89478554cbdc8b..298b31a5406bce 100644 --- a/src/darwin/Framework/CHIP/MTRDevice.mm +++ b/src/darwin/Framework/CHIP/MTRDevice.mm @@ -802,7 +802,7 @@ - (void)addDelegate:(id)delegate queue:(dispatch_queue_t)queu [self _addDelegate:delegate queue:queue interestedPathsForAttributes:nil interestedPathsForEvents:nil]; } -- (void)addDelegate:(id)delegate queue:(dispatch_queue_t)queue interestedPathsForAttributes:(NSArray * _Nullable)interestedPathsForAttributes interestedPathsForEvents:(NSArray * _Nullable)interestedPathsForEvents MTR_NEWLY_AVAILABLE; +- (void)addDelegate:(id)delegate queue:(dispatch_queue_t)queue interestedPathsForAttributes:(NSArray * _Nullable)interestedPathsForAttributes interestedPathsForEvents:(NSArray * _Nullable)interestedPathsForEvents { MTR_LOG("%@ addDelegate %@ with interested attribute paths %@ event paths %@", self, delegate, interestedPathsForAttributes, interestedPathsForEvents); [self _addDelegate:delegate queue:queue interestedPathsForAttributes:interestedPathsForAttributes interestedPathsForEvents:interestedPathsForEvents]; diff --git a/src/darwin/Framework/CHIP/templates/availability.yaml b/src/darwin/Framework/CHIP/templates/availability.yaml index 88241256e2eca2..5d8aef90a50d95 100644 --- a/src/darwin/Framework/CHIP/templates/availability.yaml +++ b/src/darwin/Framework/CHIP/templates/availability.yaml @@ -9689,10 +9689,12 @@ versions: "future" provisional: clusters: - # Targeting Fall 2024 + # Targeting 1.4 + - CommissionerControl - ServiceArea - ThreadBorderRouterManagement - ThreadNetworkDirectory + - WaterHeaterManagement - WiFiNetworkManagement attributes: OccupancySensing: @@ -9711,10 +9713,10 @@ - HoldTimeLimitsStruct bitmaps: OccupancySensing: - # Targeting Fall 1.4 + # Targeting 1.4 - Feature bitmap values: Switch: Feature: - # Targeting Fall 2024 + # Targeting 1.4 - ActionSwitch From 3df8a1ec3dc43840e6ba13fa931b73a738a955ea Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Wed, 10 Jul 2024 23:00:18 -0400 Subject: [PATCH 3/4] Fix door lock endpoint initialization. (#34270) * Fix door lock endpoint initialization. A few issues here: * DoorLockServer::InitServer was resetting endpoint state for all endpoints when initializing any endpoint. * We had two separate arrays of per-endpoint data, one for the endpoint context, one for the delegate. The fix is to not touch the state of other endpoints when initializing an endpoint and to put all the per-endpoint state in one place. * Apply suggestions from code review Co-authored-by: Nivi Sarkar <55898241+nivi-apple@users.noreply.github.com> --------- Co-authored-by: Nivi Sarkar <55898241+nivi-apple@users.noreply.github.com> --- .../door-lock-server/door-lock-server.cpp | 100 +++++++++++------- .../door-lock-server/door-lock-server.h | 43 +++++--- 2 files changed, 93 insertions(+), 50 deletions(-) diff --git a/src/app/clusters/door-lock-server/door-lock-server.cpp b/src/app/clusters/door-lock-server/door-lock-server.cpp index 481587322216eb..4ab8744b4dbd32 100644 --- a/src/app/clusters/door-lock-server/door-lock-server.cpp +++ b/src/app/clusters/door-lock-server/door-lock-server.cpp @@ -51,39 +51,6 @@ static constexpr uint8_t DOOR_LOCK_ALIRO_CREDENTIAL_SIZE = 65; static constexpr uint32_t DOOR_LOCK_MAX_LOCK_TIMEOUT_SEC = MAX_INT32U_VALUE / MILLISECOND_TICKS_PER_SECOND; -static constexpr size_t kDoorLockDelegateTableSize = - MATTER_DM_DOOR_LOCK_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; - -static_assert(kDoorLockDelegateTableSize <= kEmberInvalidEndpointIndex, "Door Lock Delegate table size error"); - -namespace chip { -namespace app { -namespace Clusters { -namespace DoorLock { - -Delegate * gDelegateTable[kDoorLockDelegateTableSize] = { nullptr }; - -Delegate * GetDelegate(EndpointId endpoint) -{ - uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, DoorLock::Id, MATTER_DM_DOOR_LOCK_CLUSTER_SERVER_ENDPOINT_COUNT); - return (ep >= kDoorLockDelegateTableSize ? nullptr : gDelegateTable[ep]); -} - -void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate) -{ - uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, DoorLock::Id, MATTER_DM_DOOR_LOCK_CLUSTER_SERVER_ENDPOINT_COUNT); - // if endpoint is found - if (ep < ArraySize(gDelegateTable)) - { - gDelegateTable[ep] = delegate; - } -} - -} // namespace DoorLock -} // namespace Clusters -} // namespace app -} // namespace chip - DoorLockServer DoorLockServer::instance; class DoorLockClusterFabricDelegate : public chip::FabricTable::Delegate @@ -117,7 +84,18 @@ DoorLockServer & DoorLockServer::Instance() * * @param endpointId */ -void DoorLockServer::InitServer(chip::EndpointId endpointId) +void DoorLockServer::InitServer(EndpointId endpointId) +{ + CHIP_ERROR err = InitEndpoint(endpointId); + + // We have no way to communicate this error, so just log it. + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "Door Lock cluster initialization on endpoint %d failed: %" CHIP_ERROR_FORMAT, endpointId, err.Format()); + } +} + +CHIP_ERROR DoorLockServer::InitEndpoint(EndpointId endpointId, Delegate * delegate) { ChipLogProgress(Zcl, "Door Lock cluster initialized at endpoint #%u", endpointId); @@ -128,11 +106,48 @@ void DoorLockServer::InitServer(chip::EndpointId endpointId) } SetActuatorEnabled(endpointId, true); - for (auto & ep : mEndpointCtx) + auto * endpointContext = getContext(endpointId); + if (!endpointContext) + { + ChipLogError(Zcl, "Invalid endpoint %d for initializing lock server: no endpoint context available", endpointId); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + endpointContext->lockoutEndTimestamp = endpointContext->lockoutEndTimestamp.zero(); + endpointContext->wrongCodeEntryAttempts = 0; + endpointContext->delegate = delegate; + return CHIP_NO_ERROR; +} + +void DoorLockServer::ShutdownEndpoint(EndpointId endpointId) +{ + auto * endpointContext = getContext(endpointId); + if (!endpointContext) { - ep.lockoutEndTimestamp = ep.lockoutEndTimestamp.zero(); - ep.wrongCodeEntryAttempts = 0; + ChipLogError(Zcl, "Invalid endpoint %d for shutting down lock server: no endpoint context available", endpointId); + return; } + + endpointContext->delegate = nullptr; +} + +CHIP_ERROR DoorLockServer::SetDelegate(chip::EndpointId endpointId, chip::app::Clusters::DoorLock::Delegate * delegate) +{ + if (!delegate) + { + ChipLogError(Zcl, "Trying to set a null DoorLock::Delegate on endpoint %d", endpointId); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + auto * endpointContext = getContext(endpointId); + if (!endpointContext) + { + ChipLogError(Zcl, "Invalid endpoint %d for setting a delegate: no endpoint context available", endpointId); + return CHIP_ERROR_INVALID_ARGUMENT; + } + + endpointContext->delegate = delegate; + return CHIP_NO_ERROR; } bool DoorLockServer::SetLockState(chip::EndpointId endpointId, DlLockState newLockState) @@ -3445,6 +3460,17 @@ EmberAfDoorLockEndpointContext * DoorLockServer::getContext(chip::EndpointId end return nullptr; } +Delegate * DoorLockServer::GetDelegate(EndpointId endpointId) +{ + auto * endpointContext = getContext(endpointId); + if (!endpointContext) + { + return nullptr; + } + + return endpointContext->delegate; +} + bool DoorLockServer::HandleRemoteLockOperation(chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, LockOperationTypeEnum opType, RemoteLockOpHandler opHandler, const Optional & pinCode) diff --git a/src/app/clusters/door-lock-server/door-lock-server.h b/src/app/clusters/door-lock-server/door-lock-server.h index bf2798115c985c..ccb62dde46712b 100644 --- a/src/app/clusters/door-lock-server/door-lock-server.h +++ b/src/app/clusters/door-lock-server/door-lock-server.h @@ -86,20 +86,9 @@ struct EmberAfDoorLockEndpointContext { chip::System::Clock::Timestamp lockoutEndTimestamp; int wrongCodeEntryAttempts; + chip::app::Clusters::DoorLock::Delegate * delegate = nullptr; }; -namespace chip { -namespace app { -namespace Clusters { -namespace DoorLock { - -void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate); - -} // namespace DoorLock -} // namespace Clusters -} // namespace app -} // namespace chip - /** * @brief Door Lock Server Plugin class. */ @@ -112,7 +101,28 @@ class DoorLockServer : public chip::app::AttributeAccessInterface using Feature = chip::app::Clusters::DoorLock::Feature; using OnFabricRemovedCustomCallback = void (*)(chip::EndpointId endpointId, chip::FabricIndex fabricIndex); - void InitServer(chip::EndpointId endpointId); + /** + * Multiple InitEndpoint calls can happen for different endpoints. Calling + * InitEndpoint twice for the same endpoint requires a ShutdownEndpoint call + * for that endpoint in between. + * + * A DoorLock::Delegate is optional, but needs to be provided in either + * InitEndpoint or in a separate SetDelegate call for Aliro features, and + * possibly other new features, to work. + */ + CHIP_ERROR InitEndpoint(chip::EndpointId endpointId, chip::app::Clusters::DoorLock::Delegate * delegate = nullptr); + + void ShutdownEndpoint(chip::EndpointId endpointId); + + // InitServer is a deprecated alias for InitEndpoint with no delegate. + void InitServer(chip::EndpointId endpointid); + + /** + * Delegate is not supposed to be null. Removing a delegate + * should only happen when shutting down the door lock cluster on the + * endpoint, via ShutdownEndpoint. + */ + CHIP_ERROR SetDelegate(chip::EndpointId endpointId, chip::app::Clusters::DoorLock::Delegate * delegate); /** * Updates the LockState attribute with new value and sends LockOperation event. @@ -488,6 +498,13 @@ class DoorLockServer : public chip::app::AttributeAccessInterface static void sendClusterResponse(chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, chip::Protocols::InteractionModel::ClusterStatusCode status); + /** + * Get the DoorLock::Delegate for the given endpoint, if any. Will return + * null if there is no door lock server initialized on that endpoint or if + * there is no delegate associated with the initialized server. + */ + chip::app::Clusters::DoorLock::Delegate * GetDelegate(chip::EndpointId endpointId); + /** * @brief Common handler for LockDoor, UnlockDoor, UnlockWithTimeout commands * From c70a769b79258dfec831dd3cb1eedf6dbecf280b Mon Sep 17 00:00:00 2001 From: Yufeng Wang Date: Wed, 10 Jul 2024 20:32:17 -0700 Subject: [PATCH 4/4] Add Fabric Synchronization Guide (#34195) * Add Fabric Synchronization Guide * Update docs/guides/fabric_synchronization_guide.md Co-authored-by: Andrei Litvin * Address review comments * Update docs/guides/fabric_synchronization_guide.md Co-authored-by: Andrei Litvin * Update scripts with mutiple pathes * grep for /fabric-bridge-app without the prefix --------- Co-authored-by: Andrei Litvin --- docs/guides/fabric_synchronization_guide.md | 164 ++++++++++++++++++ .../images/matter_fabric_synchronization.png | Bin 0 -> 38846 bytes .../fabric-admin/scripts/run_fabric_sink.sh | 78 +++++++++ .../fabric-admin/scripts/run_fabric_source.sh | 116 +++++++++++++ .../scripts/stop_fabric_source.sh | 22 +++ 5 files changed, 380 insertions(+) create mode 100644 docs/guides/fabric_synchronization_guide.md create mode 100644 docs/guides/images/matter_fabric_synchronization.png create mode 100755 examples/fabric-admin/scripts/run_fabric_sink.sh create mode 100755 examples/fabric-admin/scripts/run_fabric_source.sh create mode 100755 examples/fabric-admin/scripts/stop_fabric_source.sh diff --git a/docs/guides/fabric_synchronization_guide.md b/docs/guides/fabric_synchronization_guide.md new file mode 100644 index 00000000000000..36107744a97930 --- /dev/null +++ b/docs/guides/fabric_synchronization_guide.md @@ -0,0 +1,164 @@ +# Fabric Synchronization Guide + +- [Fabric Synchronization Guide](#fabric-synchronization-guide) + - [Fabric Sync Example Applications](#fabric-sync-example-applications) + - [Run Fabric Sync Demo on RP4](#run-fabric-sync-demo-on-rp4) + +## Fabric Sync Example Applications + +Fabric-Admin and Fabric-Bridge example applications are provided to demonstrate +Fabric Synchronization feature. You can find them in the examples. + +![matter_fabric_synchronization](images/matter_fabric_synchronization.png) + +Fabric-Admin example app implements the Fabric Administrator role and +communicates with the Fabric-Bridge-App on the other side, facilitating the +Fabric Synchronization process. + +Fabric-Bridge-App example app implements the Aggregator device type with Fabric +Synchronization condition met and demonstrates the end-to-end Fabric +Synchronization feature using dynamic endpoints. + +Fabric Synchronization can be triggered from either side. The initiator of the +Fabric Synchronization process, who shares their devices, takes on the +Commissioner role. The recipient of the Fabric Synchronization request, who +receives the shared devices, assumes the Commissionee role. This flexibility +enables a seamless and efficient synchronization process. + +### Building the Example Application + +- Building the Fabric-Admin Application + + [Fabric-Admin](https://github.com/project-chip/connectedhomeip/tree/master/examples/fabric-admin/README.md) + +* Building the Fabric-Bridge Application + + [Fabric-Bridge](https://github.com/project-chip/connectedhomeip/tree/master/examples/fabric-bridge-app/linux/README.md) + +## Run Fabric Sync Demo on RP4 + +### Setup Fabric Source + +Connect to the Fabric Source server: + +``` +ssh ubuntu@xxx.xxx.xxx.xxx +``` + +Password: + +Run the Fabric Source script: + +``` +./run_fabric_source.sh +``` + +### Setup Fabric Sink + +Connect to the Fabric Sink server: + +``` +ssh ubuntu@xxx.xxx.xxx.xxx +``` + +Password: + +Run the Fabric Sink script: + +``` +./run_fabric_sink.sh +``` + +### Fabric Sync Setup + +Enable Fabric Auto Sync: + +In Fabric-Sync console: + +``` +fabricsync enable-auto-sync 1 +``` + +Pair the Fabric-Source bridge to Fabric-Sync with node ID 1: + +``` +fabricsync add-bridge 1 +``` + +### Pair Light Example to Fabric-Source + +Pair the Light Example with node ID 3 using its payload number: + +``` +pairing already-discovered 3 20202021 5540 +``` + +After the Light Example is successfully paired in Fabric-Source, it will be +synced to Fabric-Sink with a new assigned node ID. + +Toggle the Light Example: + +From Fabric-Source: + +``` +onoff on 1 +onoff off 1 +``` + +From Fabric-Sink: (Use the node ID assigned) + +``` +onoff on x 1 +onoff off x 1 +``` + +### Remove Light Example from Fabric-Source + +Unpair the Light Example: + +``` +pairing unpair +``` + +After the Light Example is successfully unpaired from Fabric-Source, it will +also be removed from the Fabric-Sink. + +### Pair Commercial Switch to Fabric-Source + +Pair the switch using its payload number: + +In Fabric-Source console: + +``` +pairing code-wifi +``` + +After the switch is successfully paired in Fabric-Source, it will be synced to +Fabric-Sink with a new assigned node ID. + +Toggle the switch: + +From Fabric-Source: + +``` +onoff on 1 +onoff off 1 +``` + +From Fabric-Sink: (Use the node ID assigned) + +``` +onoff on 1 +onoff off 1 +``` + +### Remove Switch from Fabric-Source + +Unpair the switch: + +``` +pairing unpair +``` + +After the switch is successfully unpaired from Fabric-Source, it will also be +removed from the Fabric-Sink. diff --git a/docs/guides/images/matter_fabric_synchronization.png b/docs/guides/images/matter_fabric_synchronization.png new file mode 100644 index 0000000000000000000000000000000000000000..95c99c4fe150b952513bb0576912e6004dbffefc GIT binary patch literal 38846 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 JX!Qrr{|_*Mk|_WH literal 0 HcmV?d00001 diff --git a/examples/fabric-admin/scripts/run_fabric_sink.sh b/examples/fabric-admin/scripts/run_fabric_sink.sh new file mode 100755 index 00000000000000..3013965479268a --- /dev/null +++ b/examples/fabric-admin/scripts/run_fabric_sink.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Default paths +DEFAULT_CHOICES=( + "./fabric-admin" + "out/debug/standalone/fabric-admin" + "out/linux-x64-fabric-admin/fabric-admin" + "out/darwin-arm64-fabric-admin/fabric-admin" +) +FABRIC_ADMIN_LOG="/tmp/fabric_admin.log" +FABRIC_ADMIN_PATH="" + +# Function to find fabric-admin binary +find_fabric_admin() { + local choices=("$@") + for path in "${choices[@]}"; do + if [[ -e "$path" ]]; then + echo "$path" + return 0 + fi + done + return 1 +} + +# Parse arguments +VERBOSE=false +SPECIFIED_PATH="" + +for arg in "$@"; do + case $arg in + --verbose) + VERBOSE=true + ;; + --path=*) + SPECIFIED_PATH="${arg#*=}" + ;; + esac +done + +# Use specified path if provided +if [[ -n "$SPECIFIED_PATH" ]]; then + if [[ -e "$SPECIFIED_PATH" ]]; then + FABRIC_ADMIN_PATH="$SPECIFIED_PATH" + else + echo >&2 "Specified path does not exist: $SPECIFIED_PATH" + exit 1 + fi +else + FABRIC_ADMIN_PATH=$(find_fabric_admin "${DEFAULT_CHOICES[@]}") + if [[ $? -ne 0 ]]; then + echo >&2 "Could not find the fabric-admin binary" + exit 1 + fi +fi + +echo "PATH IS: $FABRIC_ADMIN_PATH" + +# Kill fabric-admin if it is running +echo "Checking for running fabric-admin process..." +fabric_admin_pid=$(pgrep -f "$FABRIC_ADMIN_PATH") +if [[ -n "$fabric_admin_pid" ]]; then + echo "Found fabric-admin with PID $fabric_admin_pid, attempting to kill..." + kill -9 "$fabric_admin_pid" + echo "Killed fabric-admin with PID $fabric_admin_pid" +fi + +# Remove /tmp/chip_* files and directories +echo "Removing /tmp/chip_* files and directories..." +sudo rm -rf /tmp/chip_* +echo "Removed /tmp/chip_* files and directories" + +# Start fabric-admin with or without log file path based on --verbose option +echo "Starting fabric-admin..." +if [ "$VERBOSE" = true ]; then + "$FABRIC_ADMIN_PATH" +else + "$FABRIC_ADMIN_PATH" --log-file-path "$FABRIC_ADMIN_LOG" +fi diff --git a/examples/fabric-admin/scripts/run_fabric_source.sh b/examples/fabric-admin/scripts/run_fabric_source.sh new file mode 100755 index 00000000000000..95df7a135bb596 --- /dev/null +++ b/examples/fabric-admin/scripts/run_fabric_source.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# Get the path to the current script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Default paths +DEFAULT_ADMIN_CHOICES=( + "./fabric-admin" + "out/debug/standalone/fabric-admin" + "out/linux-x64-fabric-admin/fabric-admin" + "out/darwin-arm64-fabric-admin/fabric-admin" +) +DEFAULT_BRIDGE_CHOICES=( + "./fabric-bridge-app" + "out/debug/standalone/fabric-bridge-app" + "out/linux-x64-fabric-bridge-app/fabric-bridge-app" + "out/darwin-arm64-fabric-bridge-app/fabric-bridge-app" +) +FABRIC_ADMIN_LOG="/tmp/fabric_admin.log" +FABRIC_BRIDGE_APP_LOG="/tmp/fabric_bridge_app.log" +FABRIC_ADMIN_PATH="" +FABRIC_BRIDGE_APP_PATH="" + +# Function to find a binary +find_binary() { + local choices=("$@") + for path in "${choices[@]}"; do + if [[ -e "$path" ]]; then + echo "$path" + return 0 + fi + done + return 1 +} + +# Parse arguments +VERBOSE=false +SPECIFIED_ADMIN_PATH="" +SPECIFIED_BRIDGE_PATH="" + +for arg in "$@"; do + case $arg in + --verbose) + VERBOSE=true + ;; + --admin-path=*) + SPECIFIED_ADMIN_PATH="${arg#*=}" + ;; + --bridge-path=*) + SPECIFIED_BRIDGE_PATH="${arg#*=}" + ;; + esac +done + +# Use specified paths if provided +if [[ -n "$SPECIFIED_ADMIN_PATH" ]]; then + if [[ -e "$SPECIFIED_ADMIN_PATH" ]]; then + FABRIC_ADMIN_PATH="$SPECIFIED_ADMIN_PATH" + else + echo >&2 "Specified admin path does not exist: $SPECIFIED_ADMIN_PATH" + exit 1 + fi +else + FABRIC_ADMIN_PATH=$(find_binary "${DEFAULT_ADMIN_CHOICES[@]}") + if [[ $? -ne 0 ]]; then + echo >&2 "Could not find the fabric-admin binary" + exit 1 + fi +fi + +if [[ -n "$SPECIFIED_BRIDGE_PATH" ]]; then + if [[ -e "$SPECIFIED_BRIDGE_PATH" ]]; then + FABRIC_BRIDGE_APP_PATH="$SPECIFIED_BRIDGE_PATH" + else + echo >&2 "Specified bridge path does not exist: $SPECIFIED_BRIDGE_PATH" + exit 1 + fi +else + FABRIC_BRIDGE_APP_PATH=$(find_binary "${DEFAULT_BRIDGE_CHOICES[@]}") + if [[ $? -ne 0 ]]; then + echo >&2 "Could not find the fabric-bridge-app binary" + exit 1 + fi +fi + +echo "Admin path: $FABRIC_ADMIN_PATH" +echo "Bridge path: $FABRIC_BRIDGE_APP_PATH" + +# Determine the path to stop_fabric_source.sh based on the location of run_fabric_source.sh +RUN_FABRIC_SOURCE_PATH=$(find_binary "$SCRIPT_DIR/run_fabric_source.sh") +if [[ $? -ne 0 ]]; then + echo >&2 "Could not find the run_fabric_source.sh script" + exit 1 +fi +STOP_FABRIC_SOURCE_PATH="${RUN_FABRIC_SOURCE_PATH/run_fabric_source/stop_fabric_source}" + +# Stop any running instances and clean up +if [[ -e "$STOP_FABRIC_SOURCE_PATH" ]]; then + "$STOP_FABRIC_SOURCE_PATH" +else + echo >&2 "Could not find the stop_fabric_source.sh script" + exit 1 +fi + +# Start fabric-bridge-app if available and redirect its output to /dev/null +if [ -f "$FABRIC_BRIDGE_APP_PATH" ]; then + "$FABRIC_BRIDGE_APP_PATH" >"$FABRIC_BRIDGE_APP_LOG" 2>&1 & + echo "Started fabric-bridge-app" +fi + +# Start fabric-admin with or without log file path based on --verbose option +if [ "$VERBOSE" = true ]; then + "$FABRIC_ADMIN_PATH" +else + "$FABRIC_ADMIN_PATH" --log-file-path "$FABRIC_ADMIN_LOG" +fi diff --git a/examples/fabric-admin/scripts/stop_fabric_source.sh b/examples/fabric-admin/scripts/stop_fabric_source.sh new file mode 100755 index 00000000000000..85fff9e3a878cc --- /dev/null +++ b/examples/fabric-admin/scripts/stop_fabric_source.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +FABRIC_ADMIN_PATH="/fabric-admin" +FABRIC_BRIDGE_APP_PATH="/fabric-bridge-app" + +# Kill fabric-admin if it is running +fabric_admin_pid=$(pgrep -f "$FABRIC_ADMIN_PATH") +if [ ! -z "$fabric_admin_pid" ]; then + kill -9 "$fabric_admin_pid" + echo "Killed fabric-admin with PID $fabric_admin_pid" +fi + +# Kill fabric-bridge-app if it is running +fabric_bridge_app_pid=$(pgrep -f "$FABRIC_BRIDGE_APP_PATH") +if [ ! -z "$fabric_bridge_app_pid" ]; then + kill -9 "$fabric_bridge_app_pid" + echo "Killed fabric-bridge-app with PID $fabric_bridge_app_pid" +fi + +# Remove /tmp/chip_* files and directories +sudo rm -rf /tmp/chip_* +echo "Removed /tmp/chip_* files and directories"