diff --git a/examples/energy-management-app/energy-management-common/energy-management-app.matter b/examples/energy-management-app/energy-management-common/energy-management-app.matter index 2c580b5d463276..dae189309d4dbb 100644 --- a/examples/energy-management-app/energy-management-common/energy-management-app.matter +++ b/examples/energy-management-app/energy-management-common/energy-management-app.matter @@ -900,6 +900,85 @@ cluster GroupKeyManagement = 63 { fabric command access(invoke: administer) KeySetReadAllIndices(): KeySetReadAllIndicesResponse = 4; } +/** This cluster provides a mechanism for querying data about the electrical energy imported or provided by the server. */ +provisional cluster ElectricalEnergyMeasurement = 145 { + revision 1; + + enum MeasurementTypeEnum : enum16 { + kUnspecified = 0; + kVoltage = 1; + kActiveCurrent = 2; + kReactiveCurrent = 3; + kApparentCurrent = 4; + kActivePower = 5; + kReactivePower = 6; + kApparentPower = 7; + kRMSVoltage = 8; + kRMSCurrent = 9; + kRMSPower = 10; + kFrequency = 11; + kPowerFactor = 12; + kNeutralCurrent = 13; + kElectricalEnergy = 14; + } + + bitmap Feature : bitmap32 { + kImportedEnergy = 0x1; + kExportedEnergy = 0x2; + kCumulativeEnergy = 0x4; + kPeriodicEnergy = 0x8; + } + + struct MeasurementAccuracyRangeStruct { + int64s rangeMin = 0; + int64s rangeMax = 1; + optional percent100ths percentMax = 2; + optional percent100ths percentMin = 3; + optional percent100ths percentTypical = 4; + optional int64u fixedMax = 5; + optional int64u fixedMin = 6; + optional int64u fixedTypical = 7; + } + + struct MeasurementAccuracyStruct { + MeasurementTypeEnum measurementType = 0; + boolean measured = 1; + int64s minMeasuredValue = 2; + int64s maxMeasuredValue = 3; + MeasurementAccuracyRangeStruct accuracyRanges[] = 4; + } + + struct EnergyMeasurementStruct { + int64s energy = 0; + optional epoch_s startTimestamp = 1; + optional epoch_s endTimestamp = 2; + optional systime_ms startSystime = 3; + optional systime_ms endSystime = 4; + } + + info event CumulativeEnergyMeasured = 0 { + optional EnergyMeasurementStruct energyImported = 0; + optional EnergyMeasurementStruct energyExported = 1; + } + + info event PeriodicEnergyMeasured = 1 { + optional EnergyMeasurementStruct energyImported = 0; + optional EnergyMeasurementStruct energyExported = 1; + } + + readonly attribute MeasurementAccuracyStruct accuracy = 0; + readonly attribute optional nullable EnergyMeasurementStruct cumulativeEnergyImported = 1; + readonly attribute optional nullable EnergyMeasurementStruct cumulativeEnergyExported = 2; + readonly attribute optional nullable EnergyMeasurementStruct periodicEnergyImported = 3; + readonly attribute optional nullable EnergyMeasurementStruct periodicEnergyExported = 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 allows a client to manage the power draw of a device. An example of such a client could be an Energy Management System (EMS) which controls an Energy Smart Appliance (ESA). */ provisional cluster DeviceEnergyManagement = 152 { revision 3; @@ -1598,6 +1677,18 @@ endpoint 1 { callback attribute clusterRevision; } + server cluster ElectricalEnergyMeasurement { + emits event CumulativeEnergyMeasured; + callback attribute accuracy; + callback attribute cumulativeEnergyImported; + callback attribute generatedCommandList; + callback attribute acceptedCommandList; + callback attribute eventList; + callback attribute attributeList; + ram attribute featureMap default = 5; + ram attribute clusterRevision default = 1; + } + server cluster DeviceEnergyManagement { emits event PowerAdjustStart; emits event PowerAdjustEnd; diff --git a/examples/energy-management-app/energy-management-common/energy-management-app.zap b/examples/energy-management-app/energy-management-common/energy-management-app.zap index 78485dfabbd55d..0e1f15b808317d 100644 --- a/examples/energy-management-app/energy-management-common/energy-management-app.zap +++ b/examples/energy-management-app/energy-management-common/energy-management-app.zap @@ -2517,6 +2517,154 @@ } ] }, + { + "name": "Electrical Energy Measurement", + "code": 145, + "mfgCode": null, + "define": "ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER", + "side": "server", + "enabled": 1, + "apiMaturity": "provisional", + "attributes": [ + { + "name": "Accuracy", + "code": 0, + "mfgCode": null, + "side": "server", + "type": "MeasurementAccuracyStruct", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "CumulativeEnergyImported", + "code": 1, + "mfgCode": null, + "side": "server", + "type": "EnergyMeasurementStruct", + "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": "5", + "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": "CumulativeEnergyMeasured", + "code": 0, + "mfgCode": null, + "side": "server", + "included": 1 + } + ] + }, { "name": "Device Energy Management", "code": 152, diff --git a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h index df757f7374e672..f1c3eac6f60dc9 100644 --- a/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EVSEManufacturerImpl.h @@ -60,6 +60,56 @@ class EVSEManufacturer */ static void ApplicationCallbackHandler(const EVSECbInfo * cb, intptr_t arg); + /** + * @brief Allows a client application to send in power readings into the system + * + * @param[in] aEndpointId - Endpoint to send to EPM Cluster + * @param[in] aActivePower_mW - Power measured in milli-watts + * @param[in] aVoltage_mV - Voltage measured in milli-volts + * @param[in] aCurrent_mA - Current measured in milli-amps + */ + CHIP_ERROR SendPowerReading(EndpointId aEndpointId, int64_t aActivePower_mW, int64_t aVoltage_mV, int64_t aCurrent_mA); + + /** + * @brief Allows a client application to send in energy readings into the system + * + * This is a helper function to add timestamps to the readings + * + * @param[in] aCumulativeEnergyImported -total energy imported in milli-watthours + * @param[in] aCumulativeEnergyExported -total energy exported in milli-watthours + */ + CHIP_ERROR SendEnergyReading(EndpointId aEndpointId, int64_t aCumulativeEnergyImported, int64_t aCumulativeEnergyExported); + + /** Fake Meter data generation - used for testing EPM/EEM clusters */ + /** + * @brief Starts a fake load/generator to periodically callback the power and energy + * clusters. + * @param[in] aEndpointId - which endpoint is the meter to be updated on + * @param[in] aPower_mW - the mean power of the load + * Positive power indicates Imported energy (e.g. a load) + * Negative power indicated Exported energy (e.g. a generator) + * @param[in] aPowerRandomness_mW This is used to scale random power fluctuations around the mean power of the load + * + * @param[in] aInterval_s - the callback interval in seconds + * @param[in] bReset - boolean: true will reset the energy values to 0 + */ + void StartFakeReadings(EndpointId aEndpointId, int64_t aPower_mW, uint32_t aPowerRandomness_mW, uint8_t aInterval_s, + bool bReset); + + /** + * @brief Stops any active updates to the fake load data callbacks + */ + void StopFakeReadings(); + + /** + * @brief Sends fake meter data into the cluster and restarts the timer + */ + void FakeReadingsUpdate(); + /** + * @brief Timer expiry callback to handle fake load + */ + static void FakeReadingsTimerExpiry(System::Layer * systemLayer, void * manufacturer); + private: EnergyEvseManager * mInstance; diff --git a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h index 558f7c13df2766..e6f5c8c324d91e 100644 --- a/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h +++ b/examples/energy-management-app/energy-management-common/include/EnergyEvseDelegateImpl.h @@ -26,6 +26,14 @@ #include using chip::Protocols::InteractionModel::Status; + +/** + * @brief Helper function to get current timestamp in Epoch format + * + * @param chipEpoch reference to hold return timestamp + */ +CHIP_ERROR GetEpochTS(uint32_t & chipEpoch); + namespace chip { namespace app { namespace Clusters { diff --git a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp index fe51c9da664b6d..70cd63e7926c8d 100644 --- a/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp +++ b/examples/energy-management-app/energy-management-common/src/EVSEManufacturerImpl.cpp @@ -18,12 +18,16 @@ #include #include +#include +#include #include using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; using namespace chip::app::Clusters::EnergyEvse; +using namespace chip::app::Clusters::ElectricalEnergyMeasurement; +using namespace chip::app::Clusters::ElectricalEnergyMeasurement::Structs; CHIP_ERROR EVSEManufacturer::Init() { @@ -89,6 +93,179 @@ CHIP_ERROR EVSEManufacturer::Shutdown() return CHIP_NO_ERROR; } +/** + * @brief Allows a client application to send in power readings into the system + * + * @param[in] aEndpointId - Endpoint to send to EPM Cluster + * @param[in] aActivePower_mW - Power measured in milli-watts + * @param[in] aVoltage_mV - Voltage measured in milli-volts + * @param[in] aCurrent_mA - Current measured in milli-amps + */ +CHIP_ERROR EVSEManufacturer::SendPowerReading(EndpointId aEndpointId, int64_t aActivePower_mW, int64_t aVoltage_mV, + int64_t aCurrent_mA) +{ + // TODO add Power Readings when EPM cluster is merged + + return CHIP_NO_ERROR; +} + +/** + * @brief Allows a client application to send in energy readings into the system + * + * This is a helper function to add timestamps to the readings + * + * @param[in] aCumulativeEnergyImported -total energy imported in milli-watthours + * @param[in] aCumulativeEnergyExported -total energy exported in milli-watthours + */ +CHIP_ERROR EVSEManufacturer::SendEnergyReading(EndpointId aEndpointId, int64_t aCumulativeEnergyImported, + int64_t aCumulativeEnergyExported) +{ + MeasurementData * data = MeasurementDataForEndpoint(aEndpointId); + + EnergyMeasurementStruct::Type energyImported; + EnergyMeasurementStruct::Type energyExported; + + // Get current timestamp + uint32_t currentTimestamp; + CHIP_ERROR err = GetEpochTS(currentTimestamp); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "GetEpochTS returned error getting timestamp"); + return err; + } + + /** IMPORT */ + // Copy last endTimestamp into new startTimestamp if it exists + energyImported.startTimestamp.ClearValue(); + if (data->cumulativeImported.HasValue()) + { + energyImported.startTimestamp = data->cumulativeImported.Value().endTimestamp; + } + + energyImported.endTimestamp.SetValue(currentTimestamp); + energyImported.energy = aCumulativeEnergyImported; + + /** EXPORT */ + // Copy last endTimestamp into new startTimestamp if it exists + energyExported.startTimestamp.ClearValue(); + if (data->cumulativeExported.HasValue()) + { + energyExported.startTimestamp = data->cumulativeExported.Value().endTimestamp; + } + energyExported.endTimestamp.SetValue(currentTimestamp); + energyExported.energy = aCumulativeEnergyExported; + + // call the SDK to update attributes and generate an event + if (!NotifyCumulativeEnergyMeasured(aEndpointId, MakeOptional(energyImported), MakeOptional(energyExported))) + { + ChipLogError(AppServer, "Failed to notify Cumulative Energy reading."); + return CHIP_ERROR_INTERNAL; + } + + return CHIP_NO_ERROR; +} + +struct FakeReadingsData +{ + bool bEnabled; /* If enabled then the timer callback will re-trigger */ + EndpointId mEndpointId; /* Which endpoint the meter is on */ + uint8_t mInterval_s; /* Interval in seconds to callback */ + int64_t mPower_mW; /* Power on the load in mW (signed value) +ve = imported */ + uint32_t mPowerRandomness_mW; /* The amount to randomize the Power on the load in mW */ + /* These energy values can only be positive values. + * however the underlying energy type (power_mWh) is signed, so keeping with that convention */ + int64_t mTotalEnergyImported = 0; /* Energy Imported which is updated if mPower > 0 */ + int64_t mTotalEnergyExported = 0; /* Energy Imported which is updated if mPower < 0 */ +}; + +static FakeReadingsData gFakeReadingsData; + +/* This helper routine starts and handles a callback */ +/** + * @brief Starts a fake load/generator to periodically callback the power and energy + * clusters. + * @param[in] aEndpointId - which endpoint is the meter to be updated on + * @param[in] aPower_mW - the mean power of the load + * Positive power indicates Imported energy (e.g. a load) + * Negative power indicated Exported energy (e.g. a generator) + * @param[in] aPowerRandomness_mW This is used to define the std.dev of the + * random power values around the mean power of the load + * + * @param[in] aInterval_s - the callback interval in seconds + * @param[in] bReset - boolean: true will reset the energy values to 0 + */ +void EVSEManufacturer::StartFakeReadings(EndpointId aEndpointId, int64_t aPower_mW, uint32_t aPowerRandomness_mW, + uint8_t aInterval_s, bool bReset) +{ + gFakeReadingsData.bEnabled = true; + gFakeReadingsData.mEndpointId = aEndpointId; + gFakeReadingsData.mPower_mW = aPower_mW; + gFakeReadingsData.mPowerRandomness_mW = aPowerRandomness_mW; + gFakeReadingsData.mInterval_s = aInterval_s; + + if (bReset) + { + gFakeReadingsData.mTotalEnergyImported = 0; + gFakeReadingsData.mTotalEnergyExported = 0; + } + + // Call update function to kick off regular readings + FakeReadingsUpdate(); +} +/** + * @brief Stops any active updates to the fake load data callbacks + */ +void EVSEManufacturer::StopFakeReadings() +{ + gFakeReadingsData.bEnabled = false; +} +/** + * @brief Sends fake meter data into the cluster and restarts the timer + */ +void EVSEManufacturer::FakeReadingsUpdate() +{ + /* Check to see if the fake Load is still running - don't send updates if the timer was already cancelled */ + if (!gFakeReadingsData.bEnabled) + { + return; + } + + // Update meter values + // Avoid using floats - so we will do a basic rand() call which will generate a integer value between 0 and RAND_MAX + // first compute power as a mean + some random value in range 0 to mPowerRandomness_mW + int64_t power = (rand() % gFakeReadingsData.mPowerRandomness_mW); + power += gFakeReadingsData.mPower_mW; // add in the base power + + // TODO call the EPM cluster to send a power reading + + // update the energy meter - we'll assume that the power has been constant during the previous interval + if (gFakeReadingsData.mPower_mW > 0) + { + // Positive power - means power is imported + gFakeReadingsData.mTotalEnergyImported += ((power * gFakeReadingsData.mInterval_s) / 3600); + } + else + { + // Negative power - means power is exported, but the cumulative energy is positive + gFakeReadingsData.mTotalEnergyExported += ((-power * gFakeReadingsData.mInterval_s) / 3600); + } + + SendEnergyReading(gFakeReadingsData.mEndpointId, gFakeReadingsData.mTotalEnergyImported, + gFakeReadingsData.mTotalEnergyExported); + + // start/restart the timer + DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds32(gFakeReadingsData.mInterval_s), FakeReadingsTimerExpiry, this); +} +/** + * @brief Timer expiry callback to handle fake load + */ +void EVSEManufacturer::FakeReadingsTimerExpiry(System::Layer * systemLayer, void * manufacturer) +{ + EVSEManufacturer * mn = reinterpret_cast(manufacturer); + + mn->FakeReadingsUpdate(); +} + /** * @brief Main Callback handler - to be implemented by Manufacturer * @@ -231,6 +408,37 @@ void SetTestEventTrigger_EVSEDiagnosticsComplete() dg->HwDiagnosticsComplete(); } +void SetTestEventTrigger_FakeReadingsLoadStart() +{ + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrDieWithMsg(mn != nullptr, AppServer, "EVSEManufacturer is null"); + + int64_t aPower_mW = 1'000'000; // Fake load 1000 W + uint32_t aPowerRandomness_mW = 20'000; // randomness 20W + uint8_t aInterval_s = 2; // 2s updates + bool bReset = true; + mn->StartFakeReadings(EndpointId(1), aPower_mW, aPowerRandomness_mW, aInterval_s, bReset); +} + +void SetTestEventTrigger_FakeReadingsGeneratorStart() +{ + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrDieWithMsg(mn != nullptr, AppServer, "EVSEManufacturer is null"); + + int64_t aPower_mW = -3'000'000; // Fake Generator -3000 W + uint32_t aPowerRandomness_mW = 20'000; // randomness 20W + uint8_t aInterval_s = 5; // 5s updates + bool bReset = true; + mn->StartFakeReadings(EndpointId(1), aPower_mW, aPowerRandomness_mW, aInterval_s, bReset); +} + +void SetTestEventTrigger_FakeReadingsStop() +{ + EVSEManufacturer * mn = GetEvseManufacturer(); + VerifyOrDieWithMsg(mn != nullptr, AppServer, "EVSEManufacturer is null"); + mn->StopFakeReadings(); +} + bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) { EnergyEvseTrigger trigger = static_cast(eventTrigger); @@ -284,3 +492,29 @@ bool HandleEnergyEvseTestEventTrigger(uint64_t eventTrigger) return true; } + +bool HandleEnergyReportingTestEventTrigger(uint64_t eventTrigger) +{ + EnergyReportingTrigger trigger = static_cast(eventTrigger); + + switch (trigger) + { + case EnergyReportingTrigger::kFakeReadingsStop: + ChipLogProgress(Support, "[EnergyReporting-Test-Event] => Stop Fake load"); + SetTestEventTrigger_FakeReadingsStop(); + break; + case EnergyReportingTrigger::kFakeReadingsLoadStart_1kW_2s: + ChipLogProgress(Support, "[EnergyReporting-Test-Event] => Start Fake load 1kW @2s Import"); + SetTestEventTrigger_FakeReadingsLoadStart(); + break; + case EnergyReportingTrigger::kFakeReadingsGenStart_3kW_5s: + ChipLogProgress(Support, "[EnergyReporting-Test-Event] => Start Fake generator 3kW @5s Export"); + SetTestEventTrigger_FakeReadingsGeneratorStart(); + break; + + default: + return false; + } + + return true; +} diff --git a/examples/energy-management-app/esp32/main/CMakeLists.txt b/examples/energy-management-app/esp32/main/CMakeLists.txt index 46f97d766dbfeb..edba8f9671d1bc 100644 --- a/examples/energy-management-app/esp32/main/CMakeLists.txt +++ b/examples/energy-management-app/esp32/main/CMakeLists.txt @@ -65,6 +65,7 @@ set(SRC_DIRS_LIST "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/groups-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/group-key-mgmt-server" "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/mode-base-server" + "${CMAKE_SOURCE_DIR}/third_party/connectedhomeip/src/app/clusters/electrical-energy-measurement-server" ) set(PRIV_REQUIRES_LIST chip QRCode bt led_strip app_update openthread driver nvs_flash spi_flash) diff --git a/examples/energy-management-app/linux/args.gni b/examples/energy-management-app/linux/args.gni index b7c71c3097a680..e1000e6cb3fa27 100644 --- a/examples/energy-management-app/linux/args.gni +++ b/examples/energy-management-app/linux/args.gni @@ -30,3 +30,4 @@ matter_enable_tracing_support = true chip_enable_read_client = false chip_enable_energy_evse_trigger = true +chip_enable_energy_reporting_trigger = true diff --git a/examples/energy-management-app/linux/main.cpp b/examples/energy-management-app/linux/main.cpp index 824fa17316cc70..a150b82c4bb171 100644 --- a/examples/energy-management-app/linux/main.cpp +++ b/examples/energy-management-app/linux/main.cpp @@ -208,7 +208,6 @@ CHIP_ERROR EVSEManufacturerInit() } /* Now create EVSEManufacturer */ - // TODO this takes just the EVSE Instance for now, but will need the DEM adding gEvseManufacturer = std::make_unique(gEvseInstance.get()); if (!gEvseManufacturer) { diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index 933502dc050c69..04274ab2f33c24 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -86,6 +86,9 @@ #if CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER #include #endif +#if CHIP_DEVICE_CONFIG_ENABLE_ENERGY_REPORTING_TRIGGER +#include +#endif #include #include @@ -571,6 +574,12 @@ void ChipLinuxAppMainLoop(AppMainLoopImplementation * impl) }; otherDelegate = &energyEvseTestEventTriggerDelegate; #endif +#if CHIP_DEVICE_CONFIG_ENABLE_ENERGY_REPORTING_TRIGGER + static EnergyReportingTestEventTriggerDelegate energyReportingTestEventTriggerDelegate{ + ByteSpan(LinuxDeviceOptions::GetInstance().testEventTriggerEnableKey), otherDelegate + }; + otherDelegate = &energyReportingTestEventTriggerDelegate; +#endif // For general testing of TestEventTrigger, we have a common "core" event trigger delegate. static SampleTestEventTriggerDelegate testEventTriggerDelegate; diff --git a/examples/platform/linux/BUILD.gn b/examples/platform/linux/BUILD.gn index fbbf7e1833894e..f82702be227709 100644 --- a/examples/platform/linux/BUILD.gn +++ b/examples/platform/linux/BUILD.gn @@ -23,6 +23,7 @@ declare_args() { chip_enable_smoke_co_trigger = false chip_enable_boolean_state_configuration_trigger = false chip_enable_energy_evse_trigger = false + chip_enable_energy_reporting_trigger = false } config("app-main-config") { @@ -47,6 +48,10 @@ source_set("energy-evse-test-event-trigger") { sources = [ "${chip_root}/src/app/clusters/energy-evse-server/EnergyEvseTestEventTriggerDelegate.h" ] } +source_set("energy-reporting-test-event-trigger") { + sources = [ "${chip_root}/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.h" ] +} + source_set("app-main") { defines = [ "ENABLE_TRACING=${matter_enable_tracing_support}" ] sources = [ @@ -71,6 +76,7 @@ source_set("app-main") { public_deps = [ ":boolean-state-configuration-test-event-trigger", ":energy-evse-test-event-trigger", + ":energy-reporting-test-event-trigger", ":smco-test-event-trigger", "${chip_root}/src/lib", "${chip_root}/src/platform/logging:force_stdio", @@ -110,6 +116,7 @@ source_set("app-main") { "CHIP_DEVICE_CONFIG_ENABLE_SMOKE_CO_TRIGGER=${chip_enable_smoke_co_trigger}", "CHIP_DEVICE_CONFIG_ENABLE_BOOLEAN_STATE_CONFIGURATION_TRIGGER=${chip_enable_boolean_state_configuration_trigger}", "CHIP_DEVICE_CONFIG_ENABLE_ENERGY_EVSE_TRIGGER=${chip_enable_energy_evse_trigger}", + "CHIP_DEVICE_CONFIG_ENABLE_ENERGY_REPORTING_TRIGGER=${chip_enable_energy_reporting_trigger}", ] public_configs = [ ":app-main-config" ] diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index 4afb4f6556fe28..f0fdb32484a72e 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -773,7 +773,8 @@ func connect(selectedCastingPlayer: MCCastingPlayer?) { ### Select an Endpoint on the Casting Player -_{Complete Endpoint selection examples: [Linux](linux/simple-app-helper.cpp)}_ +_{Complete Endpoint selection examples: [Linux](linux/simple-app-helper.cpp) | +[iOS](darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift)}_ On a successful connection with a `CastingPlayer`, a Casting Client may select one of the Endpoints to interact with based on its attributes (e.g. Vendor ID, @@ -799,6 +800,19 @@ if (it != endpoints.end()) } ``` +On iOS, it can select an `MCEndpoint` similarly and as shown below. + +```swift +// VendorId of the MCEndpoint on the MCCastingPlayer that the MCCastingApp desires to interact with after connection +let sampleEndpointVid: Int = 65521 +... +// select the MCEndpoint on the MCCastingPlayer to invoke the command on +if let endpoint: MCEndpoint = castingPlayer.endpoints().filter({ $0.vendorId().intValue == sampleEndpointVid}).first +{ +... +} +``` + ## Interacting with a Casting Endpoint Once the Casting Client has selected an `Endpoint`, it is ready to @@ -819,13 +833,14 @@ response types to use with these APIs: ### Issuing Commands -_{Complete Command invocation examples: [Linux](linux/simple-app-helper.cpp)}_ +_{Complete Command invocation examples: [Linux](linux/simple-app-helper.cpp) | +[iOS](darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift)}_ -The Casting Client can get a reference to a `endpoint` on a `CastingPlayer`, +The Casting Client can get a reference to an `Endpoint` on a `CastingPlayer`, check if it supports the required cluster/command, and send commands to it. It can then handle any command response / error the `CastingPlayer` sends back. -On Linux, for example, given an `endpoint`, it can send a `LaunchURL` command +On Linux, for example, given an `Endpoint`, it can send a `LaunchURL` command (part of the Content Launcher cluster) by calling the `Invoke` API on a `Command` of type `matter::casting::core::Command` @@ -865,9 +880,65 @@ void InvokeContentLauncherLaunchURL(matter::casting::memory::Strong + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttribute.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttribute.h new file mode 100644 index 00000000000000..ba9f40c24c519c --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttribute.h @@ -0,0 +1,48 @@ +/** + * + * Copyright (c) 2020-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. + */ + +#import "MCObserver.h" + +#import + +#ifndef MCAttribute_h +#define MCAttribute_h + +@interface MCAttribute<__covariant ObjectType> : NSObject + +/** + * @brief Reads the value of the MCAttribute that belongs to the associated MCEndpoint and corresponding MCCluster + * @param context current context passed back in completion block + * @param completion Called when attribute read completes with nil NSError if successful and before, after values. On failure, the NSError describes the error + */ +- (void)read:(void * _Nullable)context + completion:(void (^_Nonnull)(void * _Nullable context, ObjectType _Nullable before, ObjectType _Nullable after, NSError * _Nullable error))completion; + +/** + * @brief Subscribe to the value of the MCAttribute that belongs to the associated MCEndpoint and corresponding MCCluster + * @param context current context passed back in completion block + * @param completion Called when attribute read completes with nil NSError if successful and before, after values. On failure, the NSError describes the error + * @param minInterval the requested minimum interval boundary floor in seconds for attribute udpates + * @param maxInterval the requested maximum interval boundary ceiling in seconds for attribute udpates + */ +- (void)subscribe:(void * _Nullable)context + completion:(void (^_Nonnull)(void * _Nullable context, ObjectType _Nullable before, ObjectType _Nullable after, NSError * _Nullable error))completion + minInterval:(NSNumber * _Nonnull)minInterval + maxInterval:(NSNumber * _Nonnull)maxInterval; +@end + +#endif /* MCAttribute_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttribute.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttribute.mm new file mode 100644 index 00000000000000..09397ab86f7060 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttribute.mm @@ -0,0 +1,50 @@ +/** + * + * 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. + */ + +#import "MCAttribute_Internal.h" +#import "MCCastingApp.h" + +#import + +@implementation MCAttribute + +- (instancetype _Nonnull)initWithCppAttribute:(void *)cppAttribute +{ + if (self = [super init]) { + _cppAttribute = cppAttribute; + } + return self; +} + +- (void)read:(void * _Nullable)context completion:(void (^_Nonnull __strong)(void * _Nullable, id _Nullable __strong, id _Nullable __strong, NSError * _Nullable __strong))completion +{ +} + +- (void)subscribe:(void * _Nullable)context completion:(void (^_Nonnull __strong)(void * _Nullable, id _Nullable __strong, id _Nullable __strong, NSError * _Nullable __strong))completion minInterval:(NSNumber *)minInterval maxInterval:(NSNumber *)maxInterval +{ +} + +- (void)write:(id _Nonnull)value withCompletionBlock:(void (^_Nonnull __strong)(NSError * _Nullable __strong))completionBlock +{ +} + +- (id _Nullable)getObjTypeFromCpp:(std::any)cppValue +{ + return nil; +} + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttributeObjects.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttributeObjects.h new file mode 100644 index 00000000000000..d8f371ee1eebbe --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttributeObjects.h @@ -0,0 +1,30 @@ +/** + * + * Copyright (c) 2020-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. + */ + +#import "MCAttribute.h" +#import + +#ifndef MCAttributeObjects_h +#define MCAttributeObjects_h + +@interface MCApplicationBasicClusterVendorIDAttribute : MCAttribute +@end + +@interface MCMediaPlaybackClusterCurrentStateAttribute : MCAttribute +@end + +#endif /* MCAttributeObjects_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttributeObjects.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttributeObjects.mm new file mode 100644 index 00000000000000..5ce91d5ec781bb --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttributeObjects.mm @@ -0,0 +1,109 @@ +/** + * + * 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. + */ + +#import "MCAttributeObjects.h" + +#import "MCAttribute_Internal.h" +#import "MCCastingApp.h" +#import "MCErrorUtils.h" + +#include "core/Attribute.h" +#include + +#import + +@implementation MCApplicationBasicClusterVendorIDAttribute +- (void)read:(void * _Nullable)context + completion:(void (^_Nonnull __strong)(void * _Nullable, id _Nullable __strong before, id _Nullable __strong after, NSError * _Nullable __strong error))completion +{ + MCAttributeTemplate * mcAttribute = new MCAttributeTemplate(self.cppAttribute, + [self](std::any cppValue) { + return [self getObjTypeFromCpp:cppValue]; + }); + mcAttribute->read(context, [mcAttribute, completion](void * context, id before, id after, NSError * err) { + completion(context, before, after, err); + delete mcAttribute; + }); +} + +- (void)subscribe:(void * _Nullable)context + completion:(void (^_Nonnull __strong)(void * _Nullable, id _Nullable __strong before, id _Nullable __strong after, NSError * _Nullable __strong error))completion + minInterval:(NSNumber * _Nonnull)minInterval + maxInterval:(NSNumber * _Nonnull)maxInterval +{ + MCAttributeTemplate * mcAttribute = new MCAttributeTemplate(self.cppAttribute, + [self](std::any cppValue) { + return [self getObjTypeFromCpp:cppValue]; + }); + mcAttribute->subscribe( + context, [mcAttribute, completion](void * context, id before, id after, NSError * err) { + completion(context, before, after, err); + delete mcAttribute; + }, minInterval, maxInterval); +} + +- (id _Nullable)getObjTypeFromCpp:(std::any)cppValue +{ + NSNumber * outValue = nil; + if (cppValue.type() == typeid(std::shared_ptr)) { + std::shared_ptr valueSharedPtr = std::any_cast>(cppValue); + outValue = valueSharedPtr != nil ? [NSNumber numberWithUnsignedInteger:*valueSharedPtr] : nil; + } + return outValue; +} +@end + +@implementation MCMediaPlaybackClusterCurrentStateAttribute +- (void)read:(void * _Nullable)context + completion:(void (^_Nonnull __strong)(void * _Nullable, id _Nullable __strong before, id _Nullable __strong after, NSError * _Nullable __strong error))completion +{ + MCAttributeTemplate * mcAttribute = new MCAttributeTemplate(self.cppAttribute, + [self](std::any cppValue) { + return [self getObjTypeFromCpp:cppValue]; + }); + mcAttribute->read(context, [mcAttribute, completion](void * context, id before, id after, NSError * err) { + completion(context, before, after, err); + delete mcAttribute; + }); +} + +- (void)subscribe:(void * _Nullable)context + completion:(void (^_Nonnull __strong)(void * _Nullable, id _Nullable __strong before, id _Nullable __strong after, NSError * _Nullable __strong error))completion + minInterval:(NSNumber * _Nonnull)minInterval + maxInterval:(NSNumber * _Nonnull)maxInterval +{ + MCAttributeTemplate * mcAttribute = new MCAttributeTemplate(self.cppAttribute, + [self](std::any cppValue) { + return [self getObjTypeFromCpp:cppValue]; + }); + mcAttribute->subscribe( + context, [mcAttribute, completion](void * context, id before, id after, NSError * err) { + completion(context, before, after, err); + delete mcAttribute; + }, minInterval, maxInterval); +} + +- (id _Nullable)getObjTypeFromCpp:(std::any)cppValue +{ + NSNumber * outValue = nil; + if (cppValue.type() == typeid(std::shared_ptr)) { + std::shared_ptr valueSharedPtr = std::any_cast>(cppValue); + outValue = valueSharedPtr != nil ? [NSNumber numberWithUnsignedInteger:static_cast(*valueSharedPtr)] : nil; + } + return outValue; +} +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttribute_Internal.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttribute_Internal.h new file mode 100644 index 00000000000000..9a0c7e0385f515 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCAttribute_Internal.h @@ -0,0 +1,120 @@ +/** + * + * 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. + */ + +#import "MCAttribute.h" + +#import "MCCastingApp.h" +#import "MCErrorUtils.h" +#include "core/Attribute.h" +#include + +#import +#include +#include +#include + +#ifndef MCAttribute_Internal_h +#define MCAttribute_Internal_h + +@interface MCAttribute () + +@property (nonatomic, readwrite) void * _Nonnull cppAttribute; + +- (instancetype _Nonnull)initWithCppAttribute:(void * _Nonnull)cppAttribute; + +- (id _Nullable)getObjTypeFromCpp:(std::any)cppValue; + +@end + +template +class MCAttributeTemplate { +public: + MCAttributeTemplate(void * _Nonnull cppAttribute, + std::function getObjTypeFromCppFn) + { + mCppAttribute = cppAttribute; + mGetObjTypeFromCppFn = getObjTypeFromCppFn; + } + + void read(void * _Nullable context, std::function completion) + { + dispatch_queue_t workQueue = [[MCCastingApp getSharedInstance] getWorkQueue], clientQueue = [[MCCastingApp getSharedInstance] getClientQueue]; + dispatch_sync(workQueue, ^{ + matter::casting::core::Attribute * attribute = static_cast *>(mCppAttribute); + + attribute->Read( + context, + [clientQueue, completion, this](void * context, chip::Optional before, typename TypeInfo::DecodableArgType after) { + NSNumber *objCBefore = nil, *objCAfter = nil; + if (before.HasValue()) { + ChipLogProgress(AppServer, " converting 'before' value from Cpp to ObjC"); + std::any anyBefore = std::any(std::make_shared(before.Value())); + objCBefore = mGetObjTypeFromCppFn(anyBefore); + } + ChipLogProgress(AppServer, " converting 'after' value from Cpp to ObjC"); + std::any anyAfter = std::any(std::make_shared(after)); + objCAfter = mGetObjTypeFromCppFn(anyAfter); + dispatch_async(clientQueue, ^{ + completion(context, objCBefore, objCAfter, nil); + }); + }, + [clientQueue, completion](void * context, CHIP_ERROR error) { + dispatch_async(clientQueue, ^{ + completion(context, nil, nil, [MCErrorUtils NSErrorFromChipError:error]); + }); + }); + }); + } + + void subscribe(void * _Nullable context, std::function completion, + NSNumber * _Nonnull minInterval, NSNumber * _Nonnull maxInterval) + { + dispatch_queue_t workQueue = [[MCCastingApp getSharedInstance] getWorkQueue], clientQueue = [[MCCastingApp getSharedInstance] getClientQueue]; + dispatch_sync(workQueue, ^{ + matter::casting::core::Attribute * attribute = static_cast *>(mCppAttribute); + + attribute->Subscribe( + context, + [clientQueue, completion, this](void * context, chip::Optional before, typename TypeInfo::DecodableArgType after) { + NSNumber *objCBefore = nil, *objCAfter = nil; + if (before.HasValue()) { + ChipLogProgress(AppServer, " converting 'before' value from Cpp to ObjC"); + std::any anyBefore = std::any(std::make_shared(before.Value())); + objCBefore = mGetObjTypeFromCppFn(anyBefore); + } + ChipLogProgress(AppServer, " converting 'after' value from Cpp to ObjC"); + std::any anyAfter = std::any(std::make_shared(after)); + objCAfter = mGetObjTypeFromCppFn(anyAfter); + dispatch_async(clientQueue, ^{ + completion(context, objCBefore, objCAfter, nil); + }); + }, + [clientQueue, completion](void * context, CHIP_ERROR error) { + dispatch_async(clientQueue, ^{ + completion(context, nil, nil, [MCErrorUtils NSErrorFromChipError:error]); + }); + }, + minInterval.intValue, + maxInterval.intValue); + }); + } + +private: + void * _Nonnull mCppAttribute; + std::function mGetObjTypeFromCppFn; +}; +#endif /* MCAttribute_Internal_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h index 4259fe53cc7121..cc3d90e8a2f922 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.h @@ -48,20 +48,20 @@ - (void)verifyOrEstablishConnectionWithCompletionBlock:(void (^_Nonnull)(NSError * _Nullable))completion timeout:(long long)timeout desiredEndpointFilter:(MCEndpointFilter * _Nullable)desiredEndpointFilter; /** - * @brief (async) Verifies that a connection exists with this CastingPlayer, or triggers a new session request. If the - * CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on disk, this will execute the user + * @brief (async) Verifies that a connection exists with this MCCastingPlayer, or triggers a new session request. If the + * MCCastingApp does not have the nodeId and fabricIndex of this MCCastingPlayer cached on disk, this will execute the user * directed commissioning process. * * @param completion - called back when the connection process completes. Parameter is nil if it completed successfully - * @param desiredEndpointFilter - Attributes (such as VendorId) describing an Endpoint that the client wants to interact + * @param desiredEndpointFilter - Attributes (such as VendorId) describing an MCEndpoint that the client wants to interact * with after commissioning. If this value is passed in, the VerifyOrEstablishConnection will force User Directed - * Commissioning, in case the desired Endpoint is not found in the on-device cached information about the CastingPlayer + * Commissioning, in case the desired Endpoint is not found in the on-device cached information about the MCCastingPlayer * (if any) */ - (void)verifyOrEstablishConnectionWithCompletionBlock:(void (^_Nonnull)(NSError * _Nullable))completion desiredEndpointFilter:(MCEndpointFilter * _Nullable)desiredEndpointFilter; /** - * @brief Sets the internal connection state of this CastingPlayer to "disconnected" + * @brief Sets the internal connection state of this MCCastingPlayer to "disconnected" */ - (void)disconnect; @@ -72,8 +72,10 @@ - (uint32_t)deviceType; - (NSArray * _Nonnull)ipAddresses; -// TODO -// - (NSArray * _Nonnull)endpoints; +/** + * @brief Returns the NSArray of MCEndpoints associated with this MCCastingPlayer + */ +- (NSArray * _Nonnull)endpoints; - (nonnull instancetype)init UNAVAILABLE_ATTRIBUTE; + (nonnull instancetype)new UNAVAILABLE_ATTRIBUTE; diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm index 40ea160d5abc3b..45cc9568f472a1 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer.mm @@ -18,6 +18,7 @@ #import "MCCastingPlayer.h" #import "MCCastingApp.h" +#import "MCEndpoint_Internal.h" #import "MCErrorUtils.h" #import "core/CastingPlayer.h" @@ -78,12 +79,6 @@ - (void)disconnect }); } -- (NSString * _Nonnull)description -{ - return [NSString stringWithFormat:@"%@ with Product ID: %d and Vendor ID: %d. Resolved IPAddr?: %@", - self.deviceName, self.productId, self.vendorId, self.ipAddresses != nil && self.ipAddresses.count > 0 ? @"YES" : @"NO"]; -} - - (instancetype _Nonnull)initWithCppCastingPlayer:(matter::casting::memory::Strong)cppCastingPlayer { if (self = [super init]) { @@ -92,6 +87,12 @@ - (instancetype _Nonnull)initWithCppCastingPlayer:(matter::casting::memory::Stro return self; } +- (NSString * _Nonnull)description +{ + return [NSString stringWithFormat:@"%@ with Product ID: %hu and Vendor ID: %hu. Resolved IPAddr?: %@", + self.deviceName, self.productId, self.vendorId, self.ipAddresses != nil && self.ipAddresses.count > 0 ? @"YES" : @"NO"]; +} + - (NSString * _Nonnull)identifier { return [NSString stringWithCString:_cppCastingPlayer->GetId() encoding:NSUTF8StringEncoding]; @@ -128,11 +129,16 @@ - (NSArray * _Nonnull)ipAddresses return ipAddresses; } -// TODO convert to Obj-C endpoints and return -/*- (NSArray * _Nonnull)endpoints +- (NSArray * _Nonnull)endpoints { - return [NSMutableArray new]; -}*/ + NSMutableArray * endpoints = [NSMutableArray new]; + const std::vector> cppEndpoints = _cppCastingPlayer->GetEndpoints(); + for (matter::casting::memory::Strong cppEndpoint : cppEndpoints) { + MCEndpoint * endpoint = [[MCEndpoint alloc] initWithCppEndpoint:cppEndpoint]; + [endpoints addObject:endpoint]; + } + return endpoints; +} - (BOOL)isEqualToMCCastingPlayer:(MCCastingPlayer * _Nullable)other { diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer_Internal.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer_Internal.h new file mode 100644 index 00000000000000..6c52733c3d6909 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCastingPlayer_Internal.h @@ -0,0 +1,33 @@ +/** + * + * 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. + */ + +#import "MCCastingPlayer.h" + +#import "core/CastingPlayer.h" + +#import + +#ifndef MCCastingPlayer_Internal_h +#define MCCastingPlayer_Internal_h + +@interface MCCastingPlayer () + +- (instancetype _Nonnull)initWithCppCastingPlayer:(matter::casting::memory::Strong)cppCastingPlayer; + +@end + +#endif /* MCCastingPlayer_Internal_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCluster.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCluster.h new file mode 100644 index 00000000000000..bd150b7c6ea8e5 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCluster.h @@ -0,0 +1,36 @@ +/** + * + * Copyright (c) 2020-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. + */ + +#import "MCCommand.h" +#import "MCEndpoint.h" +#import + +#ifndef MCCluster_h +#define MCCluster_h + +@class MCEndpoint; + +@interface MCCluster : NSObject + +/** + * @brief Get the MCEndpoint corresponding to this MCCluster + */ +- (MCEndpoint * _Nonnull)endpoint; + +@end + +#endif /* MCCluster_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCluster.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCluster.mm new file mode 100644 index 00000000000000..ecb7a9175925d2 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCluster.mm @@ -0,0 +1,42 @@ +/** + * + * 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. + */ + +#import "MCClusterObjects.h" +#import "MCCluster_Internal.h" + +#import "MCEndpoint_Internal.h" + +#import "core/Endpoint.h" + +#import + +@implementation MCCluster + +- (instancetype _Nonnull)initWithCppCluster:(matter::casting::memory::Strong)cppCluster +{ + if (self = [super init]) { + _cppCluster = cppCluster; + } + return self; +} + +- (MCEndpoint * _Nonnull)endpoint +{ + return [[MCEndpoint alloc] initWithCppEndpoint:_cppCluster->GetEndpoint().lock()]; +} + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCClusterObjects.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCClusterObjects.h new file mode 100644 index 00000000000000..290b747702e272 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCClusterObjects.h @@ -0,0 +1,47 @@ +/** + * + * Copyright (c) 2020-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. + */ + +#import "MCAttributeObjects.h" +#import "MCCluster.h" +#import "MCCommandObjects.h" +#import + +#ifndef MCClusterObjects_h +#define MCClusterObjects_h + +@interface MCContentLauncherCluster : MCCluster +/** + * @brief Returns non-nil pointer to MCContentLauncherClusterLaunchURLCommand if supported, nil otherwise + */ +- (MCContentLauncherClusterLaunchURLCommand * _Nullable)launchURLCommand; +@end + +@interface MCApplicationBasicCluster : MCCluster +/** + * @brief Returns non-nil pointer to MCApplicationBasicClusterVendorIDAttribute if supported, nil otherwise + */ +- (MCApplicationBasicClusterVendorIDAttribute * _Nullable)vendorIDAttribute; +@end + +@interface MCMediaPlaybackCluster : MCCluster +/** + * @brief Returns non-nil pointer to MCMediaPlaybackClusterCurrentStateAttribute if supported, nil otherwise + */ +- (MCMediaPlaybackClusterCurrentStateAttribute * _Nullable)currentStateAttribute; +@end + +#endif /* MCClusterObjects_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCClusterObjects.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCClusterObjects.mm new file mode 100644 index 00000000000000..397dc628ffb405 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCClusterObjects.mm @@ -0,0 +1,52 @@ +/** + * + * 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. + */ + +#import "MCClusterObjects.h" + +#import "MCAttribute_Internal.h" +#import "MCCluster_Internal.h" +#import "MCCommand_Internal.h" + +#include "core/Attribute.h" +#include "core/Command.h" +#include + +#import + +@implementation MCContentLauncherCluster +- (id)launchURLCommand +{ + void * cppCommand = self.cppCluster->GetCommand(chip::app::Clusters::ContentLauncher::Commands::LaunchURL::Id); + return cppCommand != nil ? [[MCContentLauncherClusterLaunchURLCommand alloc] initWithCppCommand:cppCommand] : nil; +} +@end + +@implementation MCApplicationBasicCluster +- (id)vendorIDAttribute +{ + void * cppAttribute = self.cppCluster->GetAttribute(chip::app::Clusters::ApplicationBasic::Attributes::VendorID::Id); + return cppAttribute != nil ? [[MCApplicationBasicClusterVendorIDAttribute alloc] initWithCppAttribute:cppAttribute] : nil; +} +@end + +@implementation MCMediaPlaybackCluster +- (id)currentStateAttribute +{ + void * cppAttribute = self.cppCluster->GetAttribute(chip::app::Clusters::MediaPlayback::Attributes::CurrentState::Id); + return cppAttribute != nil ? [[MCMediaPlaybackClusterCurrentStateAttribute alloc] initWithCppAttribute:cppAttribute] : nil; +} +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCluster_Internal.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCluster_Internal.h new file mode 100644 index 00000000000000..5342d5e466109f --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCluster_Internal.h @@ -0,0 +1,35 @@ +/** + * + * 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. + */ + +#import "MCCluster.h" + +#import "core/Endpoint.h" + +#import + +#ifndef MCCluster_Internal_h +#define MCCluster_Internal_h + +@interface MCCluster () + +@property (nonatomic, readwrite) matter::casting::memory::Strong cppCluster; + +- (instancetype _Nonnull)initWithCppCluster:(matter::casting::memory::Strong)cppCluster; + +@end + +#endif /* MCCluster_Internal_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommand.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommand.h new file mode 100644 index 00000000000000..634d72a75affb5 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommand.h @@ -0,0 +1,39 @@ +/** + * + * Copyright (c) 2020-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. + */ + +#import + +#ifndef MCCommand_h +#define MCCommand_h + +@interface MCCommand : NSObject + +/** + * @brief Invokes this MCCommand on the associated MCEndpoint and corresponding MCCluster + * + * @param request request data corresponding to this command invocation + * @param completion Called when command execution completes with nil NSError if successful and responseData. On failure, the NSError describes the error + * @param timedInvokeTimeoutMs command timeout in ms + */ +- (void)invoke:(RequestType _Nonnull)request + context:(void * _Nullable)context + completion:(void (^_Nonnull)(void * _Nullable, NSError * _Nullable, ResponseType _Nullable))completion + timedInvokeTimeoutMs:(NSNumber * _Nullable)timedInvokeTimeoutMs; + +@end + +#endif /* MCCommand_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommand.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommand.mm new file mode 100644 index 00000000000000..b70945c7b022be --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommand.mm @@ -0,0 +1,50 @@ +/** + * + * 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. + */ + +#import "MCCastingApp.h" +#import "MCCommand_Internal.h" + +#import + +@implementation MCCommand + +- (instancetype _Nonnull)initWithCppCommand:(void *)cppCommand +{ + if (self = [super init]) { + _cppCommand = cppCommand; + } + return self; +} + +- (void)invoke:(id _Nonnull)request + context:(void * _Nullable)context + completion:(void (^_Nonnull __strong)(void * _Nullable, NSError * _Nullable __strong, id _Nullable __strong))completion + timedInvokeTimeoutMs:(NSNumber * _Nullable)timedInvokeTimeoutMs +{ +} + +- (std::any)getCppRequestFromObjC:(id _Nonnull)objcRequest +{ + return nil; +} + +- (id _Nullable)getObjCResponseFromCpp:(std::any)cppResponse +{ + return nil; +} + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommandObjects.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommandObjects.h new file mode 100644 index 00000000000000..c600d151febf1f --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommandObjects.h @@ -0,0 +1,60 @@ +/** + * + * Copyright (c) 2020-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. + */ + +#import "MCCommand.h" +#import + +#ifndef MCCommandObjects_h +#define MCCommandObjects_h + +@interface MCContentLauncherClusterDimensionStruct : NSObject +@property (nonatomic, copy) NSNumber * _Nonnull width; +@property (nonatomic, copy) NSNumber * _Nonnull height; +@property (nonatomic, copy) NSNumber * _Nonnull metric; +@end + +@interface MCContentLauncherClusterStyleInformationStruct : NSObject +@property (nonatomic, copy) NSString * _Nullable imageURL; +@property (nonatomic, copy) NSString * _Nullable imageUrl; +@property (nonatomic, copy) NSString * _Nullable color; +@property (nonatomic, copy) MCContentLauncherClusterDimensionStruct * _Nullable size; +@end + +@interface MCContentLauncherClusterBrandingInformationStruct : NSObject +@property (nonatomic, copy) NSString * _Nonnull providerName; +@property (nonatomic, copy) MCContentLauncherClusterStyleInformationStruct * _Nullable background; +@property (nonatomic, copy) MCContentLauncherClusterStyleInformationStruct * _Nullable logo; +@property (nonatomic, copy) MCContentLauncherClusterStyleInformationStruct * _Nullable progressBar; +@property (nonatomic, copy) MCContentLauncherClusterStyleInformationStruct * _Nullable splash; +@property (nonatomic, copy) MCContentLauncherClusterStyleInformationStruct * _Nullable waterMark; +@end + +@interface MCContentLauncherClusterLaunchURLRequest : NSObject +@property (nonatomic, copy) NSString * _Nonnull contentURL; +@property (nonatomic, copy) NSString * _Nullable displayString; +@property (nonatomic, copy) MCContentLauncherClusterBrandingInformationStruct * _Nullable brandingInformation; +@end + +@interface MCContentLauncherClusterLauncherResponse : NSObject +@property (nonatomic, copy) NSNumber * _Nonnull status; +@property (nonatomic, copy) NSString * _Nullable data; +@end + +@interface MCContentLauncherClusterLaunchURLCommand : MCCommand +@end + +#endif /* MCCommandObjects_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommandObjects.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommandObjects.mm new file mode 100644 index 00000000000000..8c37d0911668fb --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommandObjects.mm @@ -0,0 +1,132 @@ +/** + * + * 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. + */ + +#import "MCCommandObjects.h" + +#import "MCCastingApp.h" +#import "MCCommand_Internal.h" +#import "MCErrorUtils.h" + +#include "core/Command.h" +#include + +#import + +@implementation MCContentLauncherClusterLaunchURLRequest + +- (instancetype)init +{ + if (self = [super init]) { + _contentURL = @""; + _displayString = nil; + _brandingInformation = nil; + } + return self; +} + +- (id)copyWithZone:(NSZone * _Nullable)zone; +{ + auto other = [[MCContentLauncherClusterLaunchURLRequest alloc] init]; + other.contentURL = self.contentURL; + other.displayString = self.displayString; + other.brandingInformation = self.brandingInformation; + return other; +} + +- (NSString *)description +{ + NSString * descriptionString = [NSString stringWithFormat:@"<%@: contentURL:%@; displayString:%@; brandingInformation:%@; >", NSStringFromClass([self class]), _contentURL, _displayString, _brandingInformation]; + return descriptionString; +} +@end + +@implementation MCContentLauncherClusterLauncherResponse + +- (instancetype)init +{ + if (self = [super init]) { + _status = @(0); + _data = nil; + } + return self; +} + +- (id)copyWithZone:(NSZone * _Nullable)zone; +{ + auto other = [[MCContentLauncherClusterLauncherResponse alloc] init]; + + other.status = self.status; + other.data = self.data; + return other; +} + +- (instancetype)initWithDecodableStruct:(const chip::app::Clusters::ContentLauncher::Commands::LauncherResponse::DecodableType &)decodableStruct +{ + if (self = [super init]) { + _status = [NSNumber numberWithUnsignedChar:chip::to_underlying(decodableStruct.status)]; + _data = [[NSString alloc] initWithBytes:decodableStruct.data.Value().data() length:decodableStruct.data.Value().size() encoding:NSUTF8StringEncoding]; + } + return self; +} + +@end + +@implementation MCContentLauncherClusterLaunchURLCommand + +- (void)invoke:(id)request + context:(void * _Nullable)context + completion:(void (^_Nonnull __strong)(void *, NSError *, id))completion + timedInvokeTimeoutMs:(NSNumber * _Nullable)timedInvokeTimeoutMs +{ + MCCommandTemplate * mcCommand = new MCCommandTemplate( + self.cppCommand, + [self](id objCRequest) { + return [self getCppRequestFromObjC:objCRequest]; + }, + [self](std::any cppResponse) { + return [self getObjCResponseFromCpp:cppResponse]; + }); + mcCommand->invoke( + request, context, [mcCommand, completion](void * context, NSError * err, id response) { + completion(context, err, response); + delete mcCommand; + }, timedInvokeTimeoutMs); +} + +- (std::any)getCppRequestFromObjC:(MCContentLauncherClusterLaunchURLRequest *)objcRequest +{ + VerifyOrReturnValue(objcRequest != nil, nullptr); + + std::shared_ptr cppRequest = std::make_shared(); + cppRequest->contentURL = chip::CharSpan([objcRequest.contentURL UTF8String], [objcRequest.contentURL lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); + if (objcRequest.displayString != nil) { + cppRequest->displayString = chip::Optional(chip::CharSpan([objcRequest.displayString UTF8String], [objcRequest.displayString lengthOfBytesUsingEncoding:NSUTF8StringEncoding])); + } + cppRequest->brandingInformation = chip::MakeOptional(chip::app::Clusters::ContentLauncher::Structs::BrandingInformationStruct::Type()); // TODO: map brandingInformation + return std::any(cppRequest); +} + +- (id)getObjCResponseFromCpp:(std::any)cppResponse +{ + MCContentLauncherClusterLauncherResponse * objCResponse = nil; + if (cppResponse.type() == typeid(std::shared_ptr)) { + std::shared_ptr responseSharedPtr = std::any_cast>(cppResponse); + objCResponse = responseSharedPtr != nil ? [[MCContentLauncherClusterLauncherResponse alloc] initWithDecodableStruct:*responseSharedPtr] : nil; + } + return objCResponse; +} +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommand_Internal.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommand_Internal.h new file mode 100644 index 00000000000000..384004467fef30 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCCommand_Internal.h @@ -0,0 +1,98 @@ +/** + * + * 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. + */ + +#import "MCCommand.h" + +#import "MCCastingApp.h" +#import "MCErrorUtils.h" +#include "core/Command.h" +#include + +#import +#include +#include +#include + +#ifndef MCCommand_Internal_h +#define MCCommand_Internal_h + +@interface MCCommand () + +@property (nonatomic, readwrite) void * _Nonnull cppCommand; + +- (instancetype _Nonnull)initWithCppCommand:(void * _Nonnull)cppCommand; + +- (std::any)getCppRequestFromObjC:(id _Nonnull)objcRequest; + +- (id _Nullable)getObjCResponseFromCpp:(std::any)cppResponse; +@end + +template +class MCCommandTemplate { +public: + MCCommandTemplate(void * _Nonnull cppCommand, + std::function getCppRequestFromObjCFn, + std::function getObjCResponseFromCppFn) + { + mCppCommand = cppCommand; + mGetCppRequestFromObjCFn = getCppRequestFromObjCFn; + mGetObjCResponseFromCppFn = getObjCResponseFromCppFn; + } + + void invoke(id _Nonnull request, void * _Nullable context, std::function completion, NSNumber * _Nullable timedInvokeTimeoutMs) + { + dispatch_queue_t workQueue = [[MCCastingApp getSharedInstance] getWorkQueue], clientQueue = [[MCCastingApp getSharedInstance] getClientQueue]; + dispatch_sync(workQueue, ^{ + ChipLogProgress(AppServer, " converting 'request' from ObjC to Cpp"); + std::shared_ptr cppRequest = nil; + std::any anyRequest = mGetCppRequestFromObjCFn(request); + if (anyRequest.type() == typeid(std::shared_ptr)) { + cppRequest = std::any_cast>(anyRequest); + } + if (cppRequest == nil) { + dispatch_async(clientQueue, ^{ + completion(context, [MCErrorUtils NSErrorFromChipError:CHIP_ERROR_INVALID_ARGUMENT], nil); + }); + return; + } + + matter::casting::core::Command * command = static_cast *>(mCppCommand); + command->Invoke( + *cppRequest, context, + [clientQueue, completion, this](void * context, const typename Type::ResponseType & response) { + ChipLogProgress(AppServer, " converting 'response' from Cpp to ObjC"); + id objCResponse = mGetObjCResponseFromCppFn(std::any(std::make_shared(response))); + dispatch_async(clientQueue, ^{ + completion(context, nil, objCResponse); + }); + }, + [clientQueue, completion](void * context, CHIP_ERROR err) { + dispatch_async(clientQueue, ^{ + completion(context, [MCErrorUtils NSErrorFromChipError:err], nil); + }); + }, + timedInvokeTimeoutMs != nil ? chip::MakeOptional(timedInvokeTimeoutMs.intValue) : chip::NullOptional); + }); + } + +private: + void * _Nonnull mCppCommand; + std::function mGetCppRequestFromObjCFn; + std::function mGetObjCResponseFromCppFn; +}; + +#endif /* MCCommand_Internal_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCDeviceTypeStruct.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCDeviceTypeStruct.h new file mode 100644 index 00000000000000..e893f65ecbf7a5 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCDeviceTypeStruct.h @@ -0,0 +1,33 @@ +/** + * + * Copyright (c) 2020-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. + */ + +#import + +#ifndef MCDeviceTypeStruct_h +#define MCDeviceTypeStruct_h + +@interface MCDeviceTypeStruct : NSObject + +@property (nonatomic, strong, readonly) NSNumber * _Nonnull deviceType; + +@property (nonatomic, strong, readonly) NSNumber * _Nonnull revision; + +- (instancetype _Nonnull)initWithDeviceType:(uint32_t)deviceType revision:(uint16_t)revision; + +@end + +#endif /* MCDeviceTypeStruct_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCDeviceTypeStruct.m b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCDeviceTypeStruct.m new file mode 100644 index 00000000000000..d50e81a83f09e1 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCDeviceTypeStruct.m @@ -0,0 +1,31 @@ +/** + * + * 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. + */ + +#import "MCDeviceTypeStruct.h" + +@implementation MCDeviceTypeStruct + +- (instancetype _Nonnull)initWithDeviceType:(uint32_t)deviceType revision:(uint16_t)revision +{ + if (self = [super init]) { + _deviceType = [NSNumber numberWithUnsignedInt:deviceType]; + _revision = [NSNumber numberWithUnsignedShort:revision]; + } + return self; +} + +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.h new file mode 100644 index 00000000000000..d252bd3c92f3e1 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.h @@ -0,0 +1,46 @@ +/** + * + * Copyright (c) 2020-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 "MCCastingPlayer.h" +#import "MCCluster.h" +#import "MCEndpointClusterType.h" + +#import + +#ifndef MCEndpoint_h +#define MCEndpoint_h + +@class MCCastingPlayer; +@class MCCluster; + +@interface MCEndpoint : NSObject + +- (NSNumber * _Nonnull)identifier; +- (NSNumber * _Nonnull)vendorId; +- (NSNumber * _Nonnull)productId; +- (NSArray * _Nonnull)deviceTypeList; +- (MCCastingPlayer * _Nonnull)castingPlayer; + +- (nonnull instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (nonnull instancetype)new UNAVAILABLE_ATTRIBUTE; + +- (BOOL)hasCluster:(MCEndpointClusterType)type; +- (MCCluster * _Nullable)clusterForType:(MCEndpointClusterType)type; + +@end + +#endif /* MCEndpoint_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.mm new file mode 100644 index 00000000000000..006488aed03eb5 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint.mm @@ -0,0 +1,133 @@ +/** + * + * 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 "MCEndpoint_Internal.h" + +#import "MCCastingPlayer_Internal.h" +#import "MCCluster_Internal.h" +#import "MCDeviceTypeStruct.h" + +#import "MCClusterObjects.h" + +#import "clusters/Clusters.h" +#import "core/Endpoint.h" + +#import + +@interface MCEndpoint () + +@property (nonatomic, readwrite) matter::casting::memory::Strong cppEndpoint; + +@end + +@implementation MCEndpoint + +- (instancetype _Nonnull)initWithCppEndpoint:(matter::casting::memory::Strong)cppEndpoint +{ + if (self = [super init]) { + _cppEndpoint = cppEndpoint; + } + return self; +} + +- (NSNumber * _Nonnull)identifier +{ + return [NSNumber numberWithUnsignedShort:_cppEndpoint->GetId()]; +} + +- (NSNumber * _Nonnull)productId +{ + return [NSNumber numberWithUnsignedShort:_cppEndpoint->GetProductId()]; +} + +- (NSNumber * _Nonnull)vendorId +{ + return [NSNumber numberWithUnsignedShort:_cppEndpoint->GetVendorId()]; +} + +- (NSArray * _Nonnull)deviceTypeList +{ + NSMutableArray * deviceTypeList = [NSMutableArray new]; + std::vector cppDeviceTypeList = _cppEndpoint->GetDeviceTypeList(); + for (chip::app::Clusters::Descriptor::Structs::DeviceTypeStruct::DecodableType cppDeviceTypeStruct : cppDeviceTypeList) { + MCDeviceTypeStruct * deviceTypeStruct = [[MCDeviceTypeStruct alloc] initWithDeviceType:cppDeviceTypeStruct.deviceType revision:cppDeviceTypeStruct.revision]; + [deviceTypeList addObject:deviceTypeStruct]; + } + return deviceTypeList; +} + +- (MCCastingPlayer * _Nonnull)castingPlayer +{ + return [[MCCastingPlayer alloc] initWithCppCastingPlayer:std::shared_ptr(_cppEndpoint->GetCastingPlayer())]; +} + +- (MCCluster * _Nullable)clusterForType:(MCEndpointClusterType)type +{ + switch (type) { + case MCEndpointClusterTypeContentLauncher: + return [[MCContentLauncherCluster alloc] initWithCppCluster:_cppEndpoint->GetCluster()]; + + case MCEndpointClusterTypeApplicationBasic: + return [[MCApplicationBasicCluster alloc] initWithCppCluster:_cppEndpoint->GetCluster()]; + + case MCEndpointClusterTypeMediaPlayback: + return [[MCMediaPlaybackCluster alloc] initWithCppCluster:_cppEndpoint->GetCluster()]; + + default: + ChipLogError(AppServer, "MCEndpointClusterType not found"); + break; + } + return nil; +} + +- (BOOL)hasCluster:(MCEndpointClusterType)type +{ + return [self clusterForType:type] != nil; +} + +- (BOOL)isEqualToMCEndpoint:(MCEndpoint * _Nullable)other +{ + return [self.identifier isEqualToNumber:other.identifier]; +} + +- (BOOL)isEqual:(id _Nullable)other +{ + if (other == nil) { + return NO; + } + + if (self == other) { + return YES; + } + + if (![other isKindOfClass:[MCEndpoint class]]) { + return NO; + } + + return [self isEqualToMCEndpoint:(MCEndpoint *) other]; +} + +- (NSUInteger)hash +{ + const NSUInteger prime = 31; + NSUInteger result = 1; + + result = prime * result + [self.identifier hash]; + + return result; +} +@end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpointClusterType.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpointClusterType.h new file mode 100644 index 00000000000000..7137dd1ed96042 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpointClusterType.h @@ -0,0 +1,28 @@ +/** + * + * Copyright (c) 2020-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. + */ + +#ifndef MCEndpointClusterType_h +#define MCEndpointClusterType_h + +typedef enum _MCEndpointClusterType +{ + MCEndpointClusterTypeApplicationBasic, + MCEndpointClusterTypeContentLauncher, + MCEndpointClusterTypeMediaPlayback +} MCEndpointClusterType; + +#endif /* MCEndpointClusterType_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint_Internal.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint_Internal.h new file mode 100644 index 00000000000000..53b1d6bf22218e --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCEndpoint_Internal.h @@ -0,0 +1,33 @@ +/** + * + * 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. + */ + +#import "MCEndpoint.h" + +#import "core/Endpoint.h" + +#import + +#ifndef MCEndpoint_Internal_h +#define MCEndpoint_Internal_h + +@interface MCEndpoint () + +- (instancetype _Nonnull)initWithCppEndpoint:(matter::casting::memory::Strong)cppEndpoint; + +@end + +#endif /* MCEndpoint_Internal_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCObserver.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCObserver.h new file mode 100644 index 00000000000000..a1ca48f8b87616 --- /dev/null +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MCObserver.h @@ -0,0 +1,29 @@ +/** + * + * Copyright (c) 2020-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. + */ + +#import + +#ifndef MCObserver_h +#define MCObserver_h + +@protocol MCObserver + +- (void)attribute:(NSObject * _Nonnull)sender valueDidChange:(NSValue * _Nullable)value oldValue:(NSValue * _Nullable)oldValue; + +@end + +#endif /* MCObserver_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h index 9329bc657b9c32..093f02b9455b0d 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/MatterTvCastingBridge.h @@ -26,11 +26,19 @@ FOUNDATION_EXPORT const unsigned char MatterTvCastingBridgeVersionString[]; #import "CastingServerBridge.h" // Add simplified casting API headers here +#import "MCAttribute.h" +#import "MCAttributeObjects.h" #import "MCCastingApp.h" #import "MCCastingPlayer.h" #import "MCCastingPlayerDiscovery.h" +#import "MCCluster.h" +#import "MCClusterObjects.h" +#import "MCCommand.h" +#import "MCCommandObjects.h" #import "MCCommissionableData.h" #import "MCCryptoUtils.h" #import "MCDataSource.h" #import "MCDeviceAttestationCredentials.h" +#import "MCEndpointClusterType.h" #import "MCEndpointFilter.h" +#import "MCObserver.h" diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/project.pbxproj b/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/project.pbxproj index 1fd70f6b3f48fb..e7005121a06352 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/project.pbxproj +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/project.pbxproj @@ -7,6 +7,13 @@ objects = { /* Begin PBXBuildFile section */ + 3C40586E2B632DC500C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C40586D2B632DC500C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleView.swift */; }; + 3C4058702B632DDB00C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C40586F2B632DDB00C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift */; }; + 3C4F52302B51F32000BB8A10 /* MCContentLauncherLaunchURLExampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4F522F2B51F32000BB8A10 /* MCContentLauncherLaunchURLExampleViewModel.swift */; }; + 3C4F52322B5721D000BB8A10 /* MCContentLauncherLaunchURLExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4F52312B5721D000BB8A10 /* MCContentLauncherLaunchURLExampleView.swift */; }; + 3C621CB12B6078A9005CDBA3 /* MCApplicationBasicReadVendorIDExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C621CB02B6078A9005CDBA3 /* MCApplicationBasicReadVendorIDExampleView.swift */; }; + 3C621CB32B6078B7005CDBA3 /* MCApplicationBasicReadVendorIDExampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C621CB22B6078B7005CDBA3 /* MCApplicationBasicReadVendorIDExampleViewModel.swift */; }; + 3C621CB52B607FFD005CDBA3 /* MCActionSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C621CB42B607FFD005CDBA3 /* MCActionSelectorView.swift */; }; 3C69204A2AA1368F00D0F613 /* MCInitializationExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6920492AA1368F00D0F613 /* MCInitializationExample.swift */; }; 3C81C75328F8C79E001CB9D1 /* StartFromCacheView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C81C75228F8C79E001CB9D1 /* StartFromCacheView.swift */; }; 3C81C75528F8C7B6001CB9D1 /* StartFromCacheViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C81C75428F8C7B6001CB9D1 /* StartFromCacheViewModel.swift */; }; @@ -51,6 +58,13 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 3C40586D2B632DC500C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCMediaPlaybackSubscribeToCurrentStateExampleView.swift; sourceTree = ""; }; + 3C40586F2B632DDB00C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift; sourceTree = ""; }; + 3C4F522F2B51F32000BB8A10 /* MCContentLauncherLaunchURLExampleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCContentLauncherLaunchURLExampleViewModel.swift; sourceTree = ""; }; + 3C4F52312B5721D000BB8A10 /* MCContentLauncherLaunchURLExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCContentLauncherLaunchURLExampleView.swift; sourceTree = ""; }; + 3C621CB02B6078A9005CDBA3 /* MCApplicationBasicReadVendorIDExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCApplicationBasicReadVendorIDExampleView.swift; sourceTree = ""; }; + 3C621CB22B6078B7005CDBA3 /* MCApplicationBasicReadVendorIDExampleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCApplicationBasicReadVendorIDExampleViewModel.swift; sourceTree = ""; }; + 3C621CB42B607FFD005CDBA3 /* MCActionSelectorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCActionSelectorView.swift; sourceTree = ""; }; 3C6920492AA1368F00D0F613 /* MCInitializationExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MCInitializationExample.swift; sourceTree = ""; }; 3C75075E284C1DF800D7DB3A /* TvCasting.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TvCasting.entitlements; sourceTree = ""; }; 3C7507AC285299DF00D7DB3A /* CommissionerDiscoveryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommissionerDiscoveryView.swift; sourceTree = ""; }; @@ -131,6 +145,13 @@ 3C94377C2B364D380096E5F4 /* MCDiscoveryExampleViewModel.swift */, 3C94378F2B3B3FF90096E5F4 /* MCConnectionExampleView.swift */, 3C94377E2B364D510096E5F4 /* MCConnectionExampleViewModel.swift */, + 3C621CB42B607FFD005CDBA3 /* MCActionSelectorView.swift */, + 3C4F52312B5721D000BB8A10 /* MCContentLauncherLaunchURLExampleView.swift */, + 3C4F522F2B51F32000BB8A10 /* MCContentLauncherLaunchURLExampleViewModel.swift */, + 3C621CB02B6078A9005CDBA3 /* MCApplicationBasicReadVendorIDExampleView.swift */, + 3C621CB22B6078B7005CDBA3 /* MCApplicationBasicReadVendorIDExampleViewModel.swift */, + 3C40586D2B632DC500C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleView.swift */, + 3C40586F2B632DDB00C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift */, EAF14298296D561900E17793 /* CertTestView.swift */, EAF1429A296D57DF00E17793 /* CertTestViewModel.swift */, 3CC0E8FB2841DD3400EC6A18 /* ContentView.swift */, @@ -248,9 +269,14 @@ files = ( 3C81C75328F8C79E001CB9D1 /* StartFromCacheView.swift in Sources */, 3C94377F2B364D510096E5F4 /* MCConnectionExampleViewModel.swift in Sources */, + 3C4058702B632DDB00C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift in Sources */, 3C81C75528F8C7B6001CB9D1 /* StartFromCacheViewModel.swift in Sources */, 3CCB8745286A5D0F00771BAD /* CommissionerDiscoveryView.swift in Sources */, + 3C621CB12B6078A9005CDBA3 /* MCApplicationBasicReadVendorIDExampleView.swift in Sources */, 3CCB8746286A5D0F00771BAD /* CommissionerDiscoveryViewModel.swift in Sources */, + 3C4F52302B51F32000BB8A10 /* MCContentLauncherLaunchURLExampleViewModel.swift in Sources */, + 3C621CB32B6078B7005CDBA3 /* MCApplicationBasicReadVendorIDExampleViewModel.swift in Sources */, + 3C621CB52B607FFD005CDBA3 /* MCActionSelectorView.swift in Sources */, 3C81C75928F8E42D001CB9D1 /* ConnectionViewModel.swift in Sources */, 3CA1CA7A28E281080023ED44 /* ClusterSelectorView.swift in Sources */, 3C94378E2B3B3CB00096E5F4 /* MCDiscoveryExampleView.swift in Sources */, @@ -259,6 +285,7 @@ 3C94377D2B364D380096E5F4 /* MCDiscoveryExampleViewModel.swift in Sources */, 3CCB8747286A5D0F00771BAD /* CommissioningView.swift in Sources */, 3CCB8748286A5D0F00771BAD /* CommissioningViewModel.swift in Sources */, + 3C4F52322B5721D000BB8A10 /* MCContentLauncherLaunchURLExampleView.swift in Sources */, 3CAC955B29BA948700BEA5C3 /* ExampleDAC.swift in Sources */, 3CA1CA7E28E284950023ED44 /* MediaPlaybackViewModel.swift in Sources */, 3CCB8749286A5D0F00771BAD /* ContentLauncherView.swift in Sources */, @@ -268,6 +295,7 @@ 3CC0E8FC2841DD3400EC6A18 /* ContentView.swift in Sources */, 3CA1CA7C28E282150023ED44 /* MediaPlaybackView.swift in Sources */, 3CC0E8FA2841DD3400EC6A18 /* TvCastingApp.swift in Sources */, + 3C40586E2B632DC500C7C6D6 /* MCMediaPlaybackSubscribeToCurrentStateExampleView.swift in Sources */, 3C9437902B3B3FF90096E5F4 /* MCConnectionExampleView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/xcshareddata/xcschemes/TvCasting Release.xcscheme b/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/xcshareddata/xcschemes/TvCasting Release.xcscheme index 05333fbe6fe932..e769afbac5e4a5 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/xcshareddata/xcschemes/TvCasting Release.xcscheme +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/xcshareddata/xcschemes/TvCasting Release.xcscheme @@ -53,7 +53,7 @@ diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/xcshareddata/xcschemes/TvCasting.xcscheme b/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/xcshareddata/xcschemes/TvCasting.xcscheme index 2bdbabd75539cb..c64d5160489c18 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/xcshareddata/xcschemes/TvCasting.xcscheme +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting.xcodeproj/xcshareddata/xcschemes/TvCasting.xcscheme @@ -53,7 +53,7 @@ diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCActionSelectorView.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCActionSelectorView.swift new file mode 100644 index 00000000000000..1ddc8d9539f554 --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCActionSelectorView.swift @@ -0,0 +1,72 @@ +/** + * + * Copyright (c) 2020-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. + */ + +import SwiftUI + +struct MCActionSelectorView: View { + var selectedCastingPlayer: MCCastingPlayer? + + init(_selectedCastingPlayer: MCCastingPlayer?) { + self.selectedCastingPlayer = _selectedCastingPlayer + } + + var body: some View { + VStack(alignment: .leading) { + NavigationLink( + destination: MCContentLauncherLaunchURLExampleView(_selectedCastingPlayer: self.selectedCastingPlayer), + label: { + Text("ContentLauncher Launch URL") + .frame(width: 300, height: 30, alignment: .center) + .border(Color.black, width: 1) + } + ).background(Color.blue) + .foregroundColor(Color.white) + .padding() + + NavigationLink( + destination: MCApplicationBasicReadVendorIDExampleView(_selectedCastingPlayer: self.selectedCastingPlayer), + label: { + Text("ApplicationBasic Read VendorID") + .frame(width: 300, height: 30, alignment: .center) + .border(Color.black, width: 1) + } + ).background(Color.blue) + .foregroundColor(Color.white) + .padding() + + NavigationLink( + destination: MCMediaPlaybackSubscribeToCurrentStateExampleView(_selectedCastingPlayer: self.selectedCastingPlayer), + label: { + Text("MediaPlayback Subscribe to CurrentState") + .frame(width: 300, height: 30, alignment: .center) + .border(Color.black, width: 1) + } + ).background(Color.blue) + .foregroundColor(Color.white) + .padding() + + } + .navigationTitle("Select an action") + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top) + } +} + +struct MCActionSelectorView_Previews: PreviewProvider { + static var previews: some View { + MCActionSelectorView(_selectedCastingPlayer: nil) + } +} diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCApplicationBasicReadVendorIDExampleView.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCApplicationBasicReadVendorIDExampleView.swift new file mode 100644 index 00000000000000..7a84abf1ab0ce4 --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCApplicationBasicReadVendorIDExampleView.swift @@ -0,0 +1,52 @@ +/** + * + * Copyright (c) 2020-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. + */ + +import SwiftUI + +struct MCApplicationBasicReadVendorIDExampleView: View { + @StateObject var viewModel = MCApplicationBasicReadVendorIDExampleViewModel() + + var selectedCastingPlayer: MCCastingPlayer? + + init(_selectedCastingPlayer: MCCastingPlayer?) { + self.selectedCastingPlayer = _selectedCastingPlayer + } + + var body: some View { + VStack(alignment: .leading) + { + Button("Read VendorID!") { + viewModel.read(castingPlayer: self.selectedCastingPlayer!) + } + .background(Color.blue) + .foregroundColor(Color.white) + .cornerRadius(4) + .border(Color.black, width: 1) + .padding() + + Text(viewModel.status ?? "") + } + .navigationTitle("ApplicationBasic Read VendorID") + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top) + } +} + +struct MCApplicationBasicReadVendorIDExampleView_Previews: PreviewProvider { + static var previews: some View { + MCApplicationBasicReadVendorIDExampleView(_selectedCastingPlayer: nil) + } +} diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCApplicationBasicReadVendorIDExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCApplicationBasicReadVendorIDExampleViewModel.swift new file mode 100644 index 00000000000000..32e20e4b324ca3 --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCApplicationBasicReadVendorIDExampleViewModel.swift @@ -0,0 +1,96 @@ +/** + * + * Copyright (c) 2020-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. + */ + + +import Foundation +import os.log + +class MCApplicationBasicReadVendorIDExampleViewModel: ObservableObject { + let Log = Logger(subsystem: "com.matter.casting", + category: "MCApplicationBasicReadVendorIDExampleViewModel") + + @Published var status: String?; + + // VendorId of the MCEndpoint on the MCCastingPlayer that the MCCastingApp desires to interact with after connection + let sampleEndpointVid: Int = 65521 + + func read(castingPlayer: MCCastingPlayer) + { + // select the MCEndpoint on the MCCastingPlayer to invoke the command on + if let endpoint: MCEndpoint = castingPlayer.endpoints().filter({ $0.vendorId().intValue == sampleEndpointVid}).first + { + // validate that the selected endpoint supports the ApplicationBasic cluster + if(!endpoint.hasCluster(MCEndpointClusterTypeApplicationBasic)) + { + self.Log.error("No ApplicationBasic cluster supporting endpoint found") + DispatchQueue.main.async + { + self.status = "No ApplicationBasic cluster supporting endpoint found" + } + return + } + + // get ApplicationBasic cluster from the endpoint + let applicationBasiccluster: MCApplicationBasicCluster = endpoint.cluster(for: MCEndpointClusterTypeApplicationBasic) as! MCApplicationBasicCluster + + // get the vendorIDAttribute from the applicationBasiccluster + let vendorIDAttribute: MCApplicationBasicClusterVendorIDAttribute? = applicationBasiccluster.vendorIDAttribute() + if(vendorIDAttribute == nil) + { + self.Log.error("VendorID attribute not supported on cluster") + DispatchQueue.main.async + { + self.status = "VendorID attribute not supported on cluster" + } + return + } + + + // call read on vendorIDAttribute and pass in a completion block + vendorIDAttribute!.read(nil) { context, before, after, err in + DispatchQueue.main.async + { + if(err != nil) + { + self.Log.error("Error when reading VendorID value \(String(describing: err))") + self.status = "Error when reading VendorID value \(String(describing: err))" + return + } + + if(before != nil) + { + self.Log.info("Read VendorID value: \(String(describing: after)) Before: \(String(describing: before))") + self.status = "Read VendorID value: \(String(describing: after)) Before: \(String(describing: before))" + } + else + { + self.Log.info("Read VendorID value: \(String(describing: after))") + self.status = "Read VendorID value: \(String(describing: after))" + } + } + } + } + else + { + self.Log.error("No endpoint matching the example VID found") + DispatchQueue.main.async + { + self.status = "No endpoint matching the example VID found" + } + } + } +} diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift index 0dd6d302f7b968..87448bf46b4790 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleView.swift @@ -39,9 +39,8 @@ struct MCConnectionExampleView: View { if(connectionSuccess) { - /* TODO add this back in - NavigationLink( - destination: ClusterSelectorView(), + NavigationLink( + destination: MCActionSelectorView(_selectedCastingPlayer: self.selectedCastingPlayer), label: { Text("Next") .frame(width: 100, height: 30, alignment: .center) @@ -50,7 +49,7 @@ struct MCConnectionExampleView: View { ).background(Color.blue) .foregroundColor(Color.white) .frame(maxHeight: .infinity, alignment: .bottom) - .padding()*/ + .padding() } } } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift index 9d130b5e7737ee..13cdad3d8e60f1 100644 --- a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCConnectionExampleViewModel.swift @@ -35,15 +35,18 @@ class MCConnectionExampleViewModel: ObservableObject { desiredEndpointFilter.vendorId = kDesiredEndpointVendorId selectedCastingPlayer?.verifyOrEstablishConnection(completionBlock: { err in self.Log.error("MCConnectionExampleViewModel connect() completed with \(err)") - if(err == nil) + DispatchQueue.main.async { - self.connectionSuccess = true - self.connectionStatus = "Connected!" - } - else - { - self.connectionSuccess = false - self.connectionStatus = "Connection failed with \(String(describing: err))" + if(err == nil) + { + self.connectionSuccess = true + self.connectionStatus = "Connected!" + } + else + { + self.connectionSuccess = false + self.connectionStatus = "Connection failed with \(String(describing: err))" + } } }, desiredEndpointFilter: desiredEndpointFilter) } diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleView.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleView.swift new file mode 100644 index 00000000000000..520ea86804e765 --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleView.swift @@ -0,0 +1,79 @@ +/** + * + * Copyright (c) 2020-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. + */ + +import SwiftUI + +struct MCContentLauncherLaunchURLExampleView : View { + @StateObject var viewModel = MCContentLauncherLaunchURLExampleViewModel() + + var selectedCastingPlayer: MCCastingPlayer? + + @State private var contentUrl: String = "" + @State private var displayString: String = "" + + init(_selectedCastingPlayer: MCCastingPlayer?) { + self.selectedCastingPlayer = _selectedCastingPlayer + } + + var body: some View { + VStack(alignment: .leading) { + + HStack() { + Text("Content URL") + + TextField( + "https://www.test.com/videoid", + text: $contentUrl + ) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .border(.secondary) + } + + HStack() { + Text("Display string") + + TextField( + "Test video", + text: $displayString + ) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .border(.secondary) + } + + Button("Invoke Launch URL!") { + viewModel.invokeCommand(castingPlayer: self.selectedCastingPlayer!, contentUrl: contentUrl, displayString: displayString) + } + .background(Color.blue) + .foregroundColor(Color.white) + .cornerRadius(4) + .border(Color.black, width: 1) + .padding() + + Text(viewModel.status ?? "") + } + .navigationTitle("Content Launcher Launch URL") + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top) + } +} + +struct MCCommandInvocationExampleView_Previews: PreviewProvider { + static var previews: some View { + MCContentLauncherLaunchURLExampleView(_selectedCastingPlayer: nil) + } +} diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift new file mode 100644 index 00000000000000..b74a91a77b7286 --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCContentLauncherLaunchURLExampleViewModel.swift @@ -0,0 +1,94 @@ +/** + * + * Copyright (c) 2020-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. + */ + + +import Foundation +import os.log + +class MCContentLauncherLaunchURLExampleViewModel: ObservableObject { + let Log = Logger(subsystem: "com.matter.casting", + category: "MCContentLauncherLaunchURLExampleViewModel") + + @Published var status: String?; + + // VendorId of the MCEndpoint on the MCCastingPlayer that the MCCastingApp desires to interact with after connection + let sampleEndpointVid: Int = 65521 + + func invokeCommand(castingPlayer: MCCastingPlayer, contentUrl: String, displayString: String) + { + // select the MCEndpoint on the MCCastingPlayer to invoke the command on + if let endpoint: MCEndpoint = castingPlayer.endpoints().filter({ $0.vendorId().intValue == sampleEndpointVid}).first + { + // validate that the selected endpoint supports the ContentLauncher cluster + if(!endpoint.hasCluster(MCEndpointClusterTypeContentLauncher)) + { + self.Log.error("No ContentLauncher cluster supporting endpoint found") + DispatchQueue.main.async + { + self.status = "No ContentLauncher cluster supporting endpoint found" + } + return + } + + // get ContentLauncher cluster from the endpoint + let contentLaunchercluster: MCContentLauncherCluster = endpoint.cluster(for: MCEndpointClusterTypeContentLauncher) as! MCContentLauncherCluster + + // get the launchURLCommand from the contentLauncherCluster + let launchURLCommand: MCContentLauncherClusterLaunchURLCommand? = contentLaunchercluster.launchURLCommand() + if(launchURLCommand == nil) + { + self.Log.error("LaunchURL not supported on cluster") + DispatchQueue.main.async + { + self.status = "LaunchURL not supported on cluster" + } + return + } + + // create the LaunchURL request + let request: MCContentLauncherClusterLaunchURLRequest = MCContentLauncherClusterLaunchURLRequest() + request.contentURL = contentUrl + request.displayString = displayString + + // call invoke on launchURLCommand while passing in a completion block + launchURLCommand!.invoke(request, context: nil, completion: { context, err, response in + DispatchQueue.main.async + { + if(err == nil) + { + self.Log.info("LaunchURLCommand invoke completion success with \(String(describing: response))") + self.status = "Success. Response data: \(String(describing: response?.data))" + } + else + { + self.Log.error("LaunchURLCommand invoke completion failure with \(String(describing: err))") + self.status = "Failure: \(String(describing: err))" + } + } + }, + timedInvokeTimeoutMs: 5000) // time out after 5000ms + } + else + { + self.Log.error("No endpoint matching the example VID found") + DispatchQueue.main.async + { + self.status = "No endpoint matching the example VID found" + } + } + } +} diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleView.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleView.swift new file mode 100644 index 00000000000000..f99ea71cd0875b --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleView.swift @@ -0,0 +1,52 @@ +/** + * + * Copyright (c) 2020-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. + */ + +import SwiftUI + +struct MCMediaPlaybackSubscribeToCurrentStateExampleView: View { + @StateObject var viewModel = MCMediaPlaybackSubscribeToCurrentStateExampleViewModel() + + var selectedCastingPlayer: MCCastingPlayer? + + init(_selectedCastingPlayer: MCCastingPlayer?) { + self.selectedCastingPlayer = _selectedCastingPlayer + } + + var body: some View { + VStack(alignment: .leading) + { + Button("Subscribe to CurrentState!") { + viewModel.subscribe(castingPlayer: self.selectedCastingPlayer!) + } + .background(Color.blue) + .foregroundColor(Color.white) + .cornerRadius(4) + .border(Color.black, width: 1) + .padding() + + Text(viewModel.status ?? "") + } + .navigationTitle("MediaPlayback Subscribe to CurrentState") + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top) + } +} + +struct MCMediaPlaybackSubscribeToCurrentStateExampleView_Previews: PreviewProvider { + static var previews: some View { + MCMediaPlaybackSubscribeToCurrentStateExampleView(_selectedCastingPlayer: nil) + } +} diff --git a/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift new file mode 100644 index 00000000000000..5d30f096052a84 --- /dev/null +++ b/examples/tv-casting-app/darwin/TvCasting/TvCasting/MCMediaPlaybackSubscribeToCurrentStateExampleViewModel.swift @@ -0,0 +1,99 @@ +/** + * + * Copyright (c) 2020-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. + */ + + +import Foundation +import os.log + +class MCMediaPlaybackSubscribeToCurrentStateExampleViewModel: ObservableObject { + let Log = Logger(subsystem: "com.matter.casting", + category: "MCMediaPlaybackSubscribeToCurrentStateExampleViewModel") + + @Published var status: String?; + + // VendorId of the MCEndpoint on the MCCastingPlayer that the MCCastingApp desires to interact with after connection + let sampleEndpointVid: Int = 65521 + + func subscribe(castingPlayer: MCCastingPlayer) + { + // select the MCEndpoint on the MCCastingPlayer to invoke the command on + if let endpoint: MCEndpoint = castingPlayer.endpoints().filter({ $0.vendorId().intValue == sampleEndpointVid}).first + { + // validate that the selected endpoint supports the MediaPlayback cluster + if(!endpoint.hasCluster(MCEndpointClusterTypeMediaPlayback)) + { + self.Log.error("No MediaPlayback cluster supporting endpoint found") + DispatchQueue.main.async + { + self.status = "No MediaPlayback cluster supporting endpoint found" + } + return + } + + // get MediaPlayback cluster from the endpoint + let mediaPlaybackCluster: MCMediaPlaybackCluster = endpoint.cluster(for: MCEndpointClusterTypeMediaPlayback) as! MCMediaPlaybackCluster + + // get the currentStateAttribute from the mediaPlaybackCluster + let currentStateAttribute: MCMediaPlaybackClusterCurrentStateAttribute? = mediaPlaybackCluster.currentStateAttribute() + if(currentStateAttribute == nil) + { + self.Log.error("CurrentState attribute not supported on cluster") + DispatchQueue.main.async + { + self.status = "CurrentState attribute not supported on cluster" + } + return + } + + + // call read on currentStateAttribute and pass in a completion block + currentStateAttribute!.read(nil) { context, before, after, err in + + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "HH:mm:ss" + let currentTime = dateFormatter.string(from: Date()) + DispatchQueue.main.async + { + if(err != nil) + { + self.Log.error("Error when reading CurrentState value \(String(describing: err)) at \(currentTime)") + self.status = "Error when reading CurrentState value \(String(describing: err)) at \(currentTime)" + return + } + if(before != nil) + { + self.Log.info("Read CurrentState value: \(String(describing: after)) Before: \(String(describing: before)) at \(currentTime)") + self.status = "Read CurrentState value: \(String(describing: after)) Before: \(String(describing: before)) at \(currentTime)" + } + else + { + self.Log.info("Read CurrentState value: \(String(describing: after)) at \(currentTime)") + self.status = "Read CurrentState value: \(String(describing: after)) at \(currentTime)" + } + } + } + } + else + { + self.Log.error("No endpoint matching the example VID found") + DispatchQueue.main.async + { + self.status = "No endpoint matching the example VID found" + } + } + } +} diff --git a/examples/tv-casting-app/tv-casting-common/core/Attribute.h b/examples/tv-casting-app/tv-casting-common/core/Attribute.h index 08296135afa070..6ce2aa046680ad 100644 --- a/examples/tv-casting-app/tv-casting-common/core/Attribute.h +++ b/examples/tv-casting-app/tv-casting-common/core/Attribute.h @@ -67,8 +67,7 @@ class Attribute /** * @brief Reads the value of the Attribute that belongs to the associated Endpoint and corresponding Cluster - * - * @param context + * @param context current context passed back in successCb/FailureCb * @param successCb Called when the Attribute is read successfully, with the value of the attribute after reading, as well as * before (if the Attribute had been previously read) * @param failureCb Called when there is a failure in reading the Attribute @@ -159,7 +158,7 @@ class Attribute * @brief Writes the value of the Attribute to an associated Endpoint and corresponding Cluster * * @param requestData value of the Attribute to be written - * @param context + * @param context current context passed back in successCb/FailureCb * @param successCb Called when the Attribute is written successfully * @param failureCb Called when there is a failure in writing the Attribute * @param aTimedWriteTimeoutMs write timeout @@ -239,12 +238,12 @@ class Attribute /** * @brief Subscribes to the value of the Attribute that belongs to the associated Endpoint and corresponding Cluster * - * @param context + * @param context current context passed back in successCb/FailureCb * @param successCb Called when the Attribute is read successfully, with the value of the attribute after reading, as well as * before (if the Attribute had been previously read) * @param failureCb Called when there is a failure in reading the Attribute - * @param minIntervalFloorSeconds - * @param maxIntervalCeilingSeconds + * @param minIntervalFloorSeconds the requested minimum interval boundary floor in seconds for attribute udpates + * @param maxIntervalCeilingSeconds the requested maximum interval boundary ceiling in seconds for attribute udpates */ void Subscribe(void * context, ReadResponseSuccessCallbackFn successCb, ReadResponseFailureCallbackFn failureCb, uint16_t minIntervalFloorSeconds, uint16_t maxIntervalCeilingSeconds) diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingApp.h b/examples/tv-casting-app/tv-casting-common/core/CastingApp.h index ed26e24709e6c5..f286aad3cbde7f 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingApp.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingApp.h @@ -47,7 +47,7 @@ class CastingApp /** * @brief Initializes the CastingApp with appParameters * - * @param appParameters + * @param appParameters AppParameters required to Start up the CastingApp * @return CHIP_ERROR */ CHIP_ERROR Initialize(const matter::casting::support::AppParameters & appParameters); diff --git a/examples/tv-casting-app/tv-casting-common/core/Command.h b/examples/tv-casting-app/tv-casting-common/core/Command.h index 3075053b98493a..b3e3e44ec50457 100644 --- a/examples/tv-casting-app/tv-casting-common/core/Command.h +++ b/examples/tv-casting-app/tv-casting-common/core/Command.h @@ -43,7 +43,6 @@ class Command * @brief Invokes this command on the associated Endpoint and corresponding Cluster * * @param request request data corresponding to this command invocation - * @param context * @param successCb Called on command execution success, with responseData * @param failureCb Called on command execution failure * @param timedInvokeTimeoutMs command timeout diff --git a/examples/tv-casting-app/tv-casting-common/core/Endpoint.h b/examples/tv-casting-app/tv-casting-common/core/Endpoint.h index 006fde019dd182..4da8cd56210b07 100644 --- a/examples/tv-casting-app/tv-casting-common/core/Endpoint.h +++ b/examples/tv-casting-app/tv-casting-common/core/Endpoint.h @@ -46,6 +46,7 @@ class EndpointAttributes }; class CastingPlayer; +class BaseCluster; /** * @brief An Endpoint on a CastingPlayer e.g. a Speaker or a Matter Content App diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni index 7f8f477c19a2cd..6404eb306d2c45 100644 --- a/src/app/chip_data_model.gni +++ b/src/app/chip_data_model.gni @@ -352,6 +352,13 @@ template("chip_data_model") { "${_app_root}/clusters/${cluster}/BDXDiagnosticLogsProvider.h", "${_app_root}/clusters/${cluster}/DiagnosticLogsProviderDelegate.h", ] + } else if (cluster == "electrical-energy-measurement-server") { + sources += [ + "${_app_root}/clusters/${cluster}/${cluster}.cpp", + "${_app_root}/clusters/${cluster}/${cluster}.h", + "${_app_root}/clusters/${cluster}/EnergyReportingTestEventTriggerDelegate.cpp", + "${_app_root}/clusters/${cluster}/EnergyReportingTestEventTriggerDelegate.h", + ] } else { sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ] } diff --git a/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.cpp b/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.cpp new file mode 100644 index 00000000000000..210ab401bc2221 --- /dev/null +++ b/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.cpp @@ -0,0 +1,40 @@ +/* + * + * 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 "EnergyReportingTestEventTriggerDelegate.h" + +namespace chip { + +bool EnergyReportingTestEventTriggerDelegate::DoesEnableKeyMatch(const ByteSpan & enableKey) const +{ + return !mEnableKey.empty() && mEnableKey.data_equal(enableKey); +} + +CHIP_ERROR EnergyReportingTestEventTriggerDelegate::HandleEventTrigger(uint64_t eventTrigger) +{ + if (HandleEnergyReportingTestEventTrigger(eventTrigger)) + { + return CHIP_NO_ERROR; + } + if (mOtherDelegate != nullptr) + { + return mOtherDelegate->HandleEventTrigger(eventTrigger); + } + return CHIP_ERROR_INVALID_ARGUMENT; +} + +} // namespace chip diff --git a/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.h b/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.h new file mode 100644 index 00000000000000..a7beef6c2838ce --- /dev/null +++ b/src/app/clusters/electrical-energy-measurement-server/EnergyReportingTestEventTriggerDelegate.h @@ -0,0 +1,83 @@ +/* + * + * 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 + +namespace chip { + +/* + * These Test EventTrigger values can be used to fake meter reading data + * + * They are sent along with the enableKey (manufacturer defined secret) + * in the General Diagnostic cluster TestEventTrigger command + */ +enum class EnergyReportingTrigger : uint64_t +{ + // We use the Electrical Energy Measurement cluster ID as our TE trigger space + // Stop Fake readings + kFakeReadingsStop = 0x0091'0000'0000'0000, + // Fake a load (importing) readings at 1kW 230V 4.34A with 2s updates + kFakeReadingsLoadStart_1kW_2s = 0x0091'0000'0000'0001, + // Fake a generator (exporting) readings at 3kW 230V 3.33A with 5s updates + kFakeReadingsGenStart_3kW_5s = 0x0091'0000'0000'0002, + +}; + +class EnergyReportingTestEventTriggerDelegate : public TestEventTriggerDelegate +{ +public: + /** + * This class expects the enableKey ByteSpan to be valid forever. + * Typically this feature is only enabled in certification testing + * and uses a static secret key in the device for testing (e.g. in factory data) + */ + explicit EnergyReportingTestEventTriggerDelegate(const ByteSpan & enableKey, TestEventTriggerDelegate * otherDelegate) : + mEnableKey(enableKey), mOtherDelegate(otherDelegate) + {} + + /* This function returns True if the enableKey received in the TestEventTrigger command + * matches the value passed into the constructor. + */ + bool DoesEnableKeyMatch(const ByteSpan & enableKey) const override; + + /** This function must return True if the eventTrigger is recognised and handled + * It must return False to allow a higher level TestEvent handler to check other + * clusters that may handle it. + */ + CHIP_ERROR HandleEventTrigger(uint64_t eventTrigger) override; + +private: + ByteSpan mEnableKey; + TestEventTriggerDelegate * mOtherDelegate; +}; + +} // namespace chip + +/** + * @brief User handler for handling the test event trigger + * + * @note If TestEventTrigger is enabled, it needs to be implemented in the app + * + * @param eventTrigger Event trigger to handle + * + * @retval true on success + * @retval false if error happened + */ +bool HandleEnergyReportingTestEventTrigger(uint64_t eventTrigger); diff --git a/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp b/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp index fcc75ef319d60c..728b41dee02f74 100644 --- a/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp +++ b/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.cpp @@ -33,36 +33,9 @@ using namespace chip::app::Clusters::ElectricalEnergyMeasurement; using namespace chip::app::Clusters::ElectricalEnergyMeasurement::Attributes; using namespace chip::app::Clusters::ElectricalEnergyMeasurement::Structs; -struct MeasurementData -{ - MeasurementAccuracyStruct::Type measurementAccuracy; - Optional cumulativeImported; - Optional cumulativeExported; - Optional periodicImported; - Optional periodicExported; -}; - MeasurementData gMeasurements[EMBER_AF_ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT]; -MeasurementData * MeasurementDataForEndpoint(EndpointId endpointId) -{ - auto index = emberAfGetClusterServerEndpointIndex(endpointId, app::Clusters::ElectricalEnergyMeasurement::Id, - EMBER_AF_ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER_SERVER_ENDPOINT_COUNT); - - if (index == kEmberInvalidEndpointIndex) - { - return nullptr; - } - - if (index >= EMBER_AF_ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER_SERVER_ENDPOINT_COUNT) - { - ChipLogError(NotSpecified, "Internal error: invalid/unexpected energy measurement index."); - return nullptr; - } - return &gMeasurements[index]; -} - class ElectricalEnergyMeasurementAttrAccess : public app::AttributeAccessInterface { public: @@ -126,6 +99,24 @@ namespace app { namespace Clusters { namespace ElectricalEnergyMeasurement { +MeasurementData * MeasurementDataForEndpoint(EndpointId endpointId) +{ + auto index = emberAfGetClusterServerEndpointIndex(endpointId, app::Clusters::ElectricalEnergyMeasurement::Id, + EMBER_AF_ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER_SERVER_ENDPOINT_COUNT); + + if (index == kEmberInvalidEndpointIndex) + { + return nullptr; + } + + if (index >= EMBER_AF_ELECTRICAL_ENERGY_MEASUREMENT_CLUSTER_SERVER_ENDPOINT_COUNT) + { + ChipLogError(NotSpecified, "Internal error: invalid/unexpected energy measurement index."); + return nullptr; + } + return &gMeasurements[index]; +} + CHIP_ERROR SetMeasurementAccuracy(EndpointId endpointId, const MeasurementAccuracyStruct::Type & accuracy) { diff --git a/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.h b/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.h index 8647b9bde0b750..9c3d87683d680c 100644 --- a/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.h +++ b/src/app/clusters/electrical-energy-measurement-server/electrical-energy-measurement-server.h @@ -26,6 +26,15 @@ namespace app { namespace Clusters { namespace ElectricalEnergyMeasurement { +struct MeasurementData +{ + Structs::MeasurementAccuracyStruct::Type measurementAccuracy; + Optional cumulativeImported; + Optional cumulativeExported; + Optional periodicImported; + Optional periodicExported; +}; + bool NotifyCumulativeEnergyMeasured(EndpointId endpointId, const Optional & energyImported, const Optional & energyExported); @@ -34,6 +43,8 @@ bool NotifyPeriodicEnergyMeasured(EndpointId endpointId, const Optional