Skip to content

Commit

Permalink
Add cluster commands to tv-casting-app (project-chip#17830)
Browse files Browse the repository at this point in the history
* Add cluster commands to tv-casting-app

* Support looking up video player node id from bindings when nodeId=0 in command

* refactor shell commands into tv-casting-common

* refactor cleanup

* add shell commands for print/delete fabrics

* shell help

* lint and restyled

* lint and readme

* restyled

* spelling
  • Loading branch information
chrisdecenzo authored May 2, 2022
1 parent 6c7d2e7 commit 3c5b7ab
Show file tree
Hide file tree
Showing 29 changed files with 4,228 additions and 115 deletions.
1 change: 1 addition & 0 deletions .github/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,7 @@ xFF
xFFF
xFFFF
xfffff
xFFFFFFEFFFFFFFFF
xtensa
xwayland
xyz
Expand Down
1 change: 1 addition & 0 deletions examples/tv-casting-app/linux/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ executable("chip-tv-casting-app") {
"${chip_root}/src/credentials:default_attestation_verifier",
"${chip_root}/src/lib",
"${chip_root}/third_party/inipp",
"${chip_root}/third_party/jsoncpp",
]

include_dirs =
Expand Down
46 changes: 45 additions & 1 deletion examples/tv-casting-app/linux/CastingShellCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@ namespace Shell {
static CHIP_ERROR PrintAllCommands()
{
streamer_t * sout = streamer_get();
streamer_printf(sout, " help Usage: app <subcommand>\r\n");
streamer_printf(sout, " help Usage: cast <subcommand>\r\n");
streamer_printf(sout, " print-bindings Usage: cast print-bindings\r\n");
streamer_printf(sout, " print-fabrics Usage: cast print-fabrics\r\n");
streamer_printf(
sout,
" delete-fabric <index> Delete a fabric from the casting client's fabric store. Usage: cast delete-fabric 1\r\n");
streamer_printf(
sout,
" set-fabric <index> Set current fabric from the casting client's fabric store. Usage: cast set-fabric 1\r\n");
streamer_printf(sout,
" init <nodeid> <fabric-index> Initialize casting app using given nodeid and index from previous "
"commissioning. Usage: init 18446744004990074879 2\r\n");
Expand All @@ -50,6 +58,9 @@ static CHIP_ERROR PrintAllCommands()
sout,
" access <node> Read and display clusters on each endpoint for <node>. Usage: cast access 0xFFFFFFEFFFFFFFFF\r\n");
streamer_printf(sout, " sendudc <address> <port> Send UDC message to address. Usage: cast sendudc ::1 5543\r\n");
streamer_printf(
sout,
" cluster [clustercommand] Send cluster command. Usage: cast cluster keypadinput send-key 1 18446744004990074879 1\r\n");
streamer_printf(sout, "\r\n");

return CHIP_NO_ERROR;
Expand Down Expand Up @@ -122,6 +133,39 @@ static CHIP_ERROR CastingHandler(int argc, char ** argv)
chip::Transport::PeerAddress::UDP(commissioner, port));
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
if (strcmp(argv[0], "print-bindings") == 0)
{
CastingServer::GetInstance()->PrintBindings();
return CHIP_NO_ERROR;
}
if (strcmp(argv[0], "print-fabrics") == 0)
{
PrintFabrics();
return CHIP_NO_ERROR;
}
if (strcmp(argv[0], "delete-fabric") == 0)
{
char * eptr;
chip::FabricIndex fabricIndex = (chip::FabricIndex) strtol(argv[1], &eptr, 10);
chip::Server::GetInstance().GetFabricTable().Delete(fabricIndex);
return CHIP_NO_ERROR;
}
if (strcmp(argv[0], "set-fabric") == 0)
{
char * eptr;
chip::FabricIndex fabricIndex = (chip::FabricIndex) strtol(argv[1], &eptr, 10);
chip::NodeId nodeId = CastingServer::GetInstance()->GetVideoPlayerNodeForFabricIndex(fabricIndex);
if (nodeId == kUndefinedFabricIndex)
{
streamer_printf(streamer_get(), "ERROR - invalid fabric or video player nodeId not found\r\n");
return CHIP_ERROR_INVALID_ARGUMENT;
}
return CastingServer::GetInstance()->TargetVideoPlayerInfoInit(nodeId, fabricIndex);
}
if (strcmp(argv[0], "cluster") == 0)
{
return ProcessClusterCommand(argc, argv);
}
return CHIP_ERROR_INVALID_ARGUMENT;
}

Expand Down
19 changes: 19 additions & 0 deletions examples/tv-casting-app/linux/CastingUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,22 @@ void HandleUDCSendExpiration(System::Layer * aSystemLayer, void * context)
selectedCommissioner->ipAddress[0], selectedCommissioner->port, selectedCommissioner->interfaceId)));
}
#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT

void PrintFabrics()
{
// set fabric to be the first in the list
for (const auto & fb : chip::Server::GetInstance().GetFabricTable())
{
FabricIndex fabricIndex = fb.GetFabricIndex();
ChipLogError(AppServer, "Next Fabric index=%d", fabricIndex);
if (!fb.IsInitialized())
{
ChipLogError(AppServer, " -- Not initialized");
continue;
}
NodeId myNodeId = fb.GetNodeId();
ChipLogProgress(NotSpecified,
"---- Current Fabric nodeId=0x" ChipLogFormatX64 " fabricId=0x" ChipLogFormatX64 " fabricIndex=%d",
ChipLogValueX64(myNodeId), ChipLogValueX64(fb.GetFabricId()), fabricIndex);
}
}
4 changes: 4 additions & 0 deletions examples/tv-casting-app/linux/CastingUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

constexpr uint32_t kCommissionerDiscoveryTimeoutInMs = 5 * 1000;

CHIP_ERROR ProcessClusterCommand(int argc, char ** argv);

CHIP_ERROR DiscoverCommissioners();

CHIP_ERROR RequestCommissioning(int index);
Expand All @@ -39,3 +41,5 @@ void InitCommissioningFlow(intptr_t commandArg);
#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT
void HandleUDCSendExpiration(System::Layer * aSystemLayer, void * context);
#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT

void PrintFabrics();
105 changes: 102 additions & 3 deletions examples/tv-casting-app/linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ This is a CHIP TV Casting app that can be used to cast content to a TV. This app
discovers TVs on the local network that act as commissioners, lets the user
select one, sends the TV a User Directed Commissioning request, enters
commissioning mode, advertises itself as a Commissionable Node and gets
commissioned. Then it allows the user to send CHIP ContentLauncher commands to
the TV.
commissioned. Then it allows the user to send CHIP commands to the TV.

<hr>

Expand Down Expand Up @@ -45,9 +44,109 @@ the TV.
$ cd ~/connectedhomeip/examples/tv-app/linux
$ out/debug/chip-tv-app

- Run the tv-casting-app
- Run the tv-casting-app (clean start)

$ cd ~/connectedhomeip/examples/tv-casting-app/linux
(delete any stored fabrics from previous runs)
$ rm -rf /tmp/rm -rf /tmp/chip_casting_kvs*
$ out/debug/chip-tv-casting-app

Follow the on-screen prompts on the tv-casting-app console

- Re-run the tv-casting-app (cached fabrics)

$ cd ~/connectedhomeip/examples/tv-casting-app/linux
$ out/debug/chip-tv-casting-app

Follow the on-screen prompts on the tv-casting-app console. NOTE: When there
are cached fabrics, re-commissioning onto the same fabric will fail. See
below for details.

### Commissioning the tv-casting-app

The tv-casting-app will automatically discover video players and print these out
upon startup. The user-directed-commissioning (UDC) process can be initiated
using the shell by specifying the index of the discovered video player in the
printed list.

- Initiate UDC for the discovered video player with index 0

> cast request 0

- Re-run commissioner discovery

> cast discover

### Re-Running the Example on Linux with Cached Fabrics

After successfully commissioning the tv-casting-app onto a fabric the app can be
re-run using a cached fabric so that the commissioning step does not need to be
repeated. This allows the app to be commissioned once and then re-run upon
demand. Shell commands allow viewing, selecting and deleting a cached fabric. If
no fabric is specified for a command that required a fabric, then the first
valid cached fabric will be used.

- Print all shell commands

> help
> cast help

- Print all fabrics

> cast print-fabrics

- Delete fabric with index 1

> cast delete-fabric 1

- Switch to fabric index

> cast set-fabric 1

### Sending Arbitrary Cluster commands

The linux tv-casting-app can invoke any cluster command using the same syntax as
chip-tool. This can be done from the command line and from the shell. The
default (or first valid) cached fabric will be used unless a different fabric
has been set already using shell commands.

Similar to chip-tool arguments, the target nodeId for the command is specified
along with the endpoint. For the casting app, the nodeId would be the nodeId for
the video player (TV). For convenience, when nodeId '0' is used, the
tv-casting-app will use the cached nodeId for the video player corresponding to
the current fabric.

The tv-casting-app is able to determine the nodeId for the given fabric by
checking its binding table since the video player sets bindings on the
tv-casting-app for each endpoint to which the tv-casting-app is granted access
during commissioning.

- Run the tv-casting-app and invoke a cluster command using default fabric,
target video player nodeId 18446744004990074879

$ out/debug/chip-tv-casting-app onoff read on-off 18446744004990074879 2

- Run the tv-casting-app and invoke a cluster command using default fabric,
target video player nodeId 0xFFFFFFEFFFFFFFFF (hex)

$ out/debug/chip-tv-casting-app onoff read on-off 0xFFFFFFEFFFFFFFFF 2

- Run the tv-casting-app and invoke a cluster command using default fabric,
video player nodeId for that fabric

$ out/debug/chip-tv-casting-app onoff read on-off 0 2

- Run the tv-casting-app and invoke a different cluster command using default
fabric, video player nodeId for that fabric

$ out/debug/chip-tv-casting-app keypadinput send-key 1 0 1

- Run the tv-casting-app and invoke a cluster command from the shell using
default fabric, video player nodeId for that fabric

> cast cluster keypadinput send-key 1 0 1

- Run the tv-casting-app and invoke a cluster command from the shell using
default fabric, target video player nodeId 0xFFFFFFEFFFFFFFFF

> cast cluster keypadinput send-key 1 0xFFFFFFEFFFFFFFFF 1
117 changes: 27 additions & 90 deletions examples/tv-casting-app/linux/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
* limitations under the License.
*/

#include "commands/clusters/SubscriptionsCommands.h"
#include "commands/common/Commands.h"
#include "commands/example/ExampleCredentialIssuerCommands.h"
#include <zap-generated/cluster/Commands.h>

#include "CastingServer.h"
#include "CastingUtils.h"
#if defined(ENABLE_CHIP_SHELL)
Expand All @@ -41,89 +46,6 @@ using namespace chip::app::Clusters::ContentLauncher::Commands;
#if defined(ENABLE_CHIP_SHELL)
using chip::Shell::Engine;
#endif
struct TVExampleDeviceType
{
const char * name;
uint16_t id;
};

Dnssd::DiscoveryFilter gDiscoveryFilter = Dnssd::DiscoveryFilter();
constexpr TVExampleDeviceType kKnownDeviceTypes[] = { { "video-player", 35 }, { "dimmable-light", 257 } };
constexpr int kKnownDeviceTypesCount = sizeof kKnownDeviceTypes / sizeof *kKnownDeviceTypes;
constexpr uint16_t kOptionDeviceType = 't';

// TODO: Accept these values over CLI
const char * kContentUrl = "https://www.test.com/videoid";
const char * kContentDisplayStr = "Test video";

CommissionableNodeController gCommissionableNodeController;
chip::System::SocketWatchToken gToken;
bool gInited = false;

bool HandleOptions(const char * aProgram, OptionSet * aOptions, int aIdentifier, const char * aName, const char * aValue)
{
switch (aIdentifier)
{
case kOptionDeviceType: {
char * endPtr;
long deviceType = strtol(aValue, &endPtr, 10);
if (*endPtr == '\0' && deviceType > 0 && CanCastTo<uint16_t>(deviceType))
{
gDiscoveryFilter = Dnssd::DiscoveryFilter(Dnssd::DiscoveryFilterType::kDeviceType, static_cast<uint16_t>(deviceType));
return true;
}

for (int i = 0; i < kKnownDeviceTypesCount; i++)
{
if (strcasecmp(aValue, kKnownDeviceTypes[i].name) == 0)
{
gDiscoveryFilter = Dnssd::DiscoveryFilter(Dnssd::DiscoveryFilterType::kDeviceType, kKnownDeviceTypes[i].id);
return true;
}
}

ChipLogError(AppServer, "%s: INTERNAL ERROR: Unhandled option value: %s %s", aProgram, aName, aValue);
return false;
}
default:
ChipLogError(AppServer, "%s: INTERNAL ERROR: Unhandled option: %s", aProgram, aName);
return false;
}
}

OptionDef cmdLineOptionsDef[] = {
{ "device-type", chip::ArgParser::kArgumentRequired, kOptionDeviceType },
{},
};

OptionSet cmdLineOptions = { HandleOptions, cmdLineOptionsDef, "PROGRAM OPTIONS",
" -t <commissioner device type>\n"
" --device-type <commissioner device type>\n"
" Device type of the commissioner to discover and request commissioning from. Specify value as "
"a decimal integer or a known text representation. Defaults to all device types\n" };

HelpOptions helpOptions("tv-casting-app", "Usage: tv-casting-app [options]", "1.0");

OptionSet * allOptions[] = { &cmdLineOptions, &helpOptions, nullptr };

void DeviceEventCallback(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
{
if (event->Type == DeviceLayer::DeviceEventType::kBindingsChangedViaCluster)
{
if (CastingServer::GetInstance()->GetTargetVideoPlayerInfo()->IsInitialized())
{
CastingServer::GetInstance()->ReadServerClustersForNode(
CastingServer::GetInstance()->GetTargetVideoPlayerInfo()->GetNodeId());
}
}
else if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete)
{
ReturnOnFailure(CastingServer::GetInstance()->GetTargetVideoPlayerInfo()->Initialize(
event->CommissioningComplete.PeerNodeId, event->CommissioningComplete.PeerFabricIndex));

CastingServer::GetInstance()->ContentLauncherLaunchURL(kContentUrl, kContentDisplayStr);
}
}

CHIP_ERROR InitCommissionableDataProvider(LinuxCommissionableDataProvider & provider, LinuxDeviceOptions & options)
{
Expand Down Expand Up @@ -169,6 +91,20 @@ CHIP_ERROR InitCommissionableDataProvider(LinuxCommissionableDataProvider & prov
// To hold SPAKE2+ verifier, discriminator, passcode
LinuxCommissionableDataProvider gCommissionableDataProvider;

// For shell and command line processing of commands
ExampleCredentialIssuerCommands gCredIssuerCommands;
Commands gCommands;

CHIP_ERROR ProcessClusterCommand(int argc, char ** argv)
{
if (!CastingServer::GetInstance()->GetTargetVideoPlayerInfo()->IsInitialized())
{
CastingServer::GetInstance()->SetDefaultFabricIndex();
}
gCommands.Run(argc, argv);
return CHIP_NO_ERROR;
}

int main(int argc, char * argv[])
{
VerifyOrDie(CHIP_NO_ERROR == chip::Platform::MemoryInit());
Expand Down Expand Up @@ -197,11 +133,6 @@ int main(int argc, char * argv[])
SetDeviceAttestationVerifier(GetDefaultDACVerifier(testingRootStore));
}

if (!chip::ArgParser::ParseArgs(argv[0], argc, argv, allOptions))
{
return 1;
}

// Send discover commissioners request
SuccessOrExit(err = CastingServer::GetInstance()->DiscoverCommissioners());

Expand All @@ -210,8 +141,14 @@ int main(int argc, char * argv[])
chip::System::Clock::Milliseconds32(kCommissionerDiscoveryTimeoutInMs),
[](System::Layer *, void *) { chip::DeviceLayer::PlatformMgr().ScheduleWork(InitCommissioningFlow); }, nullptr);

// Add callback to send Content casting commands after commissioning completes
chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(DeviceEventCallback, 0);
registerClusters(gCommands, &gCredIssuerCommands);
registerClusterSubscriptions(gCommands, &gCredIssuerCommands);

if (argc > 1)
{
// if there are command-line arguments, then automatically start server
ProcessClusterCommand(argc, argv);
}

DeviceLayer::PlatformMgr().RunEventLoop();
exit:
Expand Down
Loading

0 comments on commit 3c5b7ab

Please sign in to comment.