diff --git a/examples/lighting-app/linux/BUILD.gn b/examples/lighting-app/linux/BUILD.gn index cfa89da29e0594..47c14536d9a7c3 100644 --- a/examples/lighting-app/linux/BUILD.gn +++ b/examples/lighting-app/linux/BUILD.gn @@ -36,6 +36,7 @@ config("includes") { executable("chip-lighting-app") { sources = [ + "LightingAppCommandDelegate.cpp", "include/CHIPProjectAppConfig.h", "main.cpp", ] @@ -45,6 +46,7 @@ executable("chip-lighting-app") { "${chip_root}/examples/lighting-app/lighting-common:lighting-manager", "${chip_root}/examples/platform/linux:app-main", "${chip_root}/src/lib", + "${chip_root}/third_party/jsoncpp", ] if (chip_examples_enable_imgui_ui) { diff --git a/examples/lighting-app/linux/LightingAppCommandDelegate.cpp b/examples/lighting-app/linux/LightingAppCommandDelegate.cpp new file mode 100644 index 00000000000000..d0b772f6f04845 --- /dev/null +++ b/examples/lighting-app/linux/LightingAppCommandDelegate.cpp @@ -0,0 +1,353 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "LightingAppCommandDelegate.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::DeviceLayer; + +LightingAppCommandHandler * LightingAppCommandHandler::FromJSON(const char * json) +{ + Json::Reader reader; + Json::Value value; + + if (!reader.parse(json, value)) + { + ChipLogError(NotSpecified, + "AllClusters App: Error parsing JSON with error %s:", reader.getFormattedErrorMessages().c_str()); + return nullptr; + } + + if (value.empty() || !value.isObject()) + { + ChipLogError(NotSpecified, "AllClusters App: Invalid JSON command received"); + return nullptr; + } + + if (!value.isMember("Name") || !value["Name"].isString()) + { + ChipLogError(NotSpecified, "AllClusters App: Invalid JSON command received: command name is missing"); + return nullptr; + } + + return Platform::New(std::move(value)); +} + +void LightingAppCommandHandler::HandleCommand(intptr_t context) +{ + auto * self = reinterpret_cast(context); + std::string name = self->mJsonValue["Name"].asString(); + + VerifyOrExit(!self->mJsonValue.empty(), ChipLogError(NotSpecified, "Invalid JSON event command received")); + + if (name == "SoftwareFault") + { + self->OnSoftwareFaultEventHandler(Clusters::SoftwareDiagnostics::Events::SoftwareFault::Id); + } + else if (name == "HardwareFaultChange") + { + self->OnGeneralFaultEventHandler(Clusters::GeneralDiagnostics::Events::HardwareFaultChange::Id); + } + else if (name == "RadioFaultChange") + { + self->OnGeneralFaultEventHandler(Clusters::GeneralDiagnostics::Events::RadioFaultChange::Id); + } + else if (name == "NetworkFaultChange") + { + self->OnGeneralFaultEventHandler(Clusters::GeneralDiagnostics::Events::NetworkFaultChange::Id); + } + else if (name == "SwitchLatched") + { + uint8_t newPosition = static_cast(self->mJsonValue["NewPosition"].asUInt()); + self->OnSwitchLatchedHandler(newPosition); + } + else if (name == "InitialPress") + { + uint8_t newPosition = static_cast(self->mJsonValue["NewPosition"].asUInt()); + self->OnSwitchInitialPressedHandler(newPosition); + } + else if (name == "LongPress") + { + uint8_t newPosition = static_cast(self->mJsonValue["NewPosition"].asUInt()); + self->OnSwitchLongPressedHandler(newPosition); + } + else if (name == "ShortRelease") + { + uint8_t previousPosition = static_cast(self->mJsonValue["PreviousPosition"].asUInt()); + self->OnSwitchShortReleasedHandler(previousPosition); + } + else if (name == "LongRelease") + { + uint8_t previousPosition = static_cast(self->mJsonValue["PreviousPosition"].asUInt()); + self->OnSwitchLongReleasedHandler(previousPosition); + } + else if (name == "MultiPressOngoing") + { + uint8_t newPosition = static_cast(self->mJsonValue["NewPosition"].asUInt()); + uint8_t count = static_cast(self->mJsonValue["CurrentNumberOfPressesCounted"].asUInt()); + self->OnSwitchMultiPressOngoingHandler(newPosition, count); + } + else if (name == "MultiPressComplete") + { + uint8_t previousPosition = static_cast(self->mJsonValue["PreviousPosition"].asUInt()); + uint8_t count = static_cast(self->mJsonValue["TotalNumberOfPressesCounted"].asUInt()); + self->OnSwitchMultiPressCompleteHandler(previousPosition, count); + } + else if (name == "PowerOnReboot") + { + self->OnRebootSignalHandler(BootReasonType::kPowerOnReboot); + } + else if (name == "BrownOutReset") + { + self->OnRebootSignalHandler(BootReasonType::kBrownOutReset); + } + else if (name == "SoftwareWatchdogReset") + { + self->OnRebootSignalHandler(BootReasonType::kSoftwareWatchdogReset); + } + else if (name == "HardwareWatchdogReset") + { + self->OnRebootSignalHandler(BootReasonType::kHardwareWatchdogReset); + } + else if (name == "SoftwareUpdateCompleted") + { + self->OnRebootSignalHandler(BootReasonType::kSoftwareUpdateCompleted); + } + else if (name == "SoftwareReset") + { + self->OnRebootSignalHandler(BootReasonType::kSoftwareReset); + } + else + { + ChipLogError(NotSpecified, "Unhandled command: Should never happens"); + } + +exit: + Platform::Delete(self); +} + +bool LightingAppCommandHandler::IsClusterPresentOnAnyEndpoint(ClusterId clusterId) +{ + EnabledEndpointsWithServerCluster enabledEndpoints(clusterId); + + return (enabledEndpoints.begin() != enabledEndpoints.end()); +} + +void LightingAppCommandHandler::OnRebootSignalHandler(BootReasonType bootReason) +{ + if (ConfigurationMgr().StoreBootReason(static_cast(bootReason)) == CHIP_NO_ERROR) + { + Server::GetInstance().GenerateShutDownEvent(); + PlatformMgr().ScheduleWork([](intptr_t) { PlatformMgr().StopEventLoopTask(); }); + } + else + { + ChipLogError(NotSpecified, "Failed to store boot reason:%d", static_cast(bootReason)); + } +} + +void LightingAppCommandHandler::OnGeneralFaultEventHandler(uint32_t eventId) +{ + if (!IsClusterPresentOnAnyEndpoint(Clusters::GeneralDiagnostics::Id)) + return; + + if (eventId == Clusters::GeneralDiagnostics::Events::HardwareFaultChange::Id) + { + GeneralFaults previous; + GeneralFaults current; + + using GeneralDiagnostics::HardwareFaultEnum; + + // On Linux Simulation, set following hardware faults statically. + ReturnOnFailure(previous.add(to_underlying(HardwareFaultEnum::kRadio))); + ReturnOnFailure(previous.add(to_underlying(HardwareFaultEnum::kPowerSource))); + + ReturnOnFailure(current.add(to_underlying(HardwareFaultEnum::kRadio))); + ReturnOnFailure(current.add(to_underlying(HardwareFaultEnum::kSensor))); + ReturnOnFailure(current.add(to_underlying(HardwareFaultEnum::kPowerSource))); + ReturnOnFailure(current.add(to_underlying(HardwareFaultEnum::kUserInterfaceFault))); + Clusters::GeneralDiagnosticsServer::Instance().OnHardwareFaultsDetect(previous, current); + } + else if (eventId == Clusters::GeneralDiagnostics::Events::RadioFaultChange::Id) + { + GeneralFaults previous; + GeneralFaults current; + + // On Linux Simulation, set following radio faults statically. + ReturnOnFailure(previous.add(EMBER_ZCL_RADIO_FAULT_ENUM_WI_FI_FAULT)); + ReturnOnFailure(previous.add(EMBER_ZCL_RADIO_FAULT_ENUM_THREAD_FAULT)); + + ReturnOnFailure(current.add(EMBER_ZCL_RADIO_FAULT_ENUM_WI_FI_FAULT)); + ReturnOnFailure(current.add(EMBER_ZCL_RADIO_FAULT_ENUM_CELLULAR_FAULT)); + ReturnOnFailure(current.add(EMBER_ZCL_RADIO_FAULT_ENUM_THREAD_FAULT)); + ReturnOnFailure(current.add(EMBER_ZCL_RADIO_FAULT_ENUM_NFC_FAULT)); + Clusters::GeneralDiagnosticsServer::Instance().OnRadioFaultsDetect(previous, current); + } + else if (eventId == Clusters::GeneralDiagnostics::Events::NetworkFaultChange::Id) + { + GeneralFaults previous; + GeneralFaults current; + + // On Linux Simulation, set following radio faults statically. + ReturnOnFailure(previous.add(to_underlying(Clusters::GeneralDiagnostics::NetworkFaultEnum::kHardwareFailure))); + ReturnOnFailure(previous.add(to_underlying(Clusters::GeneralDiagnostics::NetworkFaultEnum::kNetworkJammed))); + + ReturnOnFailure(current.add(to_underlying(Clusters::GeneralDiagnostics::NetworkFaultEnum::kHardwareFailure))); + ReturnOnFailure(current.add(to_underlying(Clusters::GeneralDiagnostics::NetworkFaultEnum::kNetworkJammed))); + ReturnOnFailure(current.add(to_underlying(Clusters::GeneralDiagnostics::NetworkFaultEnum::kConnectionFailed))); + Clusters::GeneralDiagnosticsServer::Instance().OnNetworkFaultsDetect(previous, current); + } + else + { + ChipLogError(NotSpecified, "Unknow event ID:%d", eventId); + } +} + +void LightingAppCommandHandler::OnSoftwareFaultEventHandler(uint32_t eventId) +{ + VerifyOrReturn(eventId == Clusters::SoftwareDiagnostics::Events::SoftwareFault::Id, + ChipLogError(NotSpecified, "Unknown software fault event received")); + + if (!IsClusterPresentOnAnyEndpoint(Clusters::SoftwareDiagnostics::Id)) + return; + + Clusters::SoftwareDiagnostics::Events::SoftwareFault::Type softwareFault; + char threadName[kMaxThreadNameLength + 1]; + + softwareFault.id = static_cast(getpid()); + Platform::CopyString(threadName, std::to_string(softwareFault.id).c_str()); + + softwareFault.name.SetValue(CharSpan::fromCharString(threadName)); + + std::time_t result = std::time(nullptr); + // Using size of 50 as it is double the expected 25 characters "Www Mmm dd hh:mm:ss yyyy\n". + char timeChar[50]; + if (std::strftime(timeChar, sizeof(timeChar), "%c", std::localtime(&result))) + { + softwareFault.faultRecording.SetValue(ByteSpan(Uint8::from_const_char(timeChar), strlen(timeChar))); + } + + Clusters::SoftwareDiagnosticsServer::Instance().OnSoftwareFaultDetect(softwareFault); +} + +void LightingAppCommandHandler::OnSwitchLatchedHandler(uint8_t newPosition) +{ + EndpointId endpoint = 0; + + EmberAfStatus status = Switch::Attributes::CurrentPosition::Set(endpoint, newPosition); + VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status, ChipLogError(NotSpecified, "Failed to set CurrentPosition attribute")); + ChipLogDetail(NotSpecified, "The latching switch is moved to a new position:%d", newPosition); + + Clusters::SwitchServer::Instance().OnSwitchLatch(endpoint, newPosition); +} + +void LightingAppCommandHandler::OnSwitchInitialPressedHandler(uint8_t newPosition) +{ + EndpointId endpoint = 0; + + EmberAfStatus status = Switch::Attributes::CurrentPosition::Set(endpoint, newPosition); + VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status, ChipLogError(NotSpecified, "Failed to set CurrentPosition attribute")); + ChipLogDetail(NotSpecified, "The new position when the momentary switch starts to be pressed:%d", newPosition); + + Clusters::SwitchServer::Instance().OnInitialPress(endpoint, newPosition); +} + +void LightingAppCommandHandler::OnSwitchLongPressedHandler(uint8_t newPosition) +{ + EndpointId endpoint = 0; + + EmberAfStatus status = Switch::Attributes::CurrentPosition::Set(endpoint, newPosition); + VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status, ChipLogError(NotSpecified, "Failed to set CurrentPosition attribute")); + ChipLogDetail(NotSpecified, "The new position when the momentary switch has been pressed for a long time:%d", newPosition); + + Clusters::SwitchServer::Instance().OnLongPress(endpoint, newPosition); +} + +void LightingAppCommandHandler::OnSwitchShortReleasedHandler(uint8_t previousPosition) +{ + EndpointId endpoint = 0; + + EmberAfStatus status = Switch::Attributes::CurrentPosition::Set(endpoint, 0); + VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status, ChipLogError(NotSpecified, "Failed to reset CurrentPosition attribute")); + ChipLogDetail(NotSpecified, "The the previous value of the CurrentPosition when the momentary switch has been released:%d", + previousPosition); + + Clusters::SwitchServer::Instance().OnShortRelease(endpoint, previousPosition); +} + +void LightingAppCommandHandler::OnSwitchLongReleasedHandler(uint8_t previousPosition) +{ + EndpointId endpoint = 0; + + EmberAfStatus status = Switch::Attributes::CurrentPosition::Set(endpoint, 0); + VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status, ChipLogError(NotSpecified, "Failed to reset CurrentPosition attribute")); + ChipLogDetail(NotSpecified, + "The the previous value of the CurrentPosition when the momentary switch has been released after having been " + "pressed for a long time:%d", + previousPosition); + + Clusters::SwitchServer::Instance().OnLongRelease(endpoint, previousPosition); +} + +void LightingAppCommandHandler::OnSwitchMultiPressOngoingHandler(uint8_t newPosition, uint8_t count) +{ + EndpointId endpoint = 0; + + EmberAfStatus status = Switch::Attributes::CurrentPosition::Set(endpoint, newPosition); + VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status, ChipLogError(NotSpecified, "Failed to set CurrentPosition attribute")); + ChipLogDetail(NotSpecified, "The new position when the momentary switch has been pressed in a multi-press sequence:%d", + newPosition); + ChipLogDetail(NotSpecified, "%d times the momentary switch has been pressed", count); + + Clusters::SwitchServer::Instance().OnMultiPressOngoing(endpoint, newPosition, count); +} + +void LightingAppCommandHandler::OnSwitchMultiPressCompleteHandler(uint8_t previousPosition, uint8_t count) +{ + EndpointId endpoint = 0; + + EmberAfStatus status = Switch::Attributes::CurrentPosition::Set(endpoint, 0); + VerifyOrReturn(EMBER_ZCL_STATUS_SUCCESS == status, ChipLogError(NotSpecified, "Failed to reset CurrentPosition attribute")); + ChipLogDetail(NotSpecified, "The previous position when the momentary switch has been pressed in a multi-press sequence:%d", + previousPosition); + ChipLogDetail(NotSpecified, "%d times the momentary switch has been pressed", count); + + Clusters::SwitchServer::Instance().OnMultiPressComplete(endpoint, previousPosition, count); +} + +void LightingAppCommandDelegate::OnEventCommandReceived(const char * json) +{ + auto handler = LightingAppCommandHandler::FromJSON(json); + if (nullptr == handler) + { + ChipLogError(NotSpecified, "AllClusters App: Unable to instantiate a command handler"); + return; + } + + chip::DeviceLayer::PlatformMgr().ScheduleWork(LightingAppCommandHandler::HandleCommand, reinterpret_cast(handler)); +} diff --git a/examples/lighting-app/linux/LightingAppCommandDelegate.h b/examples/lighting-app/linux/LightingAppCommandDelegate.h new file mode 100644 index 00000000000000..6cbf83d7cdaa1f --- /dev/null +++ b/examples/lighting-app/linux/LightingAppCommandDelegate.h @@ -0,0 +1,97 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "NamedPipeCommands.h" + +#include +#include + +class LightingAppCommandHandler +{ +public: + static LightingAppCommandHandler * FromJSON(const char * json); + + static void HandleCommand(intptr_t context); + + LightingAppCommandHandler(Json::Value && jasonValue) : mJsonValue(std::move(jasonValue)) {} + +private: + Json::Value mJsonValue; + + bool IsClusterPresentOnAnyEndpoint(chip::ClusterId clusterId); + + /** + * Should be called when a reason that caused the device to start-up has been set. + */ + void OnRebootSignalHandler(chip::DeviceLayer::BootReasonType bootReason); + + /** + * Should be called when a general fault takes place on the Node. + */ + void OnGeneralFaultEventHandler(uint32_t eventId); + + /** + * Should be called when a software fault takes place on the Node. + */ + void OnSoftwareFaultEventHandler(uint32_t eventId); + + /** + * Should be called when the latching switch is moved to a new position. + */ + void OnSwitchLatchedHandler(uint8_t newPosition); + + /** + * Should be called when the momentary switch starts to be pressed. + */ + void OnSwitchInitialPressedHandler(uint8_t newPosition); + + /** + * Should be called when the momentary switch has been pressed for a "long" time. + */ + void OnSwitchLongPressedHandler(uint8_t newPosition); + + /** + * Should be called when the momentary switch has been released. + */ + void OnSwitchShortReleasedHandler(uint8_t previousPosition); + + /** + * Should be called when the momentary switch has been released after having been pressed for a long time. + */ + void OnSwitchLongReleasedHandler(uint8_t previousPosition); + + /** + * Should be called to indicate how many times the momentary switch has been pressed in a multi-press + * sequence, during that sequence. + */ + void OnSwitchMultiPressOngoingHandler(uint8_t newPosition, uint8_t count); + + /** + * Should be called to indicate how many times the momentary switch has been pressed in a multi-press + * sequence, after it has been detected that the sequence has ended. + */ + void OnSwitchMultiPressCompleteHandler(uint8_t previousPosition, uint8_t count); +}; + +class LightingAppCommandDelegate : public NamedPipeCommandDelegate +{ +public: + void OnEventCommandReceived(const char * json) override; +}; diff --git a/examples/lighting-app/linux/README.md b/examples/lighting-app/linux/README.md index f41a351da1bebb..ff2c9b1bf50611 100644 --- a/examples/lighting-app/linux/README.md +++ b/examples/lighting-app/linux/README.md @@ -143,3 +143,115 @@ Obtain tracing json file. $ ./{PIGWEED_REPO}/pw_trace_tokenized/py/pw_trace_tokenized/get_trace.py -s localhost:33000 \ -o {OUTPUT_FILE} -t {ELF_FILE} {PIGWEED_REPO}/pw_trace_tokenized/pw_trace_protos/trace_rpc.proto ``` + +## Trigger event using lighting-app event named pipe + +You can send a command to lighting-app to trigger specific event via +lighting-app event named pipe /tmp/chip_lighting_fifo-. + +### Trigger `SoftwareFault` events + +1. Generate event `SoftwareFault` when a software fault takes place on the Node. + +``` +$ echo '{"Name":"SoftwareFault"}' > /tmp/chip_lighting_fifo- +``` + +### Trigger `HardwareFault` events + +1. Generate event `HardwareFaultChange` to indicate a change in the set of + hardware faults currently detected by the Node. + +``` +$ echo '{"Name":"HardwareFaultChange"}' > /tmp/chip_lighting_fifo- +``` + +2. Generate event `RadioFaultChange` to indicate a change in the set of radio + faults currently detected by the Node. + +``` +$ echo '{"Name":"RadioFaultChange"}' > /tmp/chip_lighting_fifo- +``` + +3. Generate event `NetworkFaultChange` to indicate a change in the set of + network faults currently detected by the Node. + +``` +$ echo '{"Name":"NetworkFaultChange"}' > /tmp/chip_lighting_fifo- +``` + +4. Generate event `BootReason` to indicate the reason that caused the device to + start-up, from the following set of `BootReasons`. + +- `PowerOnReboot` The Node has booted as the result of physical interaction + with the device resulting in a reboot. + +- `BrownOutReset` The Node has rebooted as the result of a brown-out of the + Node’s power supply. + +- `SoftwareWatchdogReset` The Node has rebooted as the result of a software + watchdog timer. + +- `HardwareWatchdogReset` The Node has rebooted as the result of a hardware + watchdog timer. + +- `SoftwareUpdateCompleted` The Node has rebooted as the result of a completed + software update. + +- `SoftwareReset` The Node has rebooted as the result of a software initiated + reboot. + +``` +$ echo '{"Name":""}' > /tmp/chip_lighting_fifo- +``` + +### Trigger Switch events + +1. Generate event `SwitchLatched`, when the latching switch is moved to a new + position. + +``` +$ echo '{"Name":"SwitchLatched","NewPosition":3}' > /tmp/chip_lighting_fifo- +``` + +2. Generate event `InitialPress`, when the momentary switch starts to be + pressed. + +``` +$ echo '{"Name":"InitialPress","NewPosition":3}' > /tmp/chip_lighting_fifo- +``` + +3. Generate event `LongPress`, when the momentary switch has been pressed for a + "long" time. + +``` +$ echo '{"Name":"LongPress","NewPosition":3}' > /tmp/chip_lighting_fifo- +``` + +4. Generate event `ShortRelease`, when the momentary switch has been released. + +``` +$ echo '{"Name":"ShortRelease","PreviousPosition":3}' > /tmp/chip_lighting_fifo- +``` + +5. Generate event `LongRelease` when the momentary switch has been released and + after having been pressed for a long time. + +``` +$ echo '{"Name":"LongRelease","PreviousPosition":3}' > /tmp/chip_lighting_fifo- +``` + +6. Generate event `MultiPressOngoing` to indicate how many times the momentary + switch has been pressed in a multi-press sequence, during that sequence. + +``` +$ echo '{"Name":"MultiPressOngoing","NewPosition":3,"CurrentNumberOfPressesCounted":4}' > /tmp/chip_lighting_fifo- +``` + +7. Generate event `MultiPressComplete` to indicate how many times the momentary + switch has been pressed in a multi-press sequence, after it has been detected + that the sequence has ended. + +``` +$ echo '{"Name":"MultiPressComplete","PreviousPosition":3,"TotalNumberOfPressesCounted":2}' > /tmp/chip_lighting_fifo- +``` diff --git a/examples/lighting-app/linux/main.cpp b/examples/lighting-app/linux/main.cpp index 00e94b2c1d413e..5689a0cd68979c 100644 --- a/examples/lighting-app/linux/main.cpp +++ b/examples/lighting-app/linux/main.cpp @@ -16,6 +16,7 @@ * limitations under the License. */ +#include "LightingAppCommandDelegate.h" #include "LightingManager.h" #include @@ -37,6 +38,13 @@ using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; +namespace { + +constexpr const char kChipEventFifoPathPrefix[] = "/tmp/chip_lighting_fifo_"; +NamedPipeCommands sChipNamedPipeCommands; +LightingAppCommandDelegate sLightingAppCommandDelegate; +} // namespace + void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & attributePath, uint8_t type, uint16_t size, uint8_t * value) { @@ -66,7 +74,24 @@ void emberAfOnOffClusterInitCallback(EndpointId endpoint) // TODO: implement any additional Cluster Server init actions } -void ApplicationInit() {} +void ApplicationInit() +{ + std::string path = kChipEventFifoPathPrefix + std::to_string(getpid()); + + if (sChipNamedPipeCommands.Start(path, &sLightingAppCommandDelegate) != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to start CHIP NamedPipeCommands"); + sChipNamedPipeCommands.Stop(); + } +} + +void ApplicationExit() +{ + if (sChipNamedPipeCommands.Stop() != CHIP_NO_ERROR) + { + ChipLogError(NotSpecified, "Failed to stop CHIP NamedPipeCommands"); + } +} int main(int argc, char * argv[]) { diff --git a/src/app/BUILD.gn b/src/app/BUILD.gn index 7686195cb7b956..aade1882cb5f5a 100644 --- a/src/app/BUILD.gn +++ b/src/app/BUILD.gn @@ -55,6 +55,7 @@ buildconfig_header("app_buildconfig") { "CHIP_CONFIG_ENABLE_SESSION_RESUMPTION=${chip_enable_session_resumption}", "CHIP_CONFIG_ACCESS_CONTROL_POLICY_LOGGING_VERBOSITY=${chip_access_control_policy_logging_verbosity}", "CHIP_CONFIG_PERSIST_SUBSCRIPTIONS=${chip_persist_subscriptions}", + "CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION=${chip_subscription_timeout_resumption}", "CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE=${enable_eventlist_attribute}", "CHIP_CONFIG_ENABLE_ICD_SERVER=${chip_enable_icd_server}", ] diff --git a/src/app/InteractionModelEngine.cpp b/src/app/InteractionModelEngine.cpp index 6c32952135c902..569d0b0fdf4a4e 100644 --- a/src/app/InteractionModelEngine.cpp +++ b/src/app/InteractionModelEngine.cpp @@ -34,6 +34,7 @@ #include #include #include +#include namespace chip { namespace app { @@ -324,6 +325,17 @@ void InteractionModelEngine::OnDone(ReadHandler & apReadObj) mReportingEngine.ResetReadHandlerTracker(&apReadObj); mReadHandlers.ReleaseObject(&apReadObj); + +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + if (!mSubscriptionResumptionScheduled && HasSubscriptionsToResume()) + { + mSubscriptionResumptionScheduled = true; + auto timeTillNextResubscriptionSecs = ComputeTimeSecondsTillNextSubscriptionResumption(); + mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(System::Clock::Seconds32(timeTillNextResubscriptionSecs), + ResumeSubscriptionsTimerCallback, this); + mNumSubscriptionResumptionRetries++; + } +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS } Status InteractionModelEngine::OnInvokeCommandRequest(Messaging::ExchangeContext * apExchangeContext, @@ -1752,6 +1764,9 @@ CHIP_ERROR InteractionModelEngine::ResumeSubscriptions() { #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS ReturnErrorCodeIf(!mpSubscriptionResumptionStorage, CHIP_NO_ERROR); +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + ReturnErrorCodeIf(mSubscriptionResumptionScheduled, CHIP_NO_ERROR); +#endif // To avoid the case of a reboot loop causing rapid traffic generation / power consumption, subscription resumption should make // use of the persisted min-interval values, and wait before resumption. Ideally, each persisted subscription should wait their @@ -1776,6 +1791,9 @@ CHIP_ERROR InteractionModelEngine::ResumeSubscriptions() if (subscriptionsToResume) { +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + mSubscriptionResumptionScheduled = true; +#endif ChipLogProgress(InteractionModel, "Resuming %d subscriptions in %u seconds", subscriptionsToResume, minInterval); ReturnErrorOnFailure(mpExchangeMgr->GetSessionManager()->SystemLayer()->StartTimer(System::Clock::Seconds16(minInterval), ResumeSubscriptionsTimerCallback, this)); @@ -1794,6 +1812,10 @@ void InteractionModelEngine::ResumeSubscriptionsTimerCallback(System::Layer * ap #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS VerifyOrReturn(apAppState != nullptr); InteractionModelEngine * imEngine = static_cast(apAppState); +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + imEngine->mSubscriptionResumptionScheduled = false; + bool resumedSubscriptions = false; +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo; auto * iterator = imEngine->mpSubscriptionResumptionStorage->IterateSubscriptions(); while (iterator->Next(subscriptionInfo)) @@ -1833,10 +1855,67 @@ void InteractionModelEngine::ResumeSubscriptionsTimerCallback(System::Layer * ap ChipLogProgress(InteractionModel, "Resuming subscriptionId %" PRIu32, subscriptionInfo.mSubscriptionId); handler->ResumeSubscription(*imEngine->mpCASESessionMgr, subscriptionInfo); +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + resumedSubscriptions = true; +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION } iterator->Release(); + +#if CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + // If no persisted subscriptions needed resumption then all resumption retries are done + if (!resumedSubscriptions) + { + imEngine->mNumSubscriptionResumptionRetries = 0; + } +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + #endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS } +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +uint32_t InteractionModelEngine::ComputeTimeSecondsTillNextSubscriptionResumption() +{ + if (mNumSubscriptionResumptionRetries > CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX) + { + return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS; + } + + return CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS + + GetFibonacciForIndex(mNumSubscriptionResumptionRetries) * + CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS; +} + +bool InteractionModelEngine::HasSubscriptionsToResume() +{ + VerifyOrReturnValue(mpSubscriptionResumptionStorage != nullptr, false); + + // Look through persisted subscriptions and see if any aren't already in mReadHandlers pool + SubscriptionResumptionStorage::SubscriptionInfo subscriptionInfo; + auto * iterator = mpSubscriptionResumptionStorage->IterateSubscriptions(); + bool foundSubscriptionToResume = false; + while (iterator->Next(subscriptionInfo)) + { + if (Loop::Break == mReadHandlers.ForEachActiveObject([&](ReadHandler * handler) { + SubscriptionId subscriptionId; + handler->GetSubscriptionId(subscriptionId); + if (subscriptionId == subscriptionInfo.mSubscriptionId) + { + return Loop::Break; + } + return Loop::Continue; + })) + { + continue; + } + + foundSubscriptionToResume = true; + break; + } + iterator->Release(); + + return foundSubscriptionToResume; +} +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + } // namespace app } // namespace chip diff --git a/src/app/InteractionModelEngine.h b/src/app/InteractionModelEngine.h index b7de62e16ff0be..9e6ed36cce2396 100644 --- a/src/app/InteractionModelEngine.h +++ b/src/app/InteractionModelEngine.h @@ -366,6 +366,7 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, private: friend class reporting::Engine; friend class TestCommandInteraction; + friend class TestInteractionModelEngine; using Status = Protocols::InteractionModel::Status; void OnDone(CommandHandler & apCommandObj) override; @@ -613,6 +614,13 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler, bool mForceHandlerQuota = false; #endif +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + bool HasSubscriptionsToResume(); + uint32_t ComputeTimeSecondsTillNextSubscriptionResumption(); + uint32_t mNumSubscriptionResumptionRetries = 0; + bool mSubscriptionResumptionScheduled = false; +#endif + FabricTable * mpFabricTable; CASESessionManager * mpCASESessionMgr = nullptr; diff --git a/src/app/ReadHandler.cpp b/src/app/ReadHandler.cpp index 1328559a85ac64..bbaf9f7992eb30 100644 --- a/src/app/ReadHandler.cpp +++ b/src/app/ReadHandler.cpp @@ -425,7 +425,6 @@ void ReadHandler::OnResponseTimeout(Messaging::ExchangeContext * apExchangeConte ChipLogError(DataManagement, "Time out! failed to receive status response from Exchange: " ChipLogFormatExchange, ChipLogValueExchange(apExchangeContext)); #if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS - // TODO: Have a retry mechanism tied to wake interval for IC devices Close(CloseOptions::kKeepPersistedSubscription); #else Close(); diff --git a/src/app/clusters/laundry-washer-controls-server/laundry-washer-controls-delegate.h b/src/app/clusters/laundry-washer-controls-server/laundry-washer-controls-delegate.h new file mode 100644 index 00000000000000..5529413a40cb88 --- /dev/null +++ b/src/app/clusters/laundry-washer-controls-server/laundry-washer-controls-delegate.h @@ -0,0 +1,59 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace LaundryWasherControls { + +/** @brief + * Defines methods for implementing application-specific logic for the laundry washer controls cluster. + */ +class Delegate +{ +public: + Delegate() = default; + virtual ~Delegate() = default; + + /** + * Get the list of supported spin_speed list. + * Fills in the provided spin_speed at index `index` if there is one, + * or returns CHIP_ERROR_PROVIDER_LIST_EXHAUSTED if the index is out of range for the list of spin_speed. + * @param index The index of the spin_speed, with 0 representing the first one. + * @param spinSpeed The spin speed is filled. + */ + virtual CHIP_ERROR GetSpinSpeedAtIndex(size_t index, MutableCharSpan & spinSpeed) = 0; + + /** + * Get the list of supported rinses list. + * Fills in the provided rinses at index `index` if there is one, + * or returns CHIP_ERROR_PROVIDER_LIST_EXHAUSTED if the index is out of range for the list of rinses. + * @param index The index of the supported rinses with 0 representing the first one. + * @param supportedRinse The supported rinse is filled. + */ + virtual CHIP_ERROR GetSupportedRinseAtIndex(size_t index, NumberOfRinsesEnum & supportedRinse) = 0; +}; + +} // namespace LaundryWasherControls +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/clusters/laundry-washer-controls-server/laundry-washer-controls-server.cpp b/src/app/clusters/laundry-washer-controls-server/laundry-washer-controls-server.cpp new file mode 100644 index 00000000000000..76832e4c091eb8 --- /dev/null +++ b/src/app/clusters/laundry-washer-controls-server/laundry-washer-controls-server.cpp @@ -0,0 +1,195 @@ +/** + * + * 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. + */ + +#include +#include +#include + +#include "laundry-washer-controls-delegate.h" +#include "laundry-washer-controls-server.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters; +using namespace chip::app::Clusters::LaundryWasherControls; +using namespace chip::app::Clusters::LaundryWasherControls::Attributes; +using chip::Protocols::InteractionModel::Status; + +static constexpr size_t kLaundryWasherControlsDelegateTableSize = + EMBER_AF_LAUNDRY_WASHER_CONTROLS_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; + +// ----------------------------------------------------------------------------- +// Delegate Implementation +// +namespace { +Delegate * gDelegateTable[kLaundryWasherControlsDelegateTableSize] = { nullptr }; +} + +namespace { +Delegate * GetDelegate(EndpointId endpoint) +{ + uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, LaundryWasherControls::Id, + EMBER_AF_LAUNDRY_WASHER_CONTROLS_CLUSTER_SERVER_ENDPOINT_COUNT); + return (ep >= kLaundryWasherControlsDelegateTableSize ? nullptr : gDelegateTable[ep]); +} + +} // namespace + +LaundryWasherControlsServer LaundryWasherControlsServer::sInstance; + +/********************************************************** + * LaundryWasherControlsServer public methods + *********************************************************/ +void LaundryWasherControlsServer::SetDefaultDelegate(EndpointId endpoint, Delegate * delegate) +{ + uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, LaundryWasherControls::Id, + EMBER_AF_LAUNDRY_WASHER_CONTROLS_CLUSTER_SERVER_ENDPOINT_COUNT); + // if endpoint is found + if (ep < kLaundryWasherControlsDelegateTableSize) + { + gDelegateTable[ep] = delegate; + } +} + +LaundryWasherControlsServer & LaundryWasherControlsServer::Instance() +{ + return sInstance; +} + +EmberAfStatus LaundryWasherControlsServer::SetSpinSpeedCurrent(EndpointId endpointId, + DataModel::Nullable newSpinSpeedCurrent) +{ + DataModel::Nullable spinSpeedCurrent; + EmberAfStatus res = SpinSpeedCurrent::Get(endpointId, spinSpeedCurrent); + if ((res == EMBER_ZCL_STATUS_SUCCESS) && (spinSpeedCurrent != newSpinSpeedCurrent)) + { + res = SpinSpeedCurrent::Set(endpointId, newSpinSpeedCurrent); + } + + return res; +} + +EmberAfStatus LaundryWasherControlsServer::GetSpinSpeedCurrent(EndpointId endpointId, + DataModel::Nullable & spinSpeedCurrent) +{ + return SpinSpeedCurrent::Get(endpointId, spinSpeedCurrent); +} + +EmberAfStatus LaundryWasherControlsServer::SetNumberOfRinses(EndpointId endpointId, NumberOfRinsesEnum newNumberOfRinses) +{ + NumberOfRinsesEnum numberOfRinses; + EmberAfStatus res = NumberOfRinses::Get(endpointId, &numberOfRinses); + + if ((res == EMBER_ZCL_STATUS_SUCCESS) && (numberOfRinses != newNumberOfRinses)) + { + res = NumberOfRinses::Set(endpointId, newNumberOfRinses); + } + + return res; +} + +EmberAfStatus LaundryWasherControlsServer::GetNumberOfRinses(EndpointId endpointId, NumberOfRinsesEnum & numberOfRinses) +{ + return NumberOfRinses::Get(endpointId, &numberOfRinses); +} + +/********************************************************** + * LaundryWasherControlsServer private methods + *********************************************************/ +CHIP_ERROR LaundryWasherControlsServer::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + if (aPath.mClusterId != LaundryWasherControls::Id) + { + // We shouldn't have been called at all. + return CHIP_ERROR_INVALID_ARGUMENT; + } + switch (aPath.mAttributeId) + { + case Attributes::SpinSpeeds::Id: + return ReadSpinSpeeds(aPath, aEncoder); + case Attributes::SupportedRinses::Id: + return ReadSupportedRinses(aPath, aEncoder); + default: + break; + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR LaundryWasherControlsServer::ReadSpinSpeeds(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) +{ + Delegate * delegate = GetDelegate(aPath.mEndpointId); + VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is nullptr")); + + return aEncoder.EncodeList([delegate](const auto & encoder) -> CHIP_ERROR { + for (uint8_t i = 0; true; i++) + { + char buffer[kMaxSpinSpeedLength]; + MutableCharSpan spinSpeed(buffer); + auto err = delegate->GetSpinSpeedAtIndex(i, spinSpeed); + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + return CHIP_NO_ERROR; + } + ReturnErrorOnFailure(err); + ReturnErrorOnFailure(encoder.Encode(spinSpeed)); + } + }); +} + +CHIP_ERROR LaundryWasherControlsServer::ReadSupportedRinses(const ConcreteReadAttributePath & aPath, + AttributeValueEncoder & aEncoder) +{ + Delegate * delegate = GetDelegate(aPath.mEndpointId); + VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is nullptr")); + + return aEncoder.EncodeList([delegate](const auto & encoder) -> CHIP_ERROR { + for (uint8_t i = 0; true; i++) + { + NumberOfRinsesEnum supportedRinse; + auto err = delegate->GetSupportedRinseAtIndex(i, supportedRinse); + if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED) + { + return CHIP_NO_ERROR; + } + ReturnErrorOnFailure(err); + ReturnErrorOnFailure(encoder.Encode(supportedRinse)); + } + }); +} + +/********************************************************** + * Register LaundryWasherControlsServer + *********************************************************/ + +void MatterLaundryWasherControlsPluginServerInitCallback() +{ + LaundryWasherControlsServer & laundryWasherControlsServer = LaundryWasherControlsServer::Instance(); + registerAttributeAccessOverride(&laundryWasherControlsServer); +} diff --git a/src/app/clusters/laundry-washer-controls-server/laundry-washer-controls-server.h b/src/app/clusters/laundry-washer-controls-server/laundry-washer-controls-server.h new file mode 100644 index 00000000000000..ac9b4ca0b7f209 --- /dev/null +++ b/src/app/clusters/laundry-washer-controls-server/laundry-washer-controls-server.h @@ -0,0 +1,103 @@ +/** + * + * 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. + */ + +#pragma once + +#include "laundry-washer-controls-delegate.h" +#include +#include +#include + +namespace chip { +namespace app { +namespace Clusters { +namespace LaundryWasherControls { + +/** + * @brief LaundryWasherControls Server Plugin class + */ +class LaundryWasherControlsServer : public AttributeAccessInterface +{ +public: + LaundryWasherControlsServer() : AttributeAccessInterface(Optional::Missing(), LaundryWasherControls::Id) {} + static LaundryWasherControlsServer & Instance(); + + static constexpr uint8_t kMaxSpinSpeedLength = 64; + + /** + * Set the default delegate of laundry washer server at endpoint x + * @param endpoint ID of the endpoint + * @param delegate The default delegate at the endpoint + * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code. + */ + static void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate); + + /** + * Init the laundry washer server. + * @param void + * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code. + */ + // CHIP_ERROR Init(); + + /** + * @brief Set the attribute newSpinSpeedCurrent + * + * @param endpointId ID of the endpoint + * @param newSpinSpeedCurrent attribute SpinSpeedCurrent + * @return true on success, false on failure + */ + EmberAfStatus SetSpinSpeedCurrent(EndpointId endpointId, DataModel::Nullable newSpinSpeedCurrent); + + /** + * @brief Get the attribute newSpinSpeedCurrent + * + * @param endpointId ID of the endpoint + * @param SpinSpeedCurrent attribute SpinSpeedCurrent + * @return true on success, false on failure + */ + EmberAfStatus GetSpinSpeedCurrent(EndpointId endpointId, DataModel::Nullable & spinSpeedCurrent); + + /** + * @brief Set the attribute NumberOfRinses + * + * @param endpointId ID of the endpoint + * @param newNumberOfRinses attribute NumberOfRinses + * @return true on success, false on failure + */ + EmberAfStatus SetNumberOfRinses(EndpointId endpointId, NumberOfRinsesEnum newNumberOfRinses); + + /** + * @brief Get the attribute NumberOfRinses + * + * @param endpointId ID of the endpoint + * @param NumberOfRinses attribute NumberOfRinses + * @return true on success, false on failure + */ + EmberAfStatus GetNumberOfRinses(EndpointId endpointId, NumberOfRinsesEnum & numberOfRinses); + +private: + CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + CHIP_ERROR ReadSpinSpeeds(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder); + CHIP_ERROR ReadSupportedRinses(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder); + + static LaundryWasherControlsServer sInstance; +}; + +} // namespace LaundryWasherControls +} // namespace Clusters +} // namespace app +} // namespace chip diff --git a/src/app/tests/TestInteractionModelEngine.cpp b/src/app/tests/TestInteractionModelEngine.cpp index a7ebc2b49320fd..6fc5b1da27dcbe 100644 --- a/src/app/tests/TestInteractionModelEngine.cpp +++ b/src/app/tests/TestInteractionModelEngine.cpp @@ -48,6 +48,9 @@ class TestInteractionModelEngine public: static void TestAttributePathParamsPushRelease(nlTestSuite * apSuite, void * apContext); static void TestRemoveDuplicateConcreteAttribute(nlTestSuite * apSuite, void * apContext); +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + static void TestSubscriptionResumptionTimer(nlTestSuite * apSuite, void * apContext); +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION static int GetAttributePathListLength(ObjectList * apattributePathParamsList); }; @@ -223,6 +226,37 @@ void TestInteractionModelEngine::TestRemoveDuplicateConcreteAttribute(nlTestSuit InteractionModelEngine::GetInstance()->ReleaseAttributePathList(attributePathParamsList); } +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION +void TestInteractionModelEngine::TestSubscriptionResumptionTimer(nlTestSuite * apSuite, void * apContext) +{ + TestContext & ctx = *static_cast(apContext); + CHIP_ERROR err = CHIP_NO_ERROR; + err = InteractionModelEngine::GetInstance()->Init(&ctx.GetExchangeManager(), &ctx.GetFabricTable()); + NL_TEST_ASSERT(apSuite, err == CHIP_NO_ERROR); + + uint32_t timeTillNextResubscriptionMs; + InteractionModelEngine::GetInstance()->mNumSubscriptionResumptionRetries = 0; + timeTillNextResubscriptionMs = InteractionModelEngine::GetInstance()->ComputeTimeSecondsTillNextSubscriptionResumption(); + NL_TEST_ASSERT(apSuite, timeTillNextResubscriptionMs == CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS); + + uint32_t lastTimeTillNextResubscriptionMs = timeTillNextResubscriptionMs; + for (InteractionModelEngine::GetInstance()->mNumSubscriptionResumptionRetries = 1; + InteractionModelEngine::GetInstance()->mNumSubscriptionResumptionRetries <= + CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX; + InteractionModelEngine::GetInstance()->mNumSubscriptionResumptionRetries++) + { + timeTillNextResubscriptionMs = InteractionModelEngine::GetInstance()->ComputeTimeSecondsTillNextSubscriptionResumption(); + NL_TEST_ASSERT(apSuite, timeTillNextResubscriptionMs >= lastTimeTillNextResubscriptionMs); + NL_TEST_ASSERT(apSuite, timeTillNextResubscriptionMs < CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS); + lastTimeTillNextResubscriptionMs = timeTillNextResubscriptionMs; + } + + InteractionModelEngine::GetInstance()->mNumSubscriptionResumptionRetries = 2000; + timeTillNextResubscriptionMs = InteractionModelEngine::GetInstance()->ComputeTimeSecondsTillNextSubscriptionResumption(); + NL_TEST_ASSERT(apSuite, timeTillNextResubscriptionMs == CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS); +} +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + } // namespace app } // namespace chip @@ -233,6 +267,9 @@ const nlTest sTests[] = { NL_TEST_DEF("TestAttributePathParamsPushRelease", chip::app::TestInteractionModelEngine::TestAttributePathParamsPushRelease), NL_TEST_DEF("TestRemoveDuplicateConcreteAttribute", chip::app::TestInteractionModelEngine::TestRemoveDuplicateConcreteAttribute), +#if CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION + NL_TEST_DEF("TestSubscriptionResumptionTimer", chip::app::TestInteractionModelEngine::TestSubscriptionResumptionTimer), +#endif // CHIP_CONFIG_PERSIST_SUBSCRIPTIONS && CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION NL_TEST_SENTINEL() }; // clang-format on diff --git a/src/app/zap_cluster_list.json b/src/app/zap_cluster_list.json index e405b924737a7d..f88948ef7d7c5a 100644 --- a/src/app/zap_cluster_list.json +++ b/src/app/zap_cluster_list.json @@ -236,6 +236,7 @@ "UNIT_TESTING_CLUSTER": ["test-cluster-server"], "USER_LABEL_CLUSTER": ["user-label-server"], "WAKE_ON_LAN_CLUSTER": ["wake-on-lan-server"], + "LAUNDRY_WASHER_CONTROLS_CLUSTER": ["laundry-washer-controls-server"], "WIFI_NETWORK_DIAGNOSTICS_CLUSTER": ["wifi-network-diagnostics-server"], "WINDOW_COVERING_CLUSTER": ["window-covering-server"], "ZLL_COMMISSIONING_CLUSTER": [] diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 9ec11778f615c5..f83951ba3775c7 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -902,6 +902,10 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_IM_MAX_NUM_TIMED_HANDLER 8 #endif +/** + * @} + */ + /** * @def CONFIG_BUILD_FOR_HOST_UNIT_TEST * @@ -1497,5 +1501,60 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #endif /** - * @} + * @name Configuation for resuming subscriptions that timed out + * + * @brief + * The following definitions sets the parameters for subscription resumption in the case of subscription timeout. + * * #CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX + * * #CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS + * * #CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS + * * #CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS + * + * @{ + */ + +/** + * @def CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX + * + * @brief + * If subscription timeout resumption is enabled, specify the max fibonacci step index. + * + * This index must satisfy below conditions (for readability "CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_" prefix is omitted): + * * MIN_RETRY_INTERVAL_SECS + Fibonacci(MAX_FIBONACCI_STEP_INDEX + 1) * WAIT_TIME_MULTIPLIER_SECS > MAX_RETRY_INTERVAL_SECS + * * MIN_RETRY_INTERVAL_SECS + Fibonacci(MAX_FIBONACCI_STEP_INDEX) * WAIT_TIME_MULTIPLIER_SECS < MAX_RETRY_INTERVAL_SECS + * + */ +#ifndef CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX +#define CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX 10 +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_FIBONACCI_STEP_INDEX + +/** + * @def CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS + * + * @brief The minimum interval before resuming a subsciption that timed out. + */ +#ifndef CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS +#define CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS 300 +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MIN_RETRY_INTERVAL_SECS + +/** + * @def CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS + * + * @brief The multiplier per step in the calculation of retry interval. + */ +#ifndef CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS +#define CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS 300 +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_WAIT_TIME_MULTIPLIER_SECS + +/** + * @def CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS + * + * @brief The maximum interval before resuming a subsciption that timed out. + */ +#ifndef CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS +#define CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS (3600 * 6) +#endif // CHIP_CONFIG_SUBSCRIPTION_TIMEOUT_RESUMPTION_MAX_RETRY_INTERVAL_SECS + +/** + * @} */ diff --git a/src/platform/device.gni b/src/platform/device.gni index ecf01173dbeb05..b70b576644318e 100644 --- a/src/platform/device.gni +++ b/src/platform/device.gni @@ -111,6 +111,11 @@ declare_args() { } } +declare_args() { + # Enable subscription resumption after timeout - separate configuration for power use measurement + chip_subscription_timeout_resumption = chip_persist_subscriptions +} + if (chip_device_platform == "bl702" || chip_device_platform == "bl702l") { if (chip_enable_openthread) { chip_mdns = "platform"