blob: 14b388445f2621cb42475773e3a1a4451d6d2e49 [file] [log] [blame]
/**
*
* Copyright (c) 2021 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 <app-common/zap-generated/af-structs.h>
#include <app-common/zap-generated/att-storage.h>
#include <app-common/zap-generated/attribute-type.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/AttributeAccessInterface.h>
#include <app/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
#include <app/clusters/mode-select-server/supported-modes-manager.h>
#include <app/clusters/on-off-server/on-off-server.h>
#include <app/util/af.h>
#include <app/util/attribute-storage.h>
#include <app/util/error-mapping.h>
#include <app/util/odd-sized-integers.h>
#include <app/util/util.h>
#include <lib/support/CodeUtils.h>
#include <platform/DiagnosticDataProvider.h>
using namespace std;
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::ModeSelect;
using namespace chip::Protocols;
using BootReasonType = GeneralDiagnostics::BootReasonType;
static InteractionModel::Status verifyModeValue(const EndpointId endpointId, const uint8_t newMode);
namespace {
inline bool areStartUpModeAndCurrentModeNonVolatile(EndpointId endpoint);
class ModeSelectAttrAccess : public AttributeAccessInterface
{
public:
ModeSelectAttrAccess() : AttributeAccessInterface(Optional<EndpointId>::Missing(), ModeSelect::Id) {}
CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
};
ModeSelectAttrAccess gModeSelectAttrAccess;
CHIP_ERROR ModeSelectAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
VerifyOrDie(aPath.mClusterId == ModeSelect::Id);
const ModeSelect::SupportedModesManager * gSupportedModeManager = ModeSelect::getSupportedModesManager();
if (ModeSelect::Attributes::SupportedModes::Id == aPath.mAttributeId)
{
const ModeSelect::SupportedModesManager::ModeOptionsProvider modeOptionsProvider =
gSupportedModeManager->getModeOptionsProvider(aPath.mEndpointId);
if (modeOptionsProvider.begin() == nullptr)
{
aEncoder.EncodeEmptyList();
return CHIP_NO_ERROR;
}
CHIP_ERROR err;
err = aEncoder.EncodeList([modeOptionsProvider](const auto & encoder) -> CHIP_ERROR {
const auto * end = modeOptionsProvider.end();
for (auto * it = modeOptionsProvider.begin(); it != end; ++it)
{
auto & modeOption = *it;
ReturnErrorOnFailure(encoder.Encode(modeOption));
}
return CHIP_NO_ERROR;
});
ReturnErrorOnFailure(err);
}
return CHIP_NO_ERROR;
}
} // anonymous namespace
bool emberAfModeSelectClusterChangeToModeCallback(CommandHandler * commandHandler, const ConcreteCommandPath & commandPath,
const ModeSelect::Commands::ChangeToMode::DecodableType & commandData)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "ModeSelect: Entering emberAfModeSelectClusterChangeToModeCallback");
EndpointId endpointId = commandPath.mEndpointId;
uint8_t newMode = commandData.newMode;
// Check that the newMode matches one of the supported options
const ModeSelect::Structs::ModeOptionStruct::Type * modeOptionPtr;
EmberAfStatus checkSupportedModeStatus =
ModeSelect::getSupportedModesManager()->getModeOptionByMode(endpointId, newMode, &modeOptionPtr);
if (EMBER_ZCL_STATUS_SUCCESS != checkSupportedModeStatus)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "ModeSelect: Failed to find the option with mode %u", newMode);
emberAfSendImmediateDefaultResponse(checkSupportedModeStatus);
return false;
}
ModeSelect::Attributes::CurrentMode::Set(endpointId, newMode);
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "ModeSelect: ChangeToMode successful");
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
return true;
}
/**
* Callback for Mode Select Cluster Server Initialization.
* Enabled in src/app/zap-templates/templates/app/helper.js
* @param endpointId id of the endpoint that is being initialized
*/
void emberAfModeSelectClusterServerInitCallback(EndpointId endpointId)
{
// StartUp behavior relies on CurrentMode StartUpMode attributes being non-volatile.
if (areStartUpModeAndCurrentModeNonVolatile(endpointId))
{
// Read the StartUpMode attribute and set the CurrentMode attribute
// The StartUpMode attribute SHALL define the desired startup behavior of a
// device when it is supplied with power and this state SHALL be
// reflected in the CurrentMode attribute. The values of the StartUpMode
// attribute are listed below.
DataModel::Nullable<uint8_t> startUpMode;
EmberAfStatus status = Attributes::StartUpMode::Get(endpointId, startUpMode);
if (status == EMBER_ZCL_STATUS_SUCCESS && !startUpMode.IsNull())
{
#ifdef EMBER_AF_PLUGIN_ON_OFF
// OnMode with Power Up
// If the On/Off feature is supported and the On/Off cluster attribute StartUpOnOff is present, with a
// value of On (turn on at power up), then the CurrentMode attribute SHALL be set to the OnMode attribute
// value when the server is supplied with power, except if the OnMode attribute is null.
if (emberAfContainsServer(endpointId, OnOff::Id) &&
emberAfContainsAttribute(endpointId, OnOff::Id, OnOff::Attributes::StartUpOnOff::Id) &&
emberAfContainsAttribute(endpointId, ModeSelect::Id, ModeSelect::Attributes::OnMode::Id))
{
Attributes::OnMode::TypeInfo::Type onMode;
bool onOffValueForStartUp = false;
if (Attributes::OnMode::Get(endpointId, onMode) == EMBER_ZCL_STATUS_SUCCESS &&
!emberAfIsKnownVolatileAttribute(endpointId, OnOff::Id, OnOff::Attributes::StartUpOnOff::Id) &&
OnOffServer::Instance().getOnOffValueForStartUp(endpointId, onOffValueForStartUp) == EMBER_ZCL_STATUS_SUCCESS)
{
if (onOffValueForStartUp && !onMode.IsNull())
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "ModeSelect: CurrentMode is overwritten by OnMode");
return;
}
}
}
#endif // EMBER_AF_PLUGIN_ON_OFF
BootReasonType bootReason = BootReasonType::kUnspecified;
CHIP_ERROR error = DeviceLayer::GetDiagnosticDataProvider().GetBootReason(bootReason);
if (error != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Unable to retrieve boot reason: %" CHIP_ERROR_FORMAT, error.Format());
// We really only care whether the boot reason is OTA. Assume it's not.
bootReason = BootReasonType::kUnspecified;
}
if (bootReason == BootReasonType::kSoftwareUpdateCompleted)
{
ChipLogDetail(Zcl, "ModeSelect: CurrentMode is ignored for OTA reboot");
return;
}
// Initialise currentMode to 0
uint8_t currentMode = 0;
status = Attributes::CurrentMode::Get(endpointId, &currentMode);
if ((status == EMBER_ZCL_STATUS_SUCCESS) && (startUpMode.Value() != currentMode))
{
status = Attributes::CurrentMode::Set(endpointId, startUpMode.Value());
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
ChipLogError(Zcl, "ModeSelect: Error initializing CurrentMode, EmberAfStatus code 0x%02x", status);
}
else
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "ModeSelect: Successfully initialized CurrentMode to %u",
startUpMode.Value());
}
}
}
}
else
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG,
"ModeSelect: Skipped initializing CurrentMode by StartUpMode because one of them is volatile");
}
}
namespace {
/**
* Checks if StartUpMode and CurrentMode are non-volatile.
* @param endpointId id of the endpoint to check
* @return true if both attributes are non-volatile; false otherwise.
*/
inline bool areStartUpModeAndCurrentModeNonVolatile(EndpointId endpointId)
{
return !emberAfIsKnownVolatileAttribute(endpointId, ModeSelect::Id, Attributes::CurrentMode::Id) &&
!emberAfIsKnownVolatileAttribute(endpointId, ModeSelect::Id, Attributes::StartUpMode::Id);
}
} // namespace
void MatterModeSelectPluginServerInitCallback()
{
registerAttributeAccessOverride(&gModeSelectAttrAccess);
}
/**
* Callback for Mode Select Cluster Server Pre Attribute Changed
* Enabled in src/app/zap-templates/templates/app/helper.js
* @param attributePath Concrete attribute path to be changed
* @param attributeType Attribute type
* @param size Attribute size
* @param value Attribute value
*/
InteractionModel::Status MatterModeSelectClusterServerPreAttributeChangedCallback(const ConcreteAttributePath & attributePath,
EmberAfAttributeType attributeType, uint16_t size,
uint8_t * value)
{
const EndpointId endpointId = attributePath.mEndpointId;
InteractionModel::Status result;
switch (attributePath.mAttributeId)
{
case ModeSelect::Attributes::StartUpMode::Id:
result = verifyModeValue(endpointId, *value);
break;
case ModeSelect::Attributes::OnMode::Id:
result = verifyModeValue(endpointId, *value);
break;
default:
result = InteractionModel::Status::Success;
}
return result;
}
/**
* Checks the new mode against the endpoint's supported modes.
* @param endpointId endpointId of the endpoint
* @param newMode value of the new mode
* @return Success status if the value is valid; InvalidValue otherwise.
*/
static InteractionModel::Status verifyModeValue(const EndpointId endpointId, const uint8_t newMode)
{
if (NumericAttributeTraits<uint8_t>::IsNullValue(newMode)) // This indicates that the new mode is null.
{
return InteractionModel::Status::Success;
}
const ModeSelect::Structs::ModeOptionStruct::Type * modeOptionPtr;
EmberAfStatus checkSupportedModeStatus =
ModeSelect::getSupportedModesManager()->getModeOptionByMode(endpointId, newMode, &modeOptionPtr);
if (EMBER_ZCL_STATUS_SUCCESS != checkSupportedModeStatus)
{
const InteractionModel::Status returnStatus = ToInteractionModelStatus(checkSupportedModeStatus);
return returnStatus;
}
return InteractionModel::Status::Success;
}