Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,7 @@ subtypes
sudo
SulfateConcentrationMeasurement
SulfurDioxideConcentrationMeasurement
superset
suppressResponse
svg
SVR
Expand Down
1 change: 1 addition & 0 deletions docs/examples/tv.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
:glob:
:maxdepth: 1

tv-app/README
tv-app/**/README
```
115 changes: 115 additions & 0 deletions examples/tv-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Matter TV Example (Media Player)

The `tv-app` is a reference Media Player device. By default it runs as a
**Casting Video Player** (device type `0x0023`), but it can also be exercised as
any of the other three media player device types defined by the Matter Device
Library and the Media Player Architecture.

For build, commissioning, casting, and App Platform instructions see
[linux/README.md](linux/README.md).

## Media player device types

There are four media player device types. They share the same "media playback"
feature set and differ by whether they also do content launching and whether
they act as a **commissioner** (the "casting" role) versus a plain
**commissionable node** (the "basic"/"streaming" role):

| Device type | ID | Rev | Role | Minimum feature set |
| ---------------------- | -------- | --- | -------------- | ------------------------------------------------------------------ |
| Basic Video Player | `0x0028` | 2 | Commissionable | Media playback + keypad (On/Off, Media Playback, Keypad Input) |
| Casting Video Player | `0x0023` | 2 | Commissioner | Basic Video Player + content launch (Content Launcher) |
| Streaming Audio Player | `0x0020` | 1 | Commissionable | Media playback + content launch (Media Playback, Content Launcher) |
| Casting Audio Player | `0x0021` | 1 | Commissioner | Streaming Audio Player + commissioning |

The authoritative cluster requirements are in the spec Device Library
(`device_types/{BasicVideoPlayer,CastingVideoPlayer,StreamingAudioPlayer,CastingAudioPlayer}`)
and the Media Player Architecture chapter. Endpoint 1 of `tv-app.zap` already
hosts a **superset** of clusters (On/Off, Media Playback, Keypad Input, Channel,
Media Input, Low Power, Target Navigator, Audio Output, Content Launcher,
Application Launcher, Content Control, Media File Management, ...), so it
satisfies the mandatory cluster set of all four types.

## Selecting the device type

The device type is expressed in **two independent places**:

1. The **declared** device type — endpoint 1's `deviceType` in
[tv-common/tv-app.matter](tv-common/tv-app.matter) / `tv-app.zap`, surfaced
at runtime through the Descriptor cluster `DeviceTypeList`. This is what a
controller reads to learn what the device is, and what certification checks.
2. The **advertised** device type — the compile-time
`CHIP_DEVICE_CONFIG_DEVICE_TYPE` in
[tv-common/include/CHIPProjectAppConfig.h](tv-common/include/CHIPProjectAppConfig.h),
which feeds only the DNS-SD `_T<id>` commissioning subtype used for discovery
filtering.

### Runtime: `--device-type` (Linux)

The Linux `tv-app` accepts a flag that presents endpoint 1 as a different media
device type at boot, without a rebuild:

```sh
./out/debug/chip-tv-app --device-type basic-video
./out/debug/chip-tv-app --device-type casting-audio
./out/debug/chip-tv-app --device-type streaming-audio
./out/debug/chip-tv-app --device-type casting-video # the default
```

Accepted values: `casting-video` (default), `basic-video`, `casting-audio`,
`streaming-audio`.

**What it changes:**

- The **Descriptor cluster `DeviceTypeList`** on endpoint 1 (via
`emberAfSetDeviceTypeList`, applied in `ApplicationInit`). This is what a
commissioner reads to learn the device type.
- The **DNS-SD `_T<id>` advertising subtype** (via
`ConfigurationMgr().SetDeviceTypeId`, applied during argument parsing so it
is in place before the server starts advertising). Commissioners that filter
discovery by device-type subtype see the selected type. The override is not
persisted across reboots.

Together these are enough to have the device _advertise as, declare itself as,_
and be tested as any of the four types, because endpoint 1 already exposes the
superset of clusters described above.

**What it does NOT change (by design):**

- The **commissioner vs. commissionable role**. The Linux `tv-app` is built as
a combined server + commissioner (it runs the UDC/CommissionerDiscovery
machinery and the `controller`/`app` shell commands). Selecting
`basic-video` or `streaming-audio` declares the commissionable-only type but
does not disable the commissioner stack; the app still behaves as a
commissioner.
- The **cluster set**. No clusters are added or removed; the endpoint keeps
its superset.

So the runtime flag is intended for exercising controllers, discovery, and the
advertised/declared device type against each type — not for producing a
byte-faithful build of a shipping product of that type (the cluster set and
commissioner role are still those of the compiled Casting Video Player).

### Build time: a faithful variant

For a variant that is also faithful in cluster set and role, change the data
model (and, optionally, the compile-time default device type) and rebuild:

1. Edit endpoint 1's device type and trim the endpoint's clusters to the target
type's requirements in `tv-common/tv-app.zap` (open it with ZAP), then
regenerate `tv-app.matter` and the generated code. Note that the two audio
player types (`0x0020`, `0x0021`) are new spec additions and are not yet
present in `src/app/zap-templates/zcl/data-model/chip/matter-devices.xml`, so
ZAP's device-type dropdown will need those device types added there first.
2. For a commissionable-only type (Basic Video Player, Streaming Audio Player),
the commissioner-specific behavior would additionally need to be gated off
(e.g. `CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE`).
3. Optionally set `CHIP_DEVICE_CONFIG_DEVICE_TYPE` in
[tv-common/include/CHIPProjectAppConfig.h](tv-common/include/CHIPProjectAppConfig.h)
to the target device type ID, so the build advertises that type by default
without needing the `--device-type` flag.

A cleaner long-term approach would be a build (`gn`) argument that selects among
per-type ZAP/data-model files and the matching compile constant, mirroring how
other examples ship multiple variants. That is not implemented today; the
runtime flag above is the supported way to switch the advertised/declared type.
4 changes: 4 additions & 0 deletions examples/tv-app/linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ to build and run Matter TV Example on Raspberry Pi. This doc is tested on
**Ubuntu for Raspberry Pi Server 20.04 LTS (aarch64)** and **Ubuntu for
Raspberry Pi Desktop 20.10 (aarch64)**

> To run the tv-app as a different media player device type (Basic Video Player,
> Casting/Streaming Audio Player) via the `--device-type` flag, see
> [Selecting the device type](../README.md#selecting-the-device-type).

<hr>

- [Matter TV Example](#matter-tv-example)
Expand Down
123 changes: 122 additions & 1 deletion examples/tv-app/linux/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,18 @@
#include <app/app-platform/ContentAppPlatform.h>
#include <app/clusters/media-file-management-server/CodegenIntegration.h>
#include <app/server/Server.h>
#include <app/util/attribute-storage.h>
#include <app/util/endpoint-config-api.h>
#include <devices/Types.h>
#include <lib/support/CHIPArgParser.hpp>
#include <platform/ConfigurationManager.h>
#include <protocols/Protocols.h>

#if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE
#include <controller/CHIPDeviceController.h> // nogncheck
#endif // CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE

#include <cstring>
#include <optional>

#if defined(ENABLE_CHIP_SHELL)
Expand Down Expand Up @@ -79,6 +84,106 @@ std::optional<MediaFileManagement::MediaFileManagementBdxProvider> gMediaFileMan
std::optional<MediaFileManagement::MediaFileManagementBdxRequestor> gMediaFileManagementBdxRequestor;
std::optional<MediaFileManagement::MediaFileManagementBdxCoordinator> gMediaFileManagementBdxCoordinator;

// ---------------------------------------------------------------------------
// --device-type flag: choose which media device type endpoint 1 presents as.
//
// tv-app is built as a Casting Video Player (0x0023) in tv-app.zap, but
// endpoint 1 hosts a superset of clusters that also satisfies the mandatory set
// of the other three media player device types. This flag makes the app present
// as any of the four without a rebuild by, at boot:
// - overriding the device type id used for DNS-SD "_T<id>" commissioning
// advertising (ConfigurationMgr().SetDeviceTypeId, applied during argument
// parsing so it is in place before the server starts advertising), and
// - rewriting endpoint 1's Descriptor DeviceTypeList (emberAfSetDeviceTypeList,
// applied in ApplicationInit once the endpoint table is populated).
// See examples/tv-app/README.md.
//
// NOTE: this changes the advertised and declared device type only. The
// commissioner role (Casting players are Commissioners) and the cluster set are
// as compiled; the build-time variant documented in the README is the path to a
// fully faithful data model.
constexpr EndpointId kVideoPlayerEndpointId = 1;

struct MediaDeviceTypeOption
{
const char * name;
EmberAfDeviceType deviceType; // { deviceTypeId, deviceTypeRevision }
};

// EmberAfDeviceType is DataModel::DeviceTypeEntry, so the generated
// devices/Types.h entries can be used directly for the ids/revisions that exist
// there (they track the data model automatically). The two audio player types
// are recent spec additions that are not yet emitted into devices/Types.h (they
// are absent from matter-devices.xml), so they are spelled out (revision 1)
// until they are generated.
constexpr MediaDeviceTypeOption kMediaDeviceTypes[] = {
{ "casting-video", app::Device::Type::kCastingVideoPlayer }, // 0x0023 (tv-app.zap default)
{ "basic-video", app::Device::Type::kBasicVideoPlayer }, // 0x0028
{ "casting-audio", { 0x0021, 1 } }, // Casting Audio Player
{ "streaming-audio", { 0x0020, 1 } }, // Streaming Audio Player
};

// Backing storage for the runtime override; emberAfSetDeviceTypeList stores the
// span (not a copy), so this must outlive the endpoint - hence file scope.
EmberAfDeviceType gMediaDeviceTypeList[1];
bool gMediaDeviceTypeOverridden = false;

constexpr uint16_t kOptionDeviceType = 0xffe0;

bool TvAppOptionHandler(const char * program, chip::ArgParser::OptionSet * options, int identifier, const char * name,
const char * value)
{
if (identifier != kOptionDeviceType)
{
ChipLogError(DeviceLayer, "%s: INTERNAL ERROR: Unhandled option: %s", program, name);
return false;
}

for (const MediaDeviceTypeOption & option : kMediaDeviceTypes)
{
if (strcmp(value, option.name) == 0)
{
// Override the DNS-SD advertised device type now, before the server
// starts advertising. Only record the override (which also drives
// the Descriptor DeviceTypeList update in ApplicationInit) once this
// succeeds, so a failure does not leave us claiming an override that
// did not take effect.
CHIP_ERROR err = ConfigurationMgr().SetDeviceTypeId(option.deviceType.deviceTypeId);
if (err != CHIP_NO_ERROR)
{
ChipLogError(DeviceLayer, "%s: failed to override advertised device type: %" CHIP_ERROR_FORMAT, program,
err.Format());
return false;
}
gMediaDeviceTypeList[0] = option.deviceType;
gMediaDeviceTypeOverridden = true;
ChipLogProgress(DeviceLayer, "TV Linux App: endpoint 1 device type selected: %s (0x%04X revision %u)", option.name,
static_cast<unsigned>(option.deviceType.deviceTypeId), option.deviceType.deviceTypeRevision);
return true;
}
}

ChipLogError(DeviceLayer, "%s: unknown --device-type '%s' (expected casting-video|basic-video|casting-audio|streaming-audio)",
program, value);
return false;
}

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

chip::ArgParser::OptionSet sTvAppOptions = {
TvAppOptionHandler,
sTvAppOptionDefs,
"TV APP OPTIONS",
" --device-type <casting-video|basic-video|casting-audio|streaming-audio>\n"
" Present endpoint 1 as the given media device type: sets both the DNS-SD\n"
" _T advertising subtype and the Descriptor cluster DeviceTypeList.\n"
" Defaults to casting-video, as built into tv-app.zap. The commissioner\n"
" role and cluster set are unchanged. See examples/tv-app/README.md.\n",
};

} // namespace

void ApplicationInit()
Expand Down Expand Up @@ -168,6 +273,22 @@ void ApplicationInit()
constexpr uint16_t kApp5ProductId = 1;
factory->InstallContentApp(kApp5VendorId, kApp5ProductId);
#endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED

// Apply the --device-type override (if any) to endpoint 1's declared device
// type. Done here, after the server has populated the endpoint table.
if (gMediaDeviceTypeOverridden)
{
CHIP_ERROR err2 = emberAfSetDeviceTypeList(kVideoPlayerEndpointId, Span<const EmberAfDeviceType>(gMediaDeviceTypeList, 1));
if (err2 != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "TV Linux App: failed to set endpoint 1 device type: %" CHIP_ERROR_FORMAT, err2.Format());
}
else
{
ChipLogProgress(Zcl, "TV Linux App: endpoint 1 now declares device type 0x%04X (DNS-SD _T subtype set to match).",
static_cast<unsigned>(gMediaDeviceTypeList[0].deviceTypeId));
}
}
}

void ApplicationShutdown()
Expand Down Expand Up @@ -203,7 +324,7 @@ void ApplicationShutdown()
int main(int argc, char * argv[])
{

VerifyOrDie(ChipLinuxAppInit(argc, argv) == 0);
VerifyOrDie(ChipLinuxAppInit(argc, argv, &sTvAppOptions) == 0);

TEMPORARY_RETURN_IGNORED AppTvInit();

Expand Down
9 changes: 7 additions & 2 deletions src/include/platform/ConfigurationManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,13 @@ class ConfigurationManager

virtual void LogDeviceConfig() = 0;

virtual bool IsCommissionableDeviceTypeEnabled() = 0;
virtual CHIP_ERROR GetDeviceTypeId(uint32_t & deviceType) = 0;
virtual bool IsCommissionableDeviceTypeEnabled() = 0;
virtual CHIP_ERROR GetDeviceTypeId(uint32_t & deviceType) = 0;
// Override the device type id returned by GetDeviceTypeId() (and therefore
// advertised in the DNS-SD "_T<id>" commissioning subtype) for this session.
// The override is not persisted across reboots. Not all platforms implement
// this; the default returns CHIP_ERROR_NOT_IMPLEMENTED.
virtual CHIP_ERROR SetDeviceTypeId(uint32_t deviceType) { return CHIP_ERROR_NOT_IMPLEMENTED; }
virtual bool IsCommissionableDeviceNameEnabled() = 0;
virtual CHIP_ERROR GetCommissionableDeviceName(char * buf, size_t bufSize) = 0;
virtual CHIP_ERROR GetInitialPairingHint(uint16_t & pairingHint) = 0;
Expand Down
17 changes: 17 additions & 0 deletions src/include/platform/internal/GenericConfigurationManagerImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include <platform/CHIPDeviceConfig.h>
#include <platform/ConfigurationManager.h>

#include <optional>

#if CHIP_ENABLE_ROTATING_DEVICE_ID && defined(CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID)
#include <lib/support/LifetimePersistedCounter.h>
#endif
Expand Down Expand Up @@ -86,6 +88,7 @@ class GenericConfigurationManagerImpl : public ConfigurationManager
CHIP_ERROR GetBLEDeviceIdentificationInfo(Ble::ChipBLEDeviceIdentificationInfo & deviceIdInfo) override;
bool IsCommissionableDeviceTypeEnabled() override;
CHIP_ERROR GetDeviceTypeId(uint32_t & deviceType) override;
CHIP_ERROR SetDeviceTypeId(uint32_t deviceType) override;
bool IsCommissionableDeviceNameEnabled() override;
CHIP_ERROR GetCommissionableDeviceName(char * buf, size_t bufSize) override;
CHIP_ERROR GetInitialPairingHint(uint16_t & pairingHint) override;
Expand Down Expand Up @@ -127,6 +130,20 @@ class GenericConfigurationManagerImpl : public ConfigurationManager
size_t mRotatingDeviceIdUniqueIdLength = kRotatingDeviceIDUniqueIDLength;
#endif

// Returns the runtime device-type override set via SetDeviceTypeId(), or
// std::nullopt if none is in effect. Platforms that provide their own
// GetDeviceTypeId() (reading persisted config) should honor this first so
// the runtime override behaves consistently across platforms.
static std::optional<uint32_t> GetDeviceTypeIdOverride();

// Backing storage for the runtime device-type override (see SetDeviceTypeId
// / GetDeviceTypeIdOverride). Not persisted across reboots. This is a static
// data member (single external-linkage instance per specialization) rather
// than a file-static, so the setter and the getter always observe the same
// object even when their template instantiations are emitted in different
// translation units.
static std::optional<uint32_t> sDeviceTypeIdOverride;

friend GenericDeviceInstanceInfoProvider<ConfigClass>;

#if CHIP_USE_TRANSITIONAL_COMMISSIONABLE_DATA_PROVIDER
Expand Down
24 changes: 22 additions & 2 deletions src/include/platform/internal/GenericConfigurationManagerImpl.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,14 @@ namespace Internal {

namespace {
std::optional<System::Clock::Seconds32> gFirmwareBuildChipEpochTime;
}
} // namespace

// Runtime override for the device type id (see SetDeviceTypeId). Not persisted;
// falls back to CHIP_DEVICE_CONFIG_DEVICE_TYPE when unset. Defined as a static
// data member (not a file-static) so there is a single instance shared by the
// setter and every platform's getter, regardless of translation unit.
template <class ConfigClass>
std::optional<uint32_t> GenericConfigurationManagerImpl<ConfigClass>::sDeviceTypeIdOverride;

#if CHIP_USE_TRANSITIONAL_COMMISSIONABLE_DATA_PROVIDER

Expand Down Expand Up @@ -347,10 +354,23 @@ CHIP_ERROR GenericConfigurationManagerImpl<ConfigClass>::SetFirmwareBuildChipEpo
template <class ConfigClass>
CHIP_ERROR GenericConfigurationManagerImpl<ConfigClass>::GetDeviceTypeId(uint32_t & deviceType)
{
deviceType = static_cast<uint32_t>(CHIP_DEVICE_CONFIG_DEVICE_TYPE);
deviceType = sDeviceTypeIdOverride.value_or(static_cast<uint32_t>(CHIP_DEVICE_CONFIG_DEVICE_TYPE));
return CHIP_NO_ERROR;
}

template <class ConfigClass>
CHIP_ERROR GenericConfigurationManagerImpl<ConfigClass>::SetDeviceTypeId(uint32_t deviceType)
{
sDeviceTypeIdOverride = deviceType;
return CHIP_NO_ERROR;
}

template <class ConfigClass>
std::optional<uint32_t> GenericConfigurationManagerImpl<ConfigClass>::GetDeviceTypeIdOverride()
{
return sDeviceTypeIdOverride;
}

template <class ConfigClass>
CHIP_ERROR GenericConfigurationManagerImpl<ConfigClass>::GetInitialPairingHint(uint16_t & pairingHint)
{
Expand Down
Loading
Loading