TV: Add sample app tools for triggering client commands (#32365)
* Add sample app tools for triggering test cases
* Fix locking when obtaining passcode from content app
* Fix messages test case
* fix ci
* Restyle TV: Add sample app tools for triggering client commands (#32366)
* Restyled by whitespace
* Restyled by google-java-format
---------
Co-authored-by: Restyled.io <commits@restyled.io>
* fix ci, address comments
* fix ci
* address comments
* address comments
---------
Co-authored-by: restyled-io[bot] <32688539+restyled-io[bot]@users.noreply.github.com>
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/TerminalFragment.java b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/TerminalFragment.java
index 660025e..30faf14 100644
--- a/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/TerminalFragment.java
+++ b/examples/tv-app/android/App/platform-app/src/main/java/com/matter/tv/server/fragments/TerminalFragment.java
@@ -24,6 +24,8 @@
private static String TERMINAL_INSTRUCTIONS =
"add <vid> [<pid>] Add app with given vendor ID [1, 2, 9050]. Usage: add 9050\r\n"
+ "remove <endpoint> Remove app at given endpoint [6, 7, etc]. Usage: remove 6\r\n"
+ + "appobserver <appendpoint> <clientnodeindex> <data> <hint> Send app observer command to client node of the given app endpoint. Usage: appobserver 4 0 data hint\r\n"
+ + "printclients <appendpoint> Print list of client nodes for the given app endpoint. Usage: printclients 4\r\n"
+ "setpin <endpoint> <pincode> Set pincode for app with given endpoint ID. Usage: setpin 6 34567890\r\n"
+ "commission <udc-entry> Commission given udc-entry using given pincode from corresponding app. Usage:"
+ "commission 0\r\n"
diff --git a/examples/tv-app/android/java/AppImpl.cpp b/examples/tv-app/android/java/AppImpl.cpp
index 3c50d75..1dfae0f 100644
--- a/examples/tv-app/android/java/AppImpl.cpp
+++ b/examples/tv-app/android/java/AppImpl.cpp
@@ -324,6 +324,7 @@
ChipLogProgress(DeviceLayer, " Looking next=%s ", app->GetApplicationBasicDelegate()->GetCatalogVendorApp()->applicationId);
if (app->GetApplicationBasicDelegate()->GetCatalogVendorApp()->Matches(vendorApp))
{
+ // need to think about loading apk here?
ContentAppPlatform::GetInstance().AddContentApp(app, &contentAppEndpoint, Span<DataVersion>(gDataVersions[i]),
Span<const EmberAfDeviceType>(gContentAppDeviceType));
return app;
@@ -422,11 +423,16 @@
ChipLogProgress(DeviceLayer,
"ContentAppFactoryImpl GetAllowedClusterListForStaticEndpoint priviledged vendor accessible clusters "
"being returned.");
- return { chip::app::Clusters::Descriptor::Id, chip::app::Clusters::OnOff::Id,
- chip::app::Clusters::WakeOnLan::Id, chip::app::Clusters::MediaPlayback::Id,
- chip::app::Clusters::LowPower::Id, chip::app::Clusters::KeypadInput::Id,
- chip::app::Clusters::ContentLauncher::Id, chip::app::Clusters::AudioOutput::Id,
- chip::app::Clusters::ApplicationLauncher::Id };
+ return { chip::app::Clusters::Descriptor::Id,
+ chip::app::Clusters::OnOff::Id,
+ chip::app::Clusters::WakeOnLan::Id,
+ chip::app::Clusters::MediaPlayback::Id,
+ chip::app::Clusters::LowPower::Id,
+ chip::app::Clusters::KeypadInput::Id,
+ chip::app::Clusters::ContentLauncher::Id,
+ chip::app::Clusters::AudioOutput::Id,
+ chip::app::Clusters::ApplicationLauncher::Id,
+ chip::app::Clusters::Messages::Id };
}
ChipLogProgress(
DeviceLayer,
@@ -434,7 +440,8 @@
return { chip::app::Clusters::Descriptor::Id, chip::app::Clusters::OnOff::Id,
chip::app::Clusters::WakeOnLan::Id, chip::app::Clusters::MediaPlayback::Id,
chip::app::Clusters::LowPower::Id, chip::app::Clusters::KeypadInput::Id,
- chip::app::Clusters::ContentLauncher::Id, chip::app::Clusters::AudioOutput::Id };
+ chip::app::Clusters::ContentLauncher::Id, chip::app::Clusters::AudioOutput::Id,
+ chip::app::Clusters::Messages::Id };
}
return {};
}
diff --git a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java
index b31f14a..4f115c5 100644
--- a/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java
+++ b/examples/tv-app/android/java/src/com/matter/tv/server/tvapp/MessagesManagerStub.java
@@ -31,13 +31,6 @@
public MessagesManagerStub(int endpoint) {
this.endpoint = endpoint;
Log.d(TAG, "MessagesManagerStub: at " + this.endpoint);
-
- HashMap<Long, String> responseOptions = new HashMap<Long, String>();
- responseOptions.put(new Long(1), "Yes");
- responseOptions.put(new Long(2), "No");
- presentMessages(
- "31323334353637383930313233343536", 1, 1, 30, 60000, "TestMessage", responseOptions);
- Log.d(TAG, "MessagesManagerStub: added dummy message");
}
@Override
diff --git a/examples/tv-app/tv-common/shell/AppTvShellCommands.cpp b/examples/tv-app/tv-common/shell/AppTvShellCommands.cpp
index c66beb9..6b94676 100644
--- a/examples/tv-app/tv-common/shell/AppTvShellCommands.cpp
+++ b/examples/tv-app/tv-common/shell/AppTvShellCommands.cpp
@@ -193,6 +193,11 @@
#if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
streamer_printf(sout, " add <vid> [<pid>] Add app with given vendor ID [1, 2, 9050]. Usage: app add 9050\r\n");
streamer_printf(sout, " remove <endpoint> Remove app at given endpoint [6, 7, etc]. Usage: app remove 6\r\n");
+ streamer_printf(sout,
+ " appobserver <appendpoint> <clientnodeindex> <data> <hint> Send app observer command to client node of "
+ "the given app endpoint. Usage: appobserver 4 0 data hint\r\n");
+ streamer_printf(
+ sout, " printclients <appendpoint> Print list of client nodes for the given app endpoint. Usage: printclients 4\r\n");
streamer_printf(
sout, " setpin <endpoint> <pincode> Set pincode for app with given endpoint ID. Usage: app setpin 6 34567890\r\n");
streamer_printf(sout,
@@ -277,6 +282,63 @@
return CHIP_NO_ERROR;
}
+ else if (strcmp(argv[0], "printclients") == 0)
+ {
+ if (argc < 2)
+ {
+ return PrintAllCommands();
+ }
+ char * eptr;
+
+ uint16_t endpoint = (uint16_t) strtol(argv[1], &eptr, 10);
+ ContentApp * app = ContentAppPlatform::GetInstance().GetContentApp(endpoint);
+ if (app == nullptr)
+ {
+ ChipLogProgress(DeviceLayer, "app not found");
+ return CHIP_ERROR_BAD_REQUEST;
+ }
+ uint8_t count = app->GetClientNodeCount();
+ ChipLogProgress(DeviceLayer, " node count: %d", count);
+ for (uint8_t i = 0; i < count; i++)
+ {
+ NodeId node = app->GetClientNode(i);
+ ChipLogProgress(DeviceLayer, " node[%d] " ChipLogFormatX64, i, ChipLogValueX64(node));
+ }
+ }
+#if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE
+ else if (strcmp(argv[0], "appobserver") == 0)
+ {
+ if (argc < 5)
+ {
+ return PrintAllCommands();
+ }
+ char * eptr;
+
+ uint16_t endpoint = (uint16_t) strtol(argv[1], &eptr, 10);
+ ContentApp * app = ContentAppPlatform::GetInstance().GetContentApp(endpoint);
+ if (app == nullptr)
+ {
+ ChipLogProgress(DeviceLayer, "app not found");
+ return CHIP_ERROR_BAD_REQUEST;
+ }
+ uint8_t clientNodeIndex = (uint8_t) strtol(argv[2], &eptr, 10);
+ if (clientNodeIndex >= app->GetClientNodeCount())
+ {
+ ChipLogProgress(DeviceLayer, "illegal client node index");
+ return CHIP_ERROR_BAD_REQUEST;
+ }
+ NodeId clientNode = app->GetClientNode(clientNodeIndex);
+
+ char * data = argv[3];
+ char * encodingHint = argv[4];
+
+ app->SendAppObserverCommand(GetDeviceCommissioner(), clientNode, data, encodingHint);
+
+ ChipLogProgress(DeviceLayer, "sent appobserver command");
+
+ return CHIP_NO_ERROR;
+ }
+#endif // CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE
else if (strcmp(argv[0], "setpin") == 0)
{
if (argc < 3)
diff --git a/examples/tv-app/tv-common/src/AppTv.cpp b/examples/tv-app/tv-common/src/AppTv.cpp
index 939e14b..8fd076b 100644
--- a/examples/tv-app/tv-common/src/AppTv.cpp
+++ b/examples/tv-app/tv-common/src/AppTv.cpp
@@ -549,11 +549,16 @@
ChipLogProgress(DeviceLayer,
"ContentAppFactoryImpl GetAllowedClusterListForStaticEndpoint priviledged vendor accessible clusters "
"being returned.");
- return { chip::app::Clusters::Descriptor::Id, chip::app::Clusters::OnOff::Id,
- chip::app::Clusters::WakeOnLan::Id, chip::app::Clusters::MediaPlayback::Id,
- chip::app::Clusters::LowPower::Id, chip::app::Clusters::KeypadInput::Id,
- chip::app::Clusters::ContentLauncher::Id, chip::app::Clusters::AudioOutput::Id,
- chip::app::Clusters::ApplicationLauncher::Id };
+ return { chip::app::Clusters::Descriptor::Id,
+ chip::app::Clusters::OnOff::Id,
+ chip::app::Clusters::WakeOnLan::Id,
+ chip::app::Clusters::MediaPlayback::Id,
+ chip::app::Clusters::LowPower::Id,
+ chip::app::Clusters::KeypadInput::Id,
+ chip::app::Clusters::ContentLauncher::Id,
+ chip::app::Clusters::AudioOutput::Id,
+ chip::app::Clusters::ApplicationLauncher::Id,
+ chip::app::Clusters::Messages::Id }; // TODO: messages?
}
ChipLogProgress(
DeviceLayer,
@@ -561,7 +566,8 @@
return { chip::app::Clusters::Descriptor::Id, chip::app::Clusters::OnOff::Id,
chip::app::Clusters::WakeOnLan::Id, chip::app::Clusters::MediaPlayback::Id,
chip::app::Clusters::LowPower::Id, chip::app::Clusters::KeypadInput::Id,
- chip::app::Clusters::ContentLauncher::Id, chip::app::Clusters::AudioOutput::Id };
+ chip::app::Clusters::ContentLauncher::Id, chip::app::Clusters::AudioOutput::Id,
+ chip::app::Clusters::Messages::Id };
}
return {};
}
diff --git a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CertTestFragment.java b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CertTestFragment.java
index 340563c..7ee9fa3 100644
--- a/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CertTestFragment.java
+++ b/examples/tv-casting-app/android/App/app/src/main/java/com/chip/casting/app/CertTestFragment.java
@@ -310,6 +310,13 @@
});
runAndWait(
+ "messages_presentMessages",
+ successFailureCallback,
+ () -> {
+ tvCastingApp.messages_presentMessages(kTVApp, "CastingAppTestMessage", callback);
+ });
+
+ runAndWait(
"mediaPlayback_subscribeToCurrentState",
successFailureCallback,
() -> {
diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java
index 2485298..9dd0fa5 100644
--- a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java
+++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java
@@ -599,6 +599,9 @@
public native boolean onOff_toggle(ContentApp contentApp, Object responseHandler);
+ public native boolean messages_presentMessages(
+ ContentApp contentApp, String messageText, Object responseHandler);
+
static {
System.loadLibrary("TvCastingApp");
}
diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/Constants.h b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/Constants.h
index b194754..735cabf 100644
--- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/Constants.h
+++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/Constants.h
@@ -37,6 +37,7 @@
MediaPlayback_Seek,
MediaPlayback_SkipForward,
MediaPlayback_SkipBackward,
+ Messages_PresentMessagesRequest,
ApplicationLauncher_LaunchApp,
ApplicationLauncher_StopApp,
ApplicationLauncher_HideApp,
diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp
index 0cbfab8..6dbdfba 100644
--- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp
+++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp
@@ -865,6 +865,36 @@
return (err == CHIP_NO_ERROR);
}
+JNI_METHOD(jboolean, messages_1presentMessages)
+(JNIEnv * env, jobject, jobject contentApp, jstring messageText, jobject jResponseHandler)
+{
+ chip::DeviceLayer::StackLock lock;
+
+ ChipLogProgress(AppServer, "JNI_METHOD messages_presentMessages called");
+ const char * nativeMessageText = env->GetStringUTFChars(messageText, 0);
+
+ TargetEndpointInfo endpoint;
+ CHIP_ERROR err = convertJContentAppToTargetEndpointInfo(contentApp, endpoint);
+ VerifyOrExit(err == CHIP_NO_ERROR,
+ ChipLogError(AppServer, "Conversion from jobject contentApp to TargetEndpointInfo * failed: %" CHIP_ERROR_FORMAT,
+ err.Format()));
+
+ err = TvCastingAppJNIMgr().getMediaCommandResponseHandler(Messages_PresentMessagesRequest).SetUp(env, jResponseHandler);
+ VerifyOrExit(CHIP_NO_ERROR == err,
+ ChipLogError(AppServer, "MatterCallbackHandlerJNI.SetUp failed %" CHIP_ERROR_FORMAT, err.Format()));
+
+ err = CastingServer::GetInstance()->Messages_PresentMessagesRequest(&endpoint, nativeMessageText, [](CHIP_ERROR err) {
+ TvCastingAppJNIMgr().getMediaCommandResponseHandler(Messages_PresentMessagesRequest).Handle(err);
+ });
+ VerifyOrExit(CHIP_NO_ERROR == err,
+ ChipLogError(AppServer, "CastingServer.Messages_PresentMessagesRequest failed %" CHIP_ERROR_FORMAT, err.Format()));
+
+ env->ReleaseStringUTFChars(messageText, nativeMessageText);
+
+exit:
+ return (err == CHIP_NO_ERROR);
+}
+
JNI_METHOD(jboolean, mediaPlayback_1play)
(JNIEnv * env, jobject, jobject contentApp, jobject jResponseHandler)
{
diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn
index ea6607a..72f14fb 100644
--- a/examples/tv-casting-app/tv-casting-common/BUILD.gn
+++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn
@@ -68,6 +68,7 @@
"include/MediaPlayback.h",
"include/MediaReadBase.h",
"include/MediaSubscriptionBase.h",
+ "include/Messages.h",
"include/OnOff.h",
"include/PersistenceManager.h",
"include/TargetEndpointInfo.h",
@@ -83,6 +84,7 @@
"src/KeypadInput.cpp",
"src/LevelControl.cpp",
"src/MediaPlayback.cpp",
+ "src/Messages.cpp",
"src/OnOff.cpp",
"src/PersistenceManager.cpp",
"src/TargetEndpointInfo.cpp",
diff --git a/examples/tv-casting-app/tv-casting-common/include/CastingServer.h b/examples/tv-casting-app/tv-casting-common/include/CastingServer.h
index 349edae..d64f504 100644
--- a/examples/tv-casting-app/tv-casting-common/include/CastingServer.h
+++ b/examples/tv-casting-app/tv-casting-common/include/CastingServer.h
@@ -27,6 +27,7 @@
#include "KeypadInput.h"
#include "LevelControl.h"
#include "MediaPlayback.h"
+#include "Messages.h"
#include "OnOff.h"
#include "PersistenceManager.h"
#include "TargetEndpointInfo.h"
@@ -187,6 +188,12 @@
CHIP_ERROR OnOff_Toggle(TargetEndpointInfo * endpoint, std::function<void(CHIP_ERROR)> responseCallback);
/**
+ * @brief Messages cluster
+ */
+ CHIP_ERROR Messages_PresentMessagesRequest(TargetEndpointInfo * endpoint, const char * messageText,
+ std::function<void(CHIP_ERROR)> responseCallback);
+
+ /**
* @brief Media Playback cluster
*/
CHIP_ERROR MediaPlayback_Play(TargetEndpointInfo * endpoint, std::function<void(CHIP_ERROR)> responseCallback);
@@ -511,6 +518,11 @@
ToggleCommand mToggleCommand;
/**
+ * @brief OnOff cluster
+ */
+ PresentMessagesRequestCommand mPresentMessagesRequestCommand;
+
+ /**
* @brief Media Playback cluster
*/
PlayCommand mPlayCommand;
diff --git a/examples/tv-casting-app/tv-casting-common/include/Messages.h b/examples/tv-casting-app/tv-casting-common/include/Messages.h
new file mode 100644
index 0000000..623ede9
--- /dev/null
+++ b/examples/tv-casting-app/tv-casting-common/include/Messages.h
@@ -0,0 +1,36 @@
+/*
+ *
+ * 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 "MediaCommandBase.h"
+#include "MediaSubscriptionBase.h"
+
+#include <controller/CHIPCluster.h>
+#include <functional>
+
+#include <app-common/zap-generated/cluster-objects.h>
+
+// COMMAND CLASSES
+class PresentMessagesRequestCommand : public MediaCommandBase<chip::app::Clusters::Messages::Commands::PresentMessagesRequest::Type,
+ chip::app::DataModel::NullObjectType>
+{
+public:
+ PresentMessagesRequestCommand() : MediaCommandBase(chip::app::Clusters::Messages::Id) {}
+
+ CHIP_ERROR
+ Invoke(const char * messageText, std::function<void(CHIP_ERROR)> responseCallback);
+};
diff --git a/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp b/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp
index a3de0a2..4cb7273 100644
--- a/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp
+++ b/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp
@@ -870,6 +870,16 @@
}
/**
+ * @brief Messages cluster
+ */
+CHIP_ERROR CastingServer::Messages_PresentMessagesRequest(TargetEndpointInfo * endpoint, const char * messageText,
+ std::function<void(CHIP_ERROR)> responseCallback)
+{
+ ReturnErrorOnFailure(mPresentMessagesRequestCommand.SetTarget(mActiveTargetVideoPlayerInfo, endpoint->GetEndpointId()));
+ return mPresentMessagesRequestCommand.Invoke(messageText, responseCallback);
+}
+
+/**
* @brief Media Playback cluster
*/
CHIP_ERROR CastingServer::MediaPlayback_Play(TargetEndpointInfo * endpoint, std::function<void(CHIP_ERROR)> responseCallback)
diff --git a/examples/tv-casting-app/tv-casting-common/src/Messages.cpp b/examples/tv-casting-app/tv-casting-common/src/Messages.cpp
new file mode 100644
index 0000000..ea6176f
--- /dev/null
+++ b/examples/tv-casting-app/tv-casting-common/src/Messages.cpp
@@ -0,0 +1,38 @@
+/*
+ *
+ * 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 "Messages.h"
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::app::Clusters;
+using namespace chip::app::Clusters::Messages;
+
+CHIP_ERROR PresentMessagesRequestCommand::Invoke(const char * messageText, std::function<void(CHIP_ERROR)> responseCallback)
+{
+ Messages::Commands::PresentMessagesRequest::Type request;
+ uint8_t buf[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5 };
+
+ request.messageID = ByteSpan(buf, sizeof(buf));
+ request.messageText = CharSpan::fromCharString(messageText);
+ request.priority = MessagePriorityEnum(static_cast<uint8_t>(0));
+ request.startTime = DataModel::Nullable<uint32_t>(static_cast<uint32_t>(0));
+ request.duration = DataModel::Nullable<uint64_t>(static_cast<uint64_t>(60 * 1000));
+
+ return MediaCommandBase::Invoke(request, responseCallback);
+}
diff --git a/src/app/app-platform/ContentApp.cpp b/src/app/app-platform/ContentApp.cpp
index 3bc16e9..9a6b4ad 100644
--- a/src/app/app-platform/ContentApp.cpp
+++ b/src/app/app-platform/ContentApp.cpp
@@ -44,6 +44,8 @@
#define ZCL_DESCRIPTOR_CLUSTER_REVISION (1u)
#define ZCL_APPLICATION_BASIC_CLUSTER_REVISION (1u)
+inline constexpr EndpointId kCastingVideoPlayerEndpointId = 1;
+
Status ContentApp::HandleReadAttribute(ClusterId clusterId, AttributeId attributeId, uint8_t * buffer, uint16_t maxReadLength)
{
ChipLogProgress(DeviceLayer,
@@ -62,6 +64,113 @@
return Status::Failure;
}
+void ContentApp::AddClientNode(NodeId subjectNodeId)
+{
+ mClientNodes[mNextClientNodeIndex++] = subjectNodeId;
+ if (mClientNodeCount < kMaxClientNodes)
+ {
+ mClientNodeCount++;
+ }
+ if (mNextClientNodeIndex >= kMaxClientNodes)
+ {
+ // if we exceed the max number, then overwrite the oldest entry
+ mNextClientNodeIndex = 0;
+ }
+}
+
+void ContentApp::SendAppObserverCommand(chip::Controller::DeviceCommissioner * commissioner, NodeId clientNodeId, char * data,
+ char * encodingHint)
+{
+ ChipLogProgress(Controller, "Attempting to send AppObserver command");
+ if (mContentAppClientCommandSender.IsBusy())
+ {
+ ChipLogProgress(Controller, "SendAppObserverCommand busy");
+ return;
+ }
+
+ mContentAppClientCommandSender.SendContentAppMessage(commissioner, clientNodeId, kCastingVideoPlayerEndpointId, data,
+ encodingHint);
+
+ ChipLogProgress(Controller, "Completed send of AppObserver command");
+}
+
+CHIP_ERROR ContentAppClientCommandSender::SendContentAppMessage(chip::Controller::DeviceCommissioner * commissioner,
+ chip::NodeId destinationId, chip::EndpointId endPointId,
+ char * data, char * encodingHint)
+{
+ if (mIsBusy)
+ {
+ return CHIP_ERROR_INCORRECT_STATE;
+ }
+ mIsBusy = true;
+ mDestinationId = destinationId;
+ mEndPointId = endPointId;
+ mData = std::string(data);
+ mEncodingHint = std::string(encodingHint);
+
+ ChipLogProgress(Controller, "Sending command to node 0x" ChipLogFormatX64, ChipLogValueX64(mDestinationId));
+
+ return commissioner->GetConnectedDevice(mDestinationId, &mOnDeviceConnectedCallback, &mOnDeviceConnectionFailureCallback);
+}
+
+void ContentAppClientCommandSender::OnDeviceConnectedFn(void * context, chip::Messaging::ExchangeManager & exchangeMgr,
+ const chip::SessionHandle & sessionHandle)
+{
+ ChipLogProgress(Controller, "ContentAppClientCommandSender::OnDeviceConnectedFn");
+ ContentAppClientCommandSender * sender = reinterpret_cast<ContentAppClientCommandSender *>(context);
+ VerifyOrReturn(sender != nullptr, ChipLogError(chipTool, "OnDeviceConnectedFn: context is null"));
+
+ sender->SendMessage(exchangeMgr, sessionHandle);
+}
+
+CHIP_ERROR ContentAppClientCommandSender::SendMessage(chip::Messaging::ExchangeManager & exchangeMgr,
+ const chip::SessionHandle & sessionHandle)
+{
+ ChipLogProgress(Controller, "ContentAppClientCommandSender::SendMessage");
+
+ chip::Controller::ClusterBase cluster(exchangeMgr, sessionHandle, mEndPointId);
+
+ chip::app::Clusters::ContentAppObserver::Commands::ContentAppMessage::Type request;
+ request.data = Optional<CharSpan>(CharSpan::fromCharString(mData.c_str()));
+ request.encodingHint = CharSpan::fromCharString(mEncodingHint.c_str());
+ CHIP_ERROR err = cluster.InvokeCommand(request, nullptr, OnCommandResponse, OnCommandFailure);
+ if (err != CHIP_NO_ERROR)
+ {
+ ChipLogDetail(Controller, "ContentAppClientCommandSender SendMessage error err %s", ErrorStr(err));
+ }
+
+ mIsBusy = false;
+ ChipLogProgress(Controller, "ContentAppClientCommandSender: Completed send of AppObserver command");
+
+ return CHIP_NO_ERROR;
+}
+
+void ContentAppClientCommandSender::OnDeviceConnectionFailureFn(void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR err)
+{
+ ChipLogProgress(Controller, "ContentAppClientCommandSender::OnDeviceConnectedFn error err %s", ErrorStr(err));
+
+ ContentAppClientCommandSender * sender = reinterpret_cast<ContentAppClientCommandSender *>(context);
+ VerifyOrReturn(sender != nullptr, ChipLogError(chipTool, "OnDeviceConnectionFailureFn: context is null"));
+
+ sender->Cleanup();
+}
+
+void ContentAppClientCommandSender::Cleanup()
+{
+ ChipLogProgress(Controller, "ContentAppClientCommandSender::Cleanup");
+ mIsBusy = false;
+}
+
+void ContentAppClientCommandSender::OnCommandResponse(void * context, const ContentAppMessageResponseDecodableType & response)
+{
+ ChipLogProgress(Controller, "ContentAppClientCommandSender::OnCommandResponse");
+}
+
+void ContentAppClientCommandSender::OnCommandFailure(void * context, CHIP_ERROR error)
+{
+ ChipLogProgress(Controller, "ContentAppClientCommandSender::OnCommandFailure error err %s", ErrorStr(error));
+}
+
} // namespace AppPlatform
} // namespace chip
diff --git a/src/app/app-platform/ContentApp.h b/src/app/app-platform/ContentApp.h
index d3b7d1f..34b1dd5 100644
--- a/src/app/app-platform/ContentApp.h
+++ b/src/app/app-platform/ContentApp.h
@@ -33,6 +33,7 @@
#include <app/clusters/media-playback-server/media-playback-delegate.h>
#include <app/clusters/target-navigator-server/target-navigator-delegate.h>
#include <app/util/attribute-storage.h>
+#include <controller/CHIPDeviceController.h>
#include <protocols/interaction_model/StatusCode.h>
namespace chip {
@@ -48,6 +49,46 @@
using MediaPlaybackDelegate = app::Clusters::MediaPlayback::Delegate;
using TargetNavigatorDelegate = app::Clusters::TargetNavigator::Delegate;
+inline constexpr uint8_t kMaxClientNodes = 8;
+
+class ContentAppClientCommandSender
+{
+public:
+ ContentAppClientCommandSender() :
+ mOnDeviceConnectedCallback(OnDeviceConnectedFn, this), mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this)
+ {}
+
+ bool IsBusy() const { return mIsBusy; }
+ CHIP_ERROR SendContentAppMessage(chip::Controller::DeviceCommissioner * commissioner, chip::NodeId destinationId,
+ chip::EndpointId endPointId, char * data, char * encodingHint);
+
+protected:
+ CHIP_ERROR SendMessage(chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle);
+
+ void Cleanup();
+
+private:
+ static void OnDeviceConnectedFn(void * context, chip::Messaging::ExchangeManager & exchangeMgr,
+ const chip::SessionHandle & sessionHandle);
+ static void OnDeviceConnectionFailureFn(void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error);
+
+ using ContentAppMessageResponseDecodableType =
+ chip::app::Clusters::ContentAppObserver::Commands::ContentAppMessageResponse::DecodableType;
+
+ static void OnCommandResponse(void * context, const ContentAppMessageResponseDecodableType & response);
+ static void OnCommandFailure(void * context, CHIP_ERROR error);
+
+ chip::Callback::Callback<chip::OnDeviceConnected> mOnDeviceConnectedCallback;
+ chip::Callback::Callback<chip::OnDeviceConnectionFailure> mOnDeviceConnectionFailureCallback;
+
+ bool mIsBusy = false;
+ chip::NodeId mDestinationId = 0;
+ chip::EndpointId mEndPointId = 0;
+
+ std::string mData;
+ std::string mEncodingHint;
+};
+
class DLL_EXPORT ContentApp
{
public:
@@ -70,8 +111,21 @@
uint16_t maxReadLength);
Protocols::InteractionModel::Status HandleWriteAttribute(ClusterId clusterId, AttributeId attributeId, uint8_t * buffer);
+ void AddClientNode(NodeId clientNodeId);
+ uint8_t GetClientNodeCount() const { return mClientNodeCount; }
+ NodeId GetClientNode(uint8_t index) const { return mClientNodes[index]; }
+
+ void SendAppObserverCommand(chip::Controller::DeviceCommissioner * commissioner, NodeId clientNodeId, char * data,
+ char * encodingHint);
+
protected:
EndpointId mEndpointId = 0;
+
+ uint8_t mClientNodeCount = 0;
+ uint8_t mNextClientNodeIndex = 0;
+ NodeId mClientNodes[kMaxClientNodes];
+
+ ContentAppClientCommandSender mContentAppClientCommandSender;
};
} // namespace AppPlatform
diff --git a/src/app/app-platform/ContentAppPlatform.cpp b/src/app/app-platform/ContentAppPlatform.cpp
index 09d6c55..3d17a52 100644
--- a/src/app/app-platform/ContentAppPlatform.cpp
+++ b/src/app/app-platform/ContentAppPlatform.cpp
@@ -716,6 +716,7 @@
continue;
}
+ bool accessAllowed = false;
for (const auto & allowedVendor : app->GetApplicationBasicDelegate()->GetAllowedVendorList())
{
if (allowedVendor == targetVendorId)
@@ -732,6 +733,12 @@
.fabricIndex = kUndefinedFabricIndex,
});
}
+ accessAllowed = true;
+ }
+ if (accessAllowed)
+ {
+ // notify content app about this nodeId
+ app->AddClientNode(subjectNodeId);
}
}
}
diff --git a/src/controller/CommissionerDiscoveryController.cpp b/src/controller/CommissionerDiscoveryController.cpp
index 86849a3..ee94bc8 100644
--- a/src/controller/CommissionerDiscoveryController.cpp
+++ b/src/controller/CommissionerDiscoveryController.cpp
@@ -110,8 +110,25 @@
ChipLogDetail(Controller, "------Via Shell Enter: controller ux ok|cancel");
}
+/// Callback for getting execution into the main chip thread
+void CallbackOk(System::Layer * aSystemLayer, void * aAppState)
+{
+ ChipLogDetail(AppServer, "UX Ok: now on main thread");
+ CommissionerDiscoveryController * cdc = static_cast<CommissionerDiscoveryController *>(aAppState);
+ cdc->InternalOk();
+}
+
void CommissionerDiscoveryController::Ok()
{
+ ChipLogDetail(AppServer, "UX Ok: moving to main thread");
+ // need to ensure callback is on main chip thread
+ assertChipStackLockedByCurrentThread();
+ DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(0), CallbackOk, this);
+}
+
+void CommissionerDiscoveryController::InternalOk()
+{
+ ChipLogDetail(AppServer, "UX InternalOk");
if (!mPendingConsent)
{
ChipLogError(AppServer, "UX Ok: no current instance");
diff --git a/src/controller/CommissionerDiscoveryController.h b/src/controller/CommissionerDiscoveryController.h
index 42a0bb6..f0dcf6d 100644
--- a/src/controller/CommissionerDiscoveryController.h
+++ b/src/controller/CommissionerDiscoveryController.h
@@ -273,6 +273,7 @@
* indicated in the UserPrompter's PromptForCommissionOKPermission callback
*/
void Ok();
+ void InternalOk();
/**
* This method should be called after the user has declined to give consent for commissioning of the client