blob: c011fab945bd7adbabc037551630f32bb92e5442 [file]
/*
*
* 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 "ThermostatClusterSetpoints.h"
#include "Setpoint.h"
#include "Temperature.h"
#include "ThermostatCluster.h"
#include "Setpoints.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 <clusters/Thermostat/Metadata.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::Structs;
using namespace chip::app::Clusters::Thermostat::Attributes;
using namespace Protocols::InteractionModel;
namespace chip {
namespace app {
namespace Clusters {
namespace Thermostat {
Status ValidateSetpointChange(Setpoints & setpoints, chip::AttributeId attributeId, temperature value,
SetpointAttributes & changedAttributes)
{
switch (attributeId)
{
case OccupiedHeatingSetpoint::Id:
return setpoints.ChangeRangeHeating(setpoints.occupiedRange, value, Setpoints::ClampMode::kDontClamp, changedAttributes);
case OccupiedCoolingSetpoint::Id:
return setpoints.ChangeRangeCooling(setpoints.occupiedRange, value, Setpoints::ClampMode::kDontClamp, changedAttributes);
case UnoccupiedHeatingSetpoint::Id:
if (!setpoints.occupancySupported)
{
return Status::UnsupportedAttribute;
}
return setpoints.ChangeRangeHeating(setpoints.unoccupiedRange, value, Setpoints::ClampMode::kDontClamp, changedAttributes);
case UnoccupiedCoolingSetpoint::Id:
if (!setpoints.occupancySupported)
{
return Status::UnsupportedAttribute;
}
return setpoints.ChangeRangeCooling(setpoints.unoccupiedRange, value, Setpoints::ClampMode::kDontClamp, changedAttributes);
case MinHeatSetpointLimit::Id:
if (!setpoints.heatSupported)
{
return Status::UnsupportedAttribute;
}
return setpoints.ChangeLimitMinimum(setpoints.userHeatLimits, setpoints.absoluteHeatLimits, value, changedAttributes);
case MaxHeatSetpointLimit::Id:
if (!setpoints.heatSupported)
{
return Status::UnsupportedAttribute;
}
return setpoints.ChangeLimitMaximum(setpoints.userHeatLimits, setpoints.absoluteHeatLimits, value, changedAttributes);
case MinCoolSetpointLimit::Id:
if (!setpoints.coolSupported)
{
return Status::UnsupportedAttribute;
}
return setpoints.ChangeLimitMinimum(setpoints.userCoolLimits, setpoints.absoluteCoolLimits, value, changedAttributes);
case MaxCoolSetpointLimit::Id:
if (!setpoints.coolSupported)
{
return Status::UnsupportedAttribute;
}
return setpoints.ChangeLimitMaximum(setpoints.userCoolLimits, setpoints.absoluteCoolLimits, value, changedAttributes);
default:
return Status::UnsupportedAttribute;
}
}
void HandleSetpointWrite(const ConcreteAttributePath & attributePath)
{
Setpoints setpoints;
auto status = LoadSetpoints(attributePath.mEndpointId, setpoints);
if (status != Status::Success)
{
ChipLogError(Zcl, "Thermostat: HandleSetpointWrite: failed to load setpoints: " ChipLogFormatIMStatus,
ChipLogValueIMStatus(status));
return;
}
temperature temp;
switch (attributePath.mAttributeId)
{
case OccupiedHeatingSetpoint::Id:
temp = setpoints.occupiedRange.heating.Temperature();
break;
case OccupiedCoolingSetpoint::Id:
temp = setpoints.occupiedRange.cooling.Temperature();
break;
case UnoccupiedHeatingSetpoint::Id:
temp = setpoints.unoccupiedRange.heating.Temperature();
break;
case UnoccupiedCoolingSetpoint::Id:
temp = setpoints.unoccupiedRange.cooling.Temperature();
break;
case MinHeatSetpointLimit::Id:
temp = setpoints.userHeatLimits.minimum.Temperature();
break;
case MaxHeatSetpointLimit::Id:
temp = setpoints.userHeatLimits.maximum.Temperature();
break;
case MinCoolSetpointLimit::Id:
temp = setpoints.userCoolLimits.minimum.Temperature();
break;
case MaxCoolSetpointLimit::Id:
temp = setpoints.userCoolLimits.maximum.Temperature();
break;
default:
ChipLogError(Zcl, "Thermostat: HandleSetpointWrite: unexpected attributeId: 0x%" PRIx32, attributePath.mAttributeId);
return;
}
SetpointAttributes changedAttributes;
changedAttributes.Set(attributePath.mAttributeId);
changedAttributes.ClearFirstDirtyAttribute();
// In theory, this should always succeed, as the values will have been filtered in
// MatterThermostatClusterServerPreAttributeChangedCallback
if (ValidateSetpointChange(setpoints, attributePath.mAttributeId, temp, changedAttributes) != Status::Success)
{
ChipLogError(Zcl, "Thermostat: HandleSetpointWrite: failed to validate setpoint after write");
return;
}
// Clear the attribute that was just written
changedAttributes.Clear(attributePath.mAttributeId);
if (!changedAttributes.Empty())
{
// If there were any other attributes that were changed, we save the first one
status = SaveFirstDirtySetpoint(attributePath.mEndpointId, setpoints, changedAttributes);
if (status != Status::Success)
{
ChipLogError(Zcl, "Thermostat: HandleSetpointWrite: failed to save dirty setpoint: " ChipLogFormatIMStatus,
ChipLogValueIMStatus(status));
}
}
}
Status SetpointRaiseLower(const EndpointId endpointId, const Commands::SetpointRaiseLower::DecodableType & commandData)
{
auto & mode = commandData.mode;
int16_t amount = static_cast<int16_t>(commandData.amount * 10);
Setpoints setpoints;
auto status = LoadSetpoints(endpointId, setpoints);
if (status != Status::Success)
{
return status;
}
OccupancyBitmap isOccupied = OccupancyBitmap(0);
if (setpoints.occupancySupported)
{
BitMask<OccupancyBitmap, uint8_t> occupancy;
status = Occupancy::Get(endpointId, &occupancy);
if (status != Status::Success)
{
return status;
}
isOccupied = occupancy.Has(OccupancyBitmap::kOccupied) ? OccupancyBitmap::kOccupied : OccupancyBitmap(0);
}
else
{
isOccupied = OccupancyBitmap::kOccupied;
}
auto & range = setpoints.GetRange(isOccupied);
chip::Optional<temperature> heat;
chip::Optional<temperature> cool;
switch (mode)
{
case SetpointRaiseLowerModeEnum::kBoth:
if (setpoints.heatSupported)
{
heat.SetValue(static_cast<temperature>(range.heating.Temperature() + amount));
}
if (setpoints.coolSupported)
{
cool.SetValue(static_cast<temperature>(range.cooling.Temperature() + amount));
}
break;
case SetpointRaiseLowerModeEnum::kHeat:
if (setpoints.heatSupported)
{
heat.SetValue(static_cast<temperature>(range.heating.Temperature() + amount));
}
break;
case SetpointRaiseLowerModeEnum::kCool:
if (setpoints.coolSupported)
{
cool.SetValue(static_cast<temperature>(range.cooling.Temperature() + amount));
}
break;
default:
return Status::InvalidCommand;
}
if (!heat.HasValue() && !cool.HasValue())
{
return Status::InvalidCommand;
}
SetpointAttributes changedAttributes;
status = setpoints.ChangeRange(range, heat, cool, Setpoints::ClampMode::kClamp, changedAttributes);
if (status != Status::Success)
{
return status;
}
return SaveSetpoints(endpointId, setpoints, changedAttributes);
}
} // namespace Thermostat
} // namespace Clusters
} // namespace app
} // namespace chip