blob: b474d8ee87df9554689112064b4da3cbf2155c8a [file] [log] [blame]
/**
*
* 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 "service-area-server.h"
#include "service-area-delegate.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/Commands.h>
#include <app/AttributeAccessInterfaceRegistry.h>
#include <app/CommandHandler.h>
#include <app/CommandHandlerInterfaceRegistry.h>
#include <app/ConcreteCommandPath.h>
#include <app/EventLogging.h>
#include <app/InteractionModelEngine.h>
#include <app/reporting/reporting.h>
#include <app/util/attribute-storage.h>
#include <app/util/util.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <protocols/interaction_model/StatusCode.h>
using Status = chip::Protocols::InteractionModel::Status;
namespace chip {
namespace app {
namespace Clusters {
namespace ServiceArea {
// ****************************************************************************
// Service Area Server Instance
Instance::Instance(StorageDelegate * storageDelegate, Delegate * aDelegate, EndpointId aEndpointId, BitMask<Feature> aFeature) :
AttributeAccessInterface(MakeOptional(aEndpointId), Id), CommandHandlerInterface(MakeOptional(aEndpointId), Id),
mStorageDelegate(storageDelegate), mDelegate(aDelegate), mEndpointId(aEndpointId), mClusterId(Id), mFeature(aFeature)
{
ChipLogProgress(Zcl, "Service Area: Instance constructor");
mDelegate->SetInstance(this);
}
Instance::~Instance()
{
CommandHandlerInterfaceRegistry::Instance().UnregisterCommandHandler(this);
AttributeAccessInterfaceRegistry::Instance().Unregister(this);
}
CHIP_ERROR Instance::Init()
{
ChipLogProgress(Zcl, "Service Area: INIT");
// Check if the cluster has been selected in zap
VerifyOrReturnError(emberAfContainsServer(mEndpointId, Id), CHIP_ERROR_INVALID_ARGUMENT,
ChipLogError(Zcl, "Service Area: The cluster with Id %lu was not enabled in zap.", long(Id)));
ReturnErrorOnFailure(CommandHandlerInterfaceRegistry::Instance().RegisterCommandHandler(this));
VerifyOrReturnError(AttributeAccessInterfaceRegistry::Instance().Register(this), CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(mStorageDelegate->Init());
return mDelegate->Init();
}
//*************************************************************************
// core functions
CHIP_ERROR Instance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
ChipLogDetail(Zcl, "Service Area: Reading attribute with ID " ChipLogFormatMEI, ChipLogValueMEI(aPath.mAttributeId));
switch (aPath.mAttributeId)
{
case Attributes::SupportedAreas::Id:
return ReadSupportedAreas(aEncoder);
case Attributes::SupportedMaps::Id:
return ReadSupportedMaps(aEncoder);
case Attributes::SelectedAreas::Id:
return ReadSelectedAreas(aEncoder);
case Attributes::CurrentArea::Id:
return aEncoder.Encode(GetCurrentArea());
case Attributes::EstimatedEndTime::Id:
return aEncoder.Encode(GetEstimatedEndTime());
case Attributes::Progress::Id:
return ReadProgress(aEncoder);
case Attributes::FeatureMap::Id:
return aEncoder.Encode(mFeature);
default:
ChipLogProgress(Zcl, "Service Area: Unsupported attribute with ID " ChipLogFormatMEI, ChipLogValueMEI(aPath.mAttributeId));
}
return CHIP_NO_ERROR;
}
void Instance::InvokeCommand(HandlerContext & handlerContext)
{
switch (handlerContext.mRequestPath.mCommandId)
{
case Commands::SelectAreas::Id:
return CommandHandlerInterface::HandleCommand<Commands::SelectAreas::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & req) { HandleSelectAreasCmd(ctx, req); });
case Commands::SkipArea::Id:
return CommandHandlerInterface::HandleCommand<Commands::SkipArea::DecodableType>(
handlerContext, [this](HandlerContext & ctx, const auto & req) { HandleSkipAreaCmd(ctx, req); });
}
}
//*************************************************************************
// attribute readers
CHIP_ERROR Instance::ReadSupportedAreas(AttributeValueEncoder & aEncoder)
{
if (GetNumberOfSupportedAreas() == 0)
{
return aEncoder.EncodeEmptyList();
}
return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR {
uint8_t locationIndex = 0;
AreaStructureWrapper supportedArea;
while (GetSupportedAreaByIndex(locationIndex++, supportedArea))
{
ReturnErrorOnFailure(encoder.Encode(supportedArea));
}
return CHIP_NO_ERROR;
});
}
CHIP_ERROR Instance::ReadSupportedMaps(AttributeValueEncoder & aEncoder)
{
if (GetNumberOfSupportedMaps() == 0)
{
return aEncoder.EncodeEmptyList();
}
return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR {
uint32_t mapIndex = 0;
MapStructureWrapper supportedMap;
while (GetSupportedMapByIndex(mapIndex++, supportedMap))
{
ReturnErrorOnFailure(encoder.Encode(supportedMap));
}
return CHIP_NO_ERROR;
});
}
CHIP_ERROR Instance::ReadSelectedAreas(AttributeValueEncoder & aEncoder)
{
if (GetNumberOfSelectedAreas() == 0)
{
return aEncoder.EncodeEmptyList();
}
return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR {
uint32_t locationIndex = 0;
uint32_t selectedArea;
while (GetSelectedAreaByIndex(locationIndex++, selectedArea))
{
ReturnErrorOnFailure(encoder.Encode(selectedArea));
}
return CHIP_NO_ERROR;
});
}
CHIP_ERROR Instance::ReadProgress(AttributeValueEncoder & aEncoder)
{
if (GetNumberOfProgressElements() == 0)
{
return aEncoder.EncodeEmptyList();
}
return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR {
uint32_t locationIndex = 0;
Structs::ProgressStruct::Type progressElement;
while (GetProgressElementByIndex(locationIndex++, progressElement))
{
ReturnErrorOnFailure(encoder.Encode(progressElement));
}
return CHIP_NO_ERROR;
});
}
//*************************************************************************
// command handlers
void Instance::HandleSelectAreasCmd(HandlerContext & ctx, const Commands::SelectAreas::DecodableType & req)
{
ChipLogDetail(Zcl, "Service Area: HandleSelectAreasCmd");
// On receipt of this command the device SHALL respond with a SelectAreasResponse command.
auto exitResponse = [ctx](SelectAreasStatus status, CharSpan statusText) {
Commands::SelectAreasResponse::Type response{
.status = status,
.statusText = statusText,
};
ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
};
size_t numberOfAreas = 0;
// Get the number of Selected Areas in the command parameter and check that it is valid.
{
if (CHIP_NO_ERROR != req.newAreas.ComputeSize(&numberOfAreas))
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::InvalidCommand);
return;
}
// If the device determines that it can't operate at all locations from the list,
// the SelectAreasResponse command's Status field SHALL indicate InvalidSet.
if (numberOfAreas > kMaxNumSelectedAreas)
{
exitResponse(SelectAreasStatus::kInvalidSet, "invalid number of locations"_span);
return;
}
}
uint32_t selectedAreasBuffer[kMaxNumSelectedAreas];
auto selectedAreasSpan = Span<uint32_t>(selectedAreasBuffer, kMaxNumSelectedAreas);
uint32_t numberOfSelectedAreas = 0;
// Closure for checking if an area ID exists in the selectedAreasSpan
auto areaAlreadyExists = [&numberOfSelectedAreas, &selectedAreasSpan](uint32_t areaId) {
for (uint32_t i = 0; i < numberOfSelectedAreas; i++)
{
if (areaId == selectedAreasSpan[i])
{
return true;
}
}
return false;
};
// if number of selected locations in parameter matches number in attribute - the locations *might* be the same
bool matchesCurrentSelectedAreas = (numberOfAreas == GetNumberOfSelectedAreas());
// do as much parameter validation as we can
if (numberOfAreas != 0)
{
uint32_t ignoredIndex = 0;
uint32_t oldSelectedArea;
auto iAreaIter = req.newAreas.begin();
while (iAreaIter.Next())
{
uint32_t selectedArea = iAreaIter.GetValue();
// If aSelectedArea is already in selectedAreasSpan skip
if (areaAlreadyExists(selectedArea))
{
continue;
}
// each item in this list SHALL match the AreaID field of an entry on the SupportedAreas attribute's list
// If the Status field is set to UnsupportedArea, the StatusText field SHALL be an empty string.
if (!mStorageDelegate->IsSupportedArea(selectedArea))
{
exitResponse(SelectAreasStatus::kUnsupportedArea, ""_span);
return;
}
// check to see if parameter list and attribute still match
if (matchesCurrentSelectedAreas)
{
if (!GetSelectedAreaByIndex(ignoredIndex, oldSelectedArea) || (selectedArea != oldSelectedArea))
{
matchesCurrentSelectedAreas = false;
}
}
selectedAreasSpan[numberOfSelectedAreas] = selectedArea;
numberOfSelectedAreas += 1;
}
// after iterating with Next through DecodableType - check for failure
if (CHIP_NO_ERROR != iAreaIter.GetStatus())
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::InvalidCommand);
return;
}
}
selectedAreasSpan.reduce_size(numberOfSelectedAreas);
// If the newAreas field is the same as the value of the SelectedAreas attribute
// the SelectAreasResponse command SHALL have the Status field set to Success and
// the StatusText field MAY be supplied with a human-readable string or include an empty string.
if (matchesCurrentSelectedAreas)
{
exitResponse(SelectAreasStatus::kSuccess, ""_span);
return;
}
char delegateStatusBuffer[kMaxSizeStatusText];
MutableCharSpan delegateStatusText(delegateStatusBuffer);
// If the current state of the device doesn't allow for the locations to be selected,
// the SelectAreasResponse command SHALL have the Status field set to InvalidInMode.
// if the Status field is set to InvalidInMode, the StatusText field SHOULD indicate why the request is not allowed,
// given the current mode of the device, which may involve other clusters.
// (note - locationStatusText to be filled out by delegated function for if return value is false)
if (!mDelegate->IsSetSelectedAreasAllowed(delegateStatusText))
{
exitResponse(SelectAreasStatus::kInvalidInMode, delegateStatusText);
return;
}
// Reset in case the delegate accidentally modified this string.
delegateStatusText = MutableCharSpan(delegateStatusBuffer);
// ask the device to handle SelectAreas Command
// (note - locationStatusText to be filled out by delegated function for kInvalidInMode and InvalidSet)
auto locationStatus = SelectAreasStatus::kSuccess;
if (!mDelegate->IsValidSelectAreasSet(selectedAreasSpan, locationStatus, delegateStatusText))
{
exitResponse(locationStatus, delegateStatusText);
return;
}
{
// If the device successfully accepts the request, the server will attempt to operate at the location(s)
// indicated by the entries of the newArea field, when requested to operate,
// the SelectAreasResponse command SHALL have the Status field set to Success,
// and the SelectedAreas attribute SHALL be set to the value of the newAreas field.
mStorageDelegate->ClearSelectedAreasRaw();
if (numberOfAreas != 0)
{
uint32_t ignored;
for (uint32_t areaId : selectedAreasSpan)
{
mStorageDelegate->AddSelectedAreaRaw(areaId, ignored);
}
}
NotifySelectedAreasChanged();
}
exitResponse(SelectAreasStatus::kSuccess, ""_span);
}
void Instance::HandleSkipAreaCmd(HandlerContext & ctx, const Commands::SkipArea::DecodableType & req)
{
ChipLogDetail(Zcl, "Service Area: HandleSkipArea");
// On receipt of this command the device SHALL respond with a SkipAreaResponse command.
auto exitResponse = [ctx](SkipAreaStatus status, CharSpan statusText) {
Commands::SkipAreaResponse::Type response{
.status = status,
.statusText = statusText,
};
ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
};
// If the SelectedAreas attribute is empty, the SkipAreaResponse command’s Status field SHALL indicate InvalidAreaList.
if (GetNumberOfSelectedAreas() == 0)
{
exitResponse(SkipAreaStatus::kInvalidAreaList, ""_span);
return;
}
// If the SkippedArea field does not match an entry in the SupportedAreas attribute, the SkipAreaResponse command’s Status field
// SHALL indicate InvalidSkippedArea.
if (!mStorageDelegate->IsSupportedArea(req.skippedArea))
{
ChipLogError(Zcl, "SkippedArea (%" PRIu32 ") is not in the SupportedAreas attribute.", req.skippedArea);
exitResponse(SkipAreaStatus::kInvalidSkippedArea, ""_span);
return;
}
// have the device attempt to skip
// If the Status field is not set to Success, or InvalidAreaList, the StatusText field SHALL include a vendor defined error
// description. InvalidInMode | The received request cannot be handled due to the current mode of the device. (skipStatusText to
// be filled out by delegated function on failure.)
char skipStatusBuffer[kMaxSizeStatusText];
MutableCharSpan skipStatusText(skipStatusBuffer);
if (!mDelegate->HandleSkipArea(req.skippedArea, skipStatusText))
{
exitResponse(SkipAreaStatus::kInvalidInMode, skipStatusText);
return;
}
exitResponse(SkipAreaStatus::kSuccess, ""_span);
}
//*************************************************************************
// attribute notifications
void Instance::NotifySupportedAreasChanged()
{
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::SupportedAreas::Id);
}
void Instance::NotifySupportedMapsChanged()
{
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::SupportedMaps::Id);
}
void Instance::NotifySelectedAreasChanged()
{
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::SelectedAreas::Id);
}
void Instance::NotifyCurrentAreaChanged()
{
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::CurrentArea::Id);
}
void Instance::NotifyEstimatedEndTimeChanged()
{
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::EstimatedEndTime::Id);
}
void Instance::NotifyProgressChanged()
{
MatterReportingAttributeChangeCallback(mEndpointId, mClusterId, Attributes::Progress::Id);
}
// ****************************************************************************
// Supported Areas manipulators
bool Instance::IsValidSupportedArea(const AreaStructureWrapper & aArea)
{
// If the LocationInfo field is null, the LandmarkInfo field SHALL NOT be null.
// If the LandmarkInfo field is null, the LocationInfo field SHALL NOT be null.
if (aArea.areaInfo.locationInfo.IsNull() && aArea.areaInfo.landmarkInfo.IsNull())
{
ChipLogDetail(Zcl, "IsValidAsSupportedArea %" PRIu32 " - must have locationInfo and/or LandmarkInfo", aArea.areaID);
return false;
}
// If LocationInfo is not null, and its LocationName field is an empty string, at least one of the following SHALL NOT
// be null: LocationInfo's FloorNumber field, LocationInfo's AreaType field, the LandmarkInfo
if (!aArea.areaInfo.locationInfo.IsNull())
{
if (aArea.areaInfo.locationInfo.Value().locationName.empty() && aArea.areaInfo.locationInfo.Value().floorNumber.IsNull() &&
aArea.areaInfo.locationInfo.Value().areaType.IsNull() && aArea.areaInfo.landmarkInfo.IsNull())
{
ChipLogDetail(
Zcl, "IsValidAsSupportedArea %" PRIu32 " - AreaName is empty string, FloorNumber, AreaType, LandmarkInfo are null",
aArea.areaID);
return false;
}
}
// The mapID field SHALL be null if SupportedMaps is not supported or SupportedMaps is an empty list.
if (mFeature.Has(Feature::kMaps) && (GetNumberOfSupportedMaps() > 0))
{
if (aArea.mapID.IsNull())
{
ChipLogDetail(Zcl, "IsValidSupportedArea %" PRIu32 " - map Id should not be null when there are supported maps",
aArea.areaID);
return false;
}
// If the SupportedMaps attribute is not null, mapID SHALL be the ID of an entry from the SupportedMaps attribute.
if (!mStorageDelegate->IsSupportedMap(aArea.mapID.Value()))
{
ChipLogError(Zcl, "IsValidSupportedArea %" PRIu32 " - map Id %" PRIu32 " is not in supported map list", aArea.areaID,
aArea.mapID.Value());
return false;
}
}
else
{
if (!aArea.mapID.IsNull())
{
ChipLogDetail(Zcl, "IsValidSupportedArea %" PRIu32 " - map Id %" PRIu32 " is not in empty supported map list",
aArea.areaID, aArea.mapID.Value());
return false;
}
}
return true;
}
bool Instance::IsUniqueSupportedArea(const AreaStructureWrapper & aArea, bool ignoreAreaId)
{
BitMask<AreaStructureWrapper::IsEqualConfig> config;
if (ignoreAreaId)
{
config.Set(AreaStructureWrapper::IsEqualConfig::kIgnoreAreaID);
}
// If the SupportedMaps attribute is not null, each entry in this list SHALL have a unique value for the combination of the
// MapID and LocationInfo fields. If the SupportedMaps attribute is null, each entry in this list SHALL have a unique value for
// the LocationInfo field.
if (GetNumberOfSupportedMaps() == 0)
{
config.Set(AreaStructureWrapper::IsEqualConfig::kIgnoreMapId);
}
uint8_t locationIndex = 0;
AreaStructureWrapper entry;
while (GetSupportedAreaByIndex(locationIndex++, entry))
{
if (aArea.IsEqual(entry, config))
{
return false;
}
}
return true;
}
bool Instance::ReportEstimatedEndTimeChange(const DataModel::Nullable<uint32_t> & aEstimatedEndTime)
{
if (mEstimatedEndTime == aEstimatedEndTime)
{
return false;
}
// The value of this attribute SHALL only be reported in the following cases:
// - when it changes from null.
if (mEstimatedEndTime.IsNull())
{
return true;
}
// - when it changes from 0
if (mEstimatedEndTime.Value() == 0)
{
return true;
}
if (aEstimatedEndTime.IsNull())
{
return false;
}
// From this point we know that mEstimatedEndTime and aEstimatedEndTime are not null and not the same.
// - when it changes to 0
if (aEstimatedEndTime.Value() == 0)
{
return true;
}
// - when it decreases
return (aEstimatedEndTime.Value() < mEstimatedEndTime.Value());
}
void Instance::HandleSupportedAreasUpdated()
{
// If there are no more Supported Areas, clear all selected areas, current area, and progress.
if (GetNumberOfSupportedAreas() == 0)
{
ClearSelectedAreas();
SetCurrentArea(DataModel::NullNullable);
ClearProgress();
return;
}
// Remove Selected Areas elements that do not exist is the Supported Areas attribute.
{
uint32_t i = 0;
uint32_t areaId = 0;
uint32_t areasToRemoveIndex = 0;
uint32_t areasToRemoveBuffer[kMaxNumSelectedAreas];
Span<uint32_t> areasToRemoveSpan(areasToRemoveBuffer);
while (GetSelectedAreaByIndex(i, areaId))
{
if (!mStorageDelegate->IsSupportedArea(areaId))
{
areasToRemoveSpan[areasToRemoveIndex] = areaId;
areasToRemoveIndex++;
}
i++;
}
areasToRemoveSpan.reduce_size(areasToRemoveIndex);
for (auto id : areasToRemoveSpan)
{
if (!RemoveSelectedAreas(id))
{
ChipLogError(Zcl, "HandleSupportedAreasUpdated: Failed to remove area %" PRIu32 " from selected areas", id);
}
}
}
// Set current Area to null if current area is not in the supported areas attribute.
{
auto currentAreaId = GetCurrentArea();
if (!currentAreaId.IsNull() && !mStorageDelegate->IsSupportedArea(currentAreaId.Value()))
{
SetCurrentArea(DataModel::NullNullable);
}
}
// Remove Progress elements associated with areas that do not exist is the Supported Areas attribute.
{
uint32_t i = 0;
Structs::ProgressStruct::Type tempProgressElement;
uint32_t progressToRemoveIndex = 0;
uint32_t progressToRemoveBuffer[kMaxNumProgressElements];
Span<uint32_t> progressToRemoveSpan(progressToRemoveBuffer);
while (mStorageDelegate->GetProgressElementByIndex(i, tempProgressElement))
{
if (mStorageDelegate->IsSupportedArea(tempProgressElement.areaID))
{
progressToRemoveSpan[progressToRemoveIndex] = tempProgressElement.areaID;
progressToRemoveIndex++;
}
i++;
}
progressToRemoveSpan.reduce_size(progressToRemoveIndex);
for (auto areaId : progressToRemoveSpan)
{
if (!mStorageDelegate->RemoveProgressElementRaw(areaId))
{
ChipLogError(Zcl, "HandleSupportedAreasUpdated: Failed to remove progress element with area ID %" PRIu32 "",
areaId);
}
}
}
}
uint32_t Instance::GetNumberOfSupportedAreas()
{
return mStorageDelegate->GetNumberOfSupportedAreas();
}
bool Instance::GetSupportedAreaByIndex(uint32_t listIndex, AreaStructureWrapper & aSupportedArea)
{
return mStorageDelegate->GetSupportedAreaByIndex(listIndex, aSupportedArea);
}
bool Instance::GetSupportedAreaById(uint32_t aAreaId, uint32_t & listIndex, AreaStructureWrapper & aSupportedArea)
{
return mStorageDelegate->GetSupportedAreaById(aAreaId, listIndex, aSupportedArea);
}
bool Instance::AddSupportedArea(AreaStructureWrapper & aNewArea)
{
// Does device mode allow this attribute to be updated?
if (!mDelegate->IsSupportedAreasChangeAllowed())
{
return false;
}
// Check there is space for the entry.
if (GetNumberOfSupportedAreas() >= kMaxNumSupportedAreas)
{
ChipLogError(Zcl, "AddSupportedAreaRaw %" PRIu32 " - too many entries", aNewArea.areaID);
return false;
}
// Verify cluster requirements concerning valid fields and field relationships.
if (!IsValidSupportedArea(aNewArea))
{
ChipLogError(Zcl, "AddSupportedAreaRaw %" PRIu32 " - not a valid location object", aNewArea.areaID);
return false;
}
// Each entry in Supported Areas SHALL have a unique value for the ID field.
// If the SupportedMaps attribute is not null, each entry in this list SHALL have a unique value for the combination of the
// MapID and AreaInfo fields. If the SupportedMaps attribute is null, each entry in this list SHALL have a unique value for
// the AreaInfo field.
if (!IsUniqueSupportedArea(aNewArea, false))
{
ChipLogError(Zcl, "AddSupportedAreaRaw %" PRIu32 " - not a unique location object", aNewArea.areaID);
return false;
}
// Add the SupportedArea to the SupportedAreas attribute.
uint32_t ignoredIndex;
if (!mStorageDelegate->AddSupportedAreaRaw(aNewArea, ignoredIndex))
{
return false;
}
NotifySupportedAreasChanged();
return true;
}
bool Instance::ModifySupportedArea(AreaStructureWrapper & aNewArea)
{
bool mapIDChanged = false;
uint32_t listIndex;
// get existing supported location to modify
AreaStructureWrapper supportedArea;
if (!GetSupportedAreaById(aNewArea.areaID, listIndex, supportedArea))
{
ChipLogError(Zcl, "ModifySupportedAreaRaw %" PRIu32 " - not a supported areaID", aNewArea.areaID);
return false;
}
{
// check for mapID change
if ((aNewArea.mapID.IsNull() != supportedArea.mapID.IsNull()) ||
(!aNewArea.mapID.IsNull() && !supportedArea.mapID.IsNull() && (aNewArea.mapID.Value() != supportedArea.mapID.Value())))
{
// does device mode allow this attribute to be updated?
if (!mDelegate->IsSupportedAreasChangeAllowed())
{
return false;
}
mapIDChanged = true;
}
// verify cluster requirements concerning valid fields and field relationships
if (!IsValidSupportedArea(aNewArea))
{
ChipLogError(Zcl, "ModifySupportedAreaRaw %" PRIu32 " - not a valid location object", aNewArea.areaID);
return false;
}
// Updated location description must not match another existing location description.
// We ignore comparing the area ID as one of the locations will match this one.
if (!IsUniqueSupportedArea(aNewArea, true))
{
ChipLogError(Zcl, "ModifySupportedAreaRaw %" PRIu32 " - not a unique location object", aNewArea.areaID);
return false;
}
// Replace the supported location with the modified location.
if (!mStorageDelegate->ModifySupportedAreaRaw(listIndex, aNewArea))
{
return false;
}
}
if (mapIDChanged)
{
HandleSupportedAreasUpdated();
}
NotifySupportedAreasChanged();
return true;
}
bool Instance::ClearSupportedAreas()
{
// does device mode allow this attribute to be updated?
if (!mDelegate->IsSupportedAreasChangeAllowed())
{
return false;
}
if (mStorageDelegate->ClearSupportedAreasRaw())
{
HandleSupportedAreasUpdated();
NotifySupportedAreasChanged();
return true;
}
return false;
}
bool Instance::RemoveSupportedArea(uint32_t areaId)
{
if (mStorageDelegate->RemoveSupportedAreaRaw(areaId))
{
HandleSupportedAreasUpdated();
NotifySupportedAreasChanged();
return true;
}
return false;
}
//*************************************************************************
// Supported Maps manipulators
uint32_t Instance::GetNumberOfSupportedMaps()
{
return mStorageDelegate->GetNumberOfSupportedMaps();
}
bool Instance::GetSupportedMapByIndex(uint32_t listIndex, MapStructureWrapper & aSupportedMap)
{
return mStorageDelegate->GetSupportedMapByIndex(listIndex, aSupportedMap);
}
bool Instance::GetSupportedMapById(uint32_t aMapId, uint32_t & listIndex, MapStructureWrapper & aSupportedMap)
{
return mStorageDelegate->GetSupportedMapById(aMapId, listIndex, aSupportedMap);
}
bool Instance::AddSupportedMap(uint32_t aMapId, const CharSpan & aMapName)
{
// check max# of list entries
if (GetNumberOfSupportedMaps() >= kMaxNumSupportedMaps)
{
ChipLogError(Zcl, "AddSupportedMapRaw %" PRIu32 " - maximum number of entries", aMapId);
return false;
}
// Map name SHALL include readable text that describes the map name (cannot be empty string).
if (aMapName.empty())
{
ChipLogError(Zcl, "AddSupportedMapRaw %" PRIu32 " - Name must not be empty string", aMapId);
return false;
}
// Each entry in this list SHALL have a unique value for the Name field.
uint8_t mapIndex = 0;
MapStructureWrapper entry;
while (GetSupportedMapByIndex(mapIndex++, entry))
{
// the name cannot be the same as an existing map
if (entry.IsNameEqual(aMapName))
{
ChipLogError(Zcl, "AddSupportedMapRaw %" PRIu32 " - A map already exists with same name '%.*s'", aMapId,
static_cast<int>(entry.GetName().size()), entry.GetName().data());
return false;
}
// Each entry in this list SHALL have a unique value for the MapID field.
if (aMapId == entry.mapID)
{
ChipLogError(Zcl, "AddSupportedMapRaw - non-unique Id %" PRIu32 "", aMapId);
return false;
}
}
{
// add to supported maps attribute
MapStructureWrapper newMap(aMapId, aMapName);
uint32_t ignoredIndex;
if (!mStorageDelegate->AddSupportedMapRaw(newMap, ignoredIndex))
{
return false;
}
}
// map successfully added
NotifySupportedMapsChanged();
return true;
}
bool Instance::RenameSupportedMap(uint32_t aMapId, const CharSpan & newMapName)
{
uint32_t modifiedIndex;
MapStructureWrapper modifiedMap;
// get existing entry
if (!GetSupportedMapById(aMapId, modifiedIndex, modifiedMap))
{
ChipLogError(Zcl, "RenameSupportedMap Id %" PRIu32 " - map does not exist", aMapId);
return false;
}
// Map name SHALL include readable text that describes the map's name. It cannot be empty string.
if (newMapName.empty())
{
ChipLogError(Zcl, "RenameSupportedMap %" PRIu32 " - Name must not be empty string", aMapId);
return false;
}
// update the local copy of the map
modifiedMap.Set(modifiedMap.mapID, newMapName);
// Each entry in this list SHALL have a unique value for the Name field.
uint32_t loopIndex = 0;
MapStructureWrapper entry;
while (GetSupportedMapByIndex(loopIndex, entry))
{
if (modifiedIndex == loopIndex)
{
continue; // don't check local modified map against its own list entry
}
if (entry.IsNameEqual(newMapName))
{
ChipLogError(Zcl, "RenameSupportedMap %" PRIu32 " - map already exists with same name '%.*s'", aMapId,
static_cast<int>(entry.GetName().size()), entry.GetName().data());
return false;
}
++loopIndex;
}
if (!mStorageDelegate->ModifySupportedMapRaw(modifiedIndex, modifiedMap))
{
return false;
}
// map successfully renamed
NotifySupportedMapsChanged();
return true;
}
bool Instance::ClearSupportedMaps()
{
// does device mode allow this attribute to be updated?
if (!mDelegate->IsSupportedMapChangeAllowed())
{
return false;
}
if (mStorageDelegate->ClearSupportedMapsRaw())
{
ClearSupportedAreas();
NotifySupportedMapsChanged();
return true;
}
return false;
}
bool Instance::RemoveSupportedMap(uint32_t mapId)
{
if (!mStorageDelegate->RemoveSupportedMapRaw(mapId))
{
return false;
}
NotifySupportedMapsChanged();
// If there are no supported maps left, none of the supported areas are vaild and their MapID needs to be null.
if (GetNumberOfSupportedMaps() == 0)
{
ClearSupportedAreas();
return true;
}
// Get the supported area IDs where the map ID matches the removed map ID.
uint32_t supportedAreaIdsBuffer[kMaxNumSupportedAreas];
Span<uint32_t> supportedAreaIdsSpan(supportedAreaIdsBuffer);
{
uint32_t supportedAreaIdsSize = 0;
uint32_t supportedAreasIndex = 0;
AreaStructureWrapper tempSupportedArea;
while (mStorageDelegate->GetSupportedAreaByIndex(supportedAreasIndex, tempSupportedArea))
{
if (tempSupportedArea.mapID == mapId)
{
supportedAreaIdsSpan[supportedAreasIndex] = tempSupportedArea.areaID;
supportedAreaIdsSize++;
}
supportedAreasIndex++;
}
supportedAreaIdsSpan.reduce_size(supportedAreaIdsSize);
}
if (!supportedAreaIdsSpan.empty())
{
for (uint32_t supportedAreaId : supportedAreaIdsSpan)
{
mStorageDelegate->RemoveSupportedAreaRaw(supportedAreaId);
}
HandleSupportedAreasUpdated();
NotifySupportedAreasChanged();
}
return true;
}
//*************************************************************************
// Selected Areas manipulators
uint32_t Instance::GetNumberOfSelectedAreas()
{
return mStorageDelegate->GetNumberOfSelectedAreas();
}
bool Instance::GetSelectedAreaByIndex(uint32_t listIndex, uint32_t & selectedArea)
{
return mStorageDelegate->GetSelectedAreaByIndex(listIndex, selectedArea);
}
bool Instance::AddSelectedArea(uint32_t & aSelectedArea)
{
// check max# of list entries
if (GetNumberOfSelectedAreas() >= kMaxNumSelectedAreas)
{
ChipLogError(Zcl, "AddSelectedAreaRaw %" PRIu32 " - maximum number of entries", aSelectedArea);
return false;
}
// each item in this list SHALL match the AreaID field of an entry on the SupportedAreas attribute's list
if (!mStorageDelegate->IsSupportedArea(aSelectedArea))
{
ChipLogError(Zcl, "AddSelectedAreaRaw %" PRIu32 " - not a supported location", aSelectedArea);
return false;
}
// each entry in this list SHALL have a unique value
if (mStorageDelegate->IsSelectedArea(aSelectedArea))
{
ChipLogError(Zcl, "AddSelectedAreaRaw %" PRIu32 " - duplicated location", aSelectedArea);
return false;
}
// Does device mode allow modification of selected locations?
char locationStatusBuffer[kMaxSizeStatusText];
MutableCharSpan locationStatusText(locationStatusBuffer);
if (!mDelegate->IsSetSelectedAreasAllowed(locationStatusText))
{
ChipLogError(Zcl, "AddSelectedAreaRaw %" PRIu32 " - %.*s", aSelectedArea, static_cast<int>(locationStatusText.size()),
locationStatusText.data());
return false;
}
uint32_t ignoredIndex;
return mStorageDelegate->AddSelectedAreaRaw(aSelectedArea, ignoredIndex);
}
bool Instance::ClearSelectedAreas()
{
if (mStorageDelegate->ClearSelectedAreasRaw())
{
NotifySelectedAreasChanged();
return true;
}
return false;
}
bool Instance::RemoveSelectedAreas(uint32_t areaId)
{
if (mStorageDelegate->RemoveSelectedAreasRaw(areaId))
{
NotifySelectedAreasChanged();
return true;
}
return false;
}
//*************************************************************************
// Current Area manipulators
DataModel::Nullable<uint32_t> Instance::GetCurrentArea()
{
return mCurrentArea;
}
bool Instance::SetCurrentArea(const DataModel::Nullable<uint32_t> & aCurrentArea)
{
// If not null, the value of this attribute SHALL match the AreaID field of an entry on the SupportedAreas attribute's
// list.
if ((!aCurrentArea.IsNull()) && (!mStorageDelegate->IsSupportedArea(aCurrentArea.Value())))
{
ChipLogError(Zcl, "SetCurrentArea %" PRIu32 " - location is not supported", aCurrentArea.Value());
return false;
}
bool notifyChange = mCurrentArea != aCurrentArea;
mCurrentArea = aCurrentArea;
if (notifyChange)
{
NotifyCurrentAreaChanged();
}
// EstimatedEndTime SHALL be null if the CurrentArea attribute is null.
if (mCurrentArea.IsNull())
{
SetEstimatedEndTime(DataModel::NullNullable);
}
return true;
}
//*************************************************************************
// Estimated End Time manipulators
DataModel::Nullable<uint32_t> Instance::GetEstimatedEndTime()
{
return mEstimatedEndTime;
}
bool Instance::SetEstimatedEndTime(const DataModel::Nullable<uint32_t> & aEstimatedEndTime)
{
// EstimatedEndTime SHALL be null if the CurrentArea attribute is null.
if (mCurrentArea.IsNull() && !aEstimatedEndTime.IsNull())
{
ChipLogError(Zcl, "SetEstimatedEndTime - must be null if Current Area is null");
return false;
}
bool notifyChange = ReportEstimatedEndTimeChange(aEstimatedEndTime);
mEstimatedEndTime = aEstimatedEndTime;
if (notifyChange)
{
NotifyEstimatedEndTimeChanged();
}
// success
return true;
}
//*************************************************************************
// Progress list manipulators
uint32_t Instance::GetNumberOfProgressElements()
{
return mStorageDelegate->GetNumberOfProgressElements();
}
bool Instance::GetProgressElementByIndex(uint32_t listIndex, Structs::ProgressStruct::Type & aProgressElement)
{
return mStorageDelegate->GetProgressElementByIndex(listIndex, aProgressElement);
}
bool Instance::GetProgressElementById(uint32_t aAreaId, uint32_t & listIndex, Structs::ProgressStruct::Type & aProgressElement)
{
return mStorageDelegate->GetProgressElementById(aAreaId, listIndex, aProgressElement);
}
bool Instance::AddPendingProgressElement(uint32_t aAreaId)
{
// create progress element
Structs::ProgressStruct::Type inactiveProgress = { aAreaId, OperationalStatusEnum::kPending };
// check max# of list entries
if (GetNumberOfProgressElements() >= kMaxNumProgressElements)
{
ChipLogError(Zcl, "AddPendingProgressElement - maximum number of entries");
return false;
}
// For each entry in this list, the AreaID field SHALL match an entry on the SupportedAreas attribute's list.
if (!mStorageDelegate->IsSupportedArea(aAreaId))
{
ChipLogError(Zcl, "AddPendingProgressElement - not a supported location %" PRIu32 "", aAreaId);
return false;
}
// Each entry in this list SHALL have a unique value for the AreaID field.
if (mStorageDelegate->IsProgressElement(aAreaId))
{
ChipLogError(Zcl, "AddPendingProgressElement - progress element already exists for location %" PRIu32 "", aAreaId);
return false;
}
uint32_t ignoredIndex;
if (!mStorageDelegate->AddProgressElementRaw(inactiveProgress, ignoredIndex))
{
return false;
}
NotifyProgressChanged();
return true;
}
bool Instance::SetProgressStatus(uint32_t aAreaId, OperationalStatusEnum opStatus)
{
uint32_t listIndex;
Structs::ProgressStruct::Type progressElement;
if (!GetProgressElementById(aAreaId, listIndex, progressElement))
{
ChipLogError(Zcl, "SetProgressStatus - progress element does not exist for location %" PRIu32 "", aAreaId);
return false;
}
// If the status value is not changing, there in no need to modify the existing element.
if (progressElement.status == opStatus)
{
return true;
}
// set the progress status in the local copy
progressElement.status = opStatus;
// TotalOperationalTime SHALL be null if the Status field is not set to Completed or Skipped.
if ((opStatus != OperationalStatusEnum::kCompleted) && (opStatus != OperationalStatusEnum::kSkipped))
{
progressElement.totalOperationalTime.Emplace(DataModel::NullNullable);
}
// add the updated element to the progress attribute
if (!mStorageDelegate->ModifyProgressElementRaw(listIndex, progressElement))
{
return false;
}
NotifyProgressChanged();
return true;
}
bool Instance::SetProgressTotalOperationalTime(uint32_t aAreaId, const DataModel::Nullable<uint32_t> & aTotalOperationalTime)
{
uint32_t listIndex;
Structs::ProgressStruct::Type progressElement;
if (!GetProgressElementById(aAreaId, listIndex, progressElement))
{
ChipLogError(Zcl, "SetProgressTotalOperationalTime - progress element does not exist for location %" PRIu32 "", aAreaId);
return false;
}
// If the time value is not changing, there is no need to modify the existing element.
if (progressElement.totalOperationalTime == aTotalOperationalTime)
{
return true;
}
// This attribute SHALL be null if the Status field is not set to Completed or Skipped
if (((progressElement.status == OperationalStatusEnum::kCompleted) ||
(progressElement.status == OperationalStatusEnum::kSkipped)) &&
!aTotalOperationalTime.IsNull())
{
ChipLogError(Zcl,
"SetProgressTotalOperationalTime - location %" PRIu32
" opStatus value %u - can be non-null only if opStatus is "
"Completed or Skipped",
aAreaId, to_underlying(progressElement.status));
return false;
}
// set the time in the local copy
progressElement.totalOperationalTime.Emplace(aTotalOperationalTime);
// add the updated element to the progress attribute
if (!mStorageDelegate->ModifyProgressElementRaw(listIndex, progressElement))
{
return false;
}
NotifyProgressChanged();
return true;
}
bool Instance::SetProgressEstimatedTime(uint32_t aAreaId, const DataModel::Nullable<uint32_t> & aEstimatedTime)
{
uint32_t listIndex;
Structs::ProgressStruct::Type progressElement;
if (!GetProgressElementById(aAreaId, listIndex, progressElement))
{
ChipLogError(Zcl, "SetProgressEstimatedTime - progress element does not exist for location %" PRIu32 "", aAreaId);
return false;
}
// If the time value is not changing, there is no need to modify the existing element.
if (progressElement.estimatedTime == aEstimatedTime)
{
return true;
};
// set the time in the local copy
progressElement.estimatedTime.Emplace(aEstimatedTime);
// add the updated element to the progress attribute
if (!mStorageDelegate->ModifyProgressElementRaw(listIndex, progressElement))
{
return false;
}
NotifyProgressChanged();
return true;
}
bool Instance::ClearProgress()
{
if (mStorageDelegate->ClearProgressRaw())
{
NotifyProgressChanged();
return true;
}
return false;
}
bool Instance::RemoveProgressElement(uint32_t areaId)
{
if (mStorageDelegate->RemoveProgressElementRaw(areaId))
{
NotifyProgressChanged();
return true;
}
return false;
}
// attribute manipulators - Feature Map
bool Instance::HasFeature(Feature feature) const
{
return mFeature.Has(feature);
}
} // namespace ServiceArea
} // namespace Clusters
} // namespace app
} // namespace chip