blob: 62b80cb79109d2fb196309030428f64a7d8a8806 [file] [log] [blame]
/**
*
* Copyright (c) 2025 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 "ThermostatSuggestionStructWithOwnedMembers.h"
#include "thermostat-server-presets.h"
#include "thermostat-server.h"
#include <app/reporting/reporting.h>
#include <platform/internal/CHIPDeviceLayerInternal.h>
#include <protocols/interaction_model/StatusCode.h>
#include <system/SystemClock.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::Thermostat;
using namespace chip::app::Clusters::Thermostat::Attributes;
using namespace chip::app::Clusters::Thermostat::Structs;
using namespace chip::app::Clusters::Globals::Structs;
using namespace chip::Protocols::InteractionModel;
using namespace System::Clock;
namespace chip {
namespace app {
namespace Clusters {
namespace Thermostat {
namespace {
CHIP_ERROR RemoveExpiredSuggestions(Delegate * delegate)
{
VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
uint32_t currentMatterEpochTimestampInSeconds = 0;
CHIP_ERROR err = System::Clock::GetClock_MatterEpochS(currentMatterEpochTimestampInSeconds);
ReturnErrorOnFailure(err);
for (int i = static_cast<int>(delegate->GetNumberOfThermostatSuggestions() - 1); i >= 0; i--)
{
ThermostatSuggestionStructWithOwnedMembers suggestion;
err = delegate->GetThermostatSuggestionAtIndex(static_cast<size_t>(i), suggestion);
ReturnErrorOnFailure(err);
if (suggestion.GetExpirationTime() <= Seconds32(currentMatterEpochTimestampInSeconds))
{
err = delegate->RemoveFromThermostatSuggestionsList(static_cast<size_t>(i));
ReturnErrorOnFailure(err);
}
}
return err;
}
Status RemoveFromThermostatSuggestionsList(Delegate * delegate, uint8_t uniqueIDToRemove)
{
VerifyOrReturnValue(delegate != nullptr, Status::Failure);
size_t uniqueIDMatchedIndex = 0;
CHIP_ERROR err = CHIP_NO_ERROR;
for (size_t index = 0; true; ++index)
{
ThermostatSuggestionStructWithOwnedMembers suggestion;
err = delegate->GetThermostatSuggestionAtIndex(index, suggestion);
if (err == CHIP_ERROR_PROVIDER_LIST_EXHAUSTED)
{
return Status::NotFound;
}
if (suggestion.GetUniqueID() == uniqueIDToRemove)
{
uniqueIDMatchedIndex = index;
break;
}
};
err = delegate->RemoveFromThermostatSuggestionsList(uniqueIDMatchedIndex);
VerifyOrReturnValue(err == CHIP_NO_ERROR, Status::Failure);
return Status::Success;
}
} // anonymous namespace
bool AddThermostatSuggestion(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::AddThermostatSuggestion::DecodableType & commandData)
{
// Check constraints for PresetHandle and ExpirationInMinutes field.
if (commandData.presetHandle.size() >= kThermostatSuggestionPresetHandleSize)
{
commandObj->AddStatus(commandPath, Status::ConstraintError);
return true;
}
if (commandData.expirationInMinutes < 30 || commandData.expirationInMinutes > 1440)
{
commandObj->AddStatus(commandPath, Status::ConstraintError);
return true;
}
EndpointId endpoint = commandPath.mEndpointId;
auto delegate = GetDelegate(endpoint);
if (delegate == nullptr)
{
ChipLogError(Zcl, "Delegate is null for endpoint %u", endpoint);
commandObj->AddStatus(commandPath, Status::InvalidInState);
return true;
}
// If time is not synced, return INVALID_IN_STATE in the AddThermostatSuggestionResponse.
uint32_t currentMatterEpochTimestampInSeconds = 0;
if (System::Clock::GetClock_MatterEpochS(currentMatterEpochTimestampInSeconds) != CHIP_NO_ERROR)
{
commandObj->AddStatus(commandPath, Status::InvalidInState);
return true;
}
// If the preset hande doesn't exist in the Presets attribute, return NOT_FOUND.
if (!IsPresetHandlePresentInPresets(delegate, commandData.presetHandle))
{
commandObj->AddStatus(commandPath, Status::NotFound);
return true;
}
// If the thermostat suggestions list is full, return RESOURCE_EXHAUSTED.
if (delegate->GetNumberOfThermostatSuggestions() >= delegate->GetMaxThermostatSuggestions())
{
commandObj->AddStatus(commandPath, Status::ResourceExhausted);
return true;
}
// If the effective time in UTC is greater than current time in UTC plus 24 hours, return INVALID_COMMAND.
const uint32_t kSecondsInDay = 24 * 60 * 60;
if (!commandData.effectiveTime.IsNull() &&
(commandData.effectiveTime.Value() > currentMatterEpochTimestampInSeconds + kSecondsInDay))
{
commandObj->AddStatus(commandPath, Status::InvalidCommand);
return true;
}
// Remove any expired suggestions before adding to the list.
CHIP_ERROR err = RemoveExpiredSuggestions(delegate);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to RemoveExpiredSuggestions at endpoint %u with error: %" CHIP_ERROR_FORMAT, endpoint,
err.Format());
commandObj->AddStatus(commandPath, Status::Failure);
return true;
}
uint8_t uniqueID = 0;
err = delegate->GetUniqueID(uniqueID);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to GetUniqueID at endpoint %u with error: %" CHIP_ERROR_FORMAT, endpoint, err.Format());
commandObj->AddStatus(commandPath, Status::Failure);
return true;
}
Structs::ThermostatSuggestionStruct::Type thermostatSuggestion;
thermostatSuggestion.uniqueID = uniqueID;
thermostatSuggestion.presetHandle = commandData.presetHandle;
uint32_t effectiveTime = commandData.effectiveTime.ValueOr(currentMatterEpochTimestampInSeconds);
thermostatSuggestion.effectiveTime = effectiveTime;
thermostatSuggestion.expirationTime = effectiveTime + (commandData.expirationInMinutes * kSecondsPerMinute);
err = delegate->AppendToThermostatSuggestionsList(thermostatSuggestion);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to AppendToThermostatSuggestionsList at endpoint %u with error: %" CHIP_ERROR_FORMAT, endpoint,
err.Format());
commandObj->AddStatus(commandPath, Status::Failure);
return true;
}
MatterReportingAttributeChangeCallback(endpoint, Thermostat::Id, ThermostatSuggestions::Id);
// Re-evaluate the current thermostat suggestion.
delegate->ReEvaluateCurrentSuggestion();
Commands::AddThermostatSuggestionResponse::Type response;
response.uniqueID = uniqueID;
commandObj->AddResponse(commandPath, response);
return true;
}
bool RemoveThermostatSuggestion(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::RemoveThermostatSuggestion::DecodableType & commandData)
{
EndpointId endpoint = commandPath.mEndpointId;
auto delegate = GetDelegate(endpoint);
if (delegate == nullptr)
{
ChipLogError(Zcl, "Delegate is null for endpoint %u", endpoint);
commandObj->AddStatus(commandPath, Status::InvalidInState);
return true;
}
Status status = RemoveFromThermostatSuggestionsList(delegate, commandData.uniqueID);
if (status != Status::Success)
{
ChipLogError(Zcl,
"Failed to RemoveFromThermostatSuggestionsList at endpoint %u with uniqueID: %u status:" ChipLogFormatIMStatus,
endpoint, commandData.uniqueID, ChipLogValueIMStatus(status));
commandObj->AddStatus(commandPath, status);
return true;
}
MatterReportingAttributeChangeCallback(endpoint, Thermostat::Id, ThermostatSuggestions::Id);
// Remove expired suggestions if any and re-evaluate the current thermostat suggestion.
RemoveExpiredSuggestions(delegate);
delegate->ReEvaluateCurrentSuggestion();
commandObj->AddStatus(commandPath, Status::Success);
return true;
}
} // namespace Thermostat
} // namespace Clusters
} // namespace app
} // namespace chip
bool emberAfThermostatClusterAddThermostatSuggestionCallback(CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Commands::AddThermostatSuggestion::DecodableType & commandData)
{
return Thermostat::AddThermostatSuggestion(commandObj, commandPath, commandData);
}
bool emberAfThermostatClusterRemoveThermostatSuggestionCallback(
CommandHandler * commandObj, const ConcreteCommandPath & commandPath,
const Clusters::Thermostat::Commands::RemoveThermostatSuggestion::DecodableType & commandData)
{
return Thermostat::RemoveThermostatSuggestion(commandObj, commandPath, commandData);
}